Download the
WebBooks.zip archive
and install this as a Web Application from Existing Sources.
You will need to add these libraries:
MySQL Driver
Hibernate or HibernateSearch
CommonsLang
This project assumes, like other Books-related examples,
that the test database is accessible
via the guest user
with no password. See the
Java Books Application for details.
The models package plus all hibernate-related
files are the same as those used in the
Java Books Application.
There is no "hibernate search" setup in this application.
Beans
This application uses a number of Java Bean classes.
Our JSP files always instantiate the relevant beans
with a "jsp:useBean" tag.
In some cases the bean is then used related servlet handler, which is
normally intended to be called after a JSP file which instantiates it.
Nevertheless, the servlet must somehow
deal with the possiblity that the bean was not
instantiated before the servlet was called.
A "null bean" turns out to be a very difficult error to track down.
With a few exceptions, the servlet should test if the bean is
not instantiated, i.e., is null, and then
instantiate and set it
throw an error indicating inappropriate usage of the servlet
The nav bean
The nav
bean controls the navigational links using the class:
util.Nav
package util;
import java.util.*;
public class Nav {
public enum Choices { NONE, ANON, LOGIN, ADMIN };
private Choices choice = Choices.NONE;
private Map<String, String>
anon_links = new LinkedHashMap<String, String>(),
admin_links = new LinkedHashMap<String, String>(),
login_links = new LinkedHashMap<String, String>();
public Nav() {
anon_links.put(".", "Home");
anon_links.put("cart.jsp", "View Cart");
anon_links.put("admin.jsp", "Admin Access");
login_links.put(".", "Home");
admin_links.put(".", "Home");
admin_links.put("admin.jsp", "Admin Access");
admin_links.put("add.jsp", "Add A Book");
admin_links.put("logout.jsp", "Admin Logout");
}
public void setChoice(Choices choice) { this.choice = choice; }
public Map<String, String> getLinks() {
if (choice == Choices.ANON) { return anon_links; }
if (choice == Choices.ADMIN) { return admin_links; }
if (choice == Choices.LOGIN) { return login_links; }
return null;
}
}
All scripts which include nav.jsp must define this bean:
nav.setChoice(util.Nav.Choices.____); // one of: ANON, ADMIN, LOGIN
The navigational link generator script is this:
nav.jsp
<%@ page import="java.util.*" %>
<%
// NetBeans flags this as an error because the
// definition of "nav" is outside the page
Map<String,String> links = nav.getLinks();
if (links == null) return;
// Stacking the navigational actions makes debugging easier.
// Set to false for final version.
boolean stackNav = true;
%>
<table class="nav">
<tr>
<% for( Map.Entry<String,String> entry: links.entrySet()) {
String target = entry.getKey();
String label = entry.getValue();
%>
<td>
<% if (stackNav) { %>
<a href="<%=target%>"><%=label%></a>
<% } else { %>
<a href="javascript:location.replace('<%=target%>')"><%=label%></a>
<% } %>
<% } %>
</td>
</tr>
</table>
The cart bean
The application features the primitive shopping cart which
simply associates a book id number to a quantity
(the number of books).
The cart is maintained by the session bean:
The cart is a Map, and the intended usage of it
is as a Map<Integer,Integer> representing an association
of book ids to a quantity. However, the useBean declaration
does not permit generic specifications.
The authadmin bean
Access to the administrative features of delete and add must be preceded
by authentication. In its simplest form, authentication is controlled
by a session-based boolean object initially set to false.
Upon the user's confirmation
that he/she knows some "password",
the application sets the boolean object's value to true.
The bean declaration is this:
package util;
public class AuthInfo {
private boolean status = false;
public AuthInfo(){ }
public boolean getStatus() { return status; }
public void setStatus(boolean status) { this.status = status; }
}
The AuthInfo class is used
instead of the more obvious (java.lang)
Boolean class because
AuthInfo objects are mutable, meaning that the class
supports get/set methods to read/write the internal boolean value.
In contrast,
Boolean objects are immutable
in the sense that
once initialized, the object's value cannot be changed.
An AuthInfo object used as a bean
has the initial value false and
the successful authentication action will set:
authadmin.setStatus(true);
The test for authentication is thus:
if (authadmin.getStatus()) { /* .. authenticated .. */ }
The db bean
The db bean is simply an instantiation of the models.DB
class propagated throughout the session whenever Hibernate database
access in needed. It serves as a convenience and, in some sense,
an efficiency.
Anonymous Display
Anonymous display is the application's home page: displaying a list
of books where the user can see individual books and add them
to a cart (see next section).
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import org.hibernate.*;
import models.*;
import util.Escape;
public class GetBook extends HttpServlet {
protected void processRequest(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
String id = request.getParameter("id");
int num_id = Integer.parseInt(id);
DB db = (DB) request.getSession().getAttribute("db");
if (db == null) {
db = new DB();
request.setAttribute("db", db);
}
Session sess = db.getSession();
Book book = (Book) sess.load( Book.class, num_id );
String title = book.getTitle();
String type = book.getType();
int qty = book.getQty();
title = Escape.html(title);
title = Escape.javaScript(title);
out.println(
"{"
+ "id:" + id + ", "
+ "title:" + '"' + title + '"' + ", "
+ "type:" + '"' + type + '"' + ", "
+ "qty:" + qty
+ " }"
);
} catch (HibernateException x) {
out.println(x.getCause());
} finally {
out.close();
}
}
// ...
}
Cart Handling
Selecting a book on the home page allows us to "add it to our cart,"
and then redirects to the cart page to show us our accumulated choices.
This is a very primitive form of cart control we can only
add, one at a time, and the cart display is only of the internal
data structure.
index.jsp
<!-- at bottom of document -->
<% if (display.getValue().equals("anon")) {%>
<button id="addtocart">Add To Cart</button>
<% } %>
The Admin link, i.e., admin.jsp
is basically the same as index.jsp with some configuration
with modifications to permit additions and deletions.
Authentication goes like this:
Read the status of the authadmin bean. If true, display the
contents of admin.jsp directly.
If false, forward
to the login.jsp page, offering a form for password entry.
The forwarding makes it that the user only ever "sees" admin.jsp
as the URL.
The login form submission activates an AJAX call to the
Validate
servlet which uses the information
to attempt to match the password with the known "secret" password.
If the entered password matches the secret one,
Validate
sets authadmin's status to true and generates a "SUCCESS" output;
if not the server generates a failure message
and leaves authadmin alone.
The JavaScript activator, in login.jsp alerts the user if
the output is other than "SUCCESS".
If the validation is successful, a reload operation is called,
this time bypassing the login form.
The forwarding/AJAX combination is, in my opinion,
a "perfect fit" for validation because it makes the
login action appear as a temporary impediment to the goal and
when the validation is complete there is no effectively trace left.
admin.jsp
<jsp:useBean id="authadmin" scope="session" class="util.AuthInfo" />
<% if (!authadmin.getStatus()) { %>
<jsp:forward page="login.jsp" />
<% } %>
<!-- the bulk is almost exactly the same as index.jsp with these changes -->
<!-- use this JavaScript file instead of "js/cart.js" -->
<script type="text/javascript" src="js/delete.js"></script>
<!-- set this choice -->
<% nav.setChoice(util.Nav.Choices.ADMIN); %>
<!-- show a delete button -->
<button id="delete">Delete</button>
<!-- omit the add-to-cart button -->
Although the delete option is only available to the admin-authenticated
user, we still need to protect the DeleteBook servlet
from being used by direct manipulation of the browser location.
To do so, we make an initial test of authentication:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import org.hibernate.*;
import util.AuthInfo;
import models.*;
public class DeleteBook extends HttpServlet {
protected void processRequest(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
AuthInfo authadmin
= (AuthInfo) request.getSession().getAttribute("authadmin");
if (authadmin == null || !authadmin.getStatus()) {
out.print("Not Validated");
return;
}
String id = request.getParameter("id");
if (id == null) {
return;
}
int num_id = Integer.parseInt(id);
DB db = (DB) request.getSession().getAttribute("db");
if (db == null) {
throw new Exception("unintended usage: db undefined");
}
Session sess = db.getSession();
sess.getTransaction().begin();
Book book = (Book) sess.load( Book.class, num_id );
sess.delete(book);
sess.flush();
sess.getTransaction().commit();
out.print("SUCCESS");
} catch (HibernateException x) {
out.print(x.getCause());
} catch(Exception x) {
out.print(x);
} finally {
out.close();
}
}
// ...
}
Add
Add makes usage of the jQuery Form plugin. It also introduces some
new jQuery feature whereby we can reset the form components through
the form id as follows
if you know CSS, this notation makes perfect sense:
Note the "authentication protection" employed in the
add.jsp script:
<% if (!authadmin.get()) return; %>
This is only a "strong suggestion" that the unauthenticated
user should not try to add a book; the actual protection
against adding is effected within the AddBook servlet.