ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Web Server Java -- Servlets and JSP
Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11

Program: JabaDot Web News Portal

Here is perhaps the most ambitious program developed in this book. It's the beginnings of a complete "news portal" web site, similar to http://www.slashdot.org/, http://www.deadly.org/, or http://daily.daemonnews.org/. However (and as you should expect!), the entire site is written in Java. Or perhaps should I say "written in or by Java," since the JSP mechanism -- which is written entirely in Java -- turns the JSP pages into Java servlets that get run on this site. The web site is shown in Figure 18-11.

Screen shot.
Figure 18-11. JabaDot welcome page.

Like most portal sites, JabaDot allows some services (such as the current news items and of course the ubiquitous banner ads) without logging in, but requires a login for others. In this figure I am logged in as myself, so I have a list of all available services. The page that supports this view is index.jsp (Example 18-15), which contains a hodgepodge of HTML and Java code.


Example 18-15: index.jsp

<%@page errorPage="oops.jsp"%>
<HTML>
<TITLE>JabaDot - Java News For Ever(yone)</TITLE>
<P ALIGN=CENTER><jsp:include page="/servlet/AdRotator" flush="true"/></P>
<BODY BGCOLOR="#f0f0f0">
<%  HttpSession sess = request.getSession(true);
  User user = (User)sess.getValue("jabadot.login");
%>
<TABLE>
<TD WIDTH=75% ALIGN="TOP">
  <!-- Most of page, at left -->
  <IMG SRC="logo.gif" ALIGN="LEFT"></IMG>
  <BR CLEAR="ALL">
  <jsp:include page="./news.jsp" flush="true"/>
</TD>
<TD WIDTH=25% BGCOLOR="#00cc00" ALIGN="TOP">
  <!-- Rest of page, at right -->
  <FONT COLOR=WHITE NAME="Helvetica,Arial">
  <% if (user == null) { %>
  <FORM ACTION=login.jsp METHOD=POST>
    Name: <INPUT TYPE=TEXT SIZE=8 NAME=nick>
    <br>
    Password: <INPUT TYPE=PASSWORD SIZE=8 NAME=pass>
    <br>
    <INPUT TYPE=SUBMIT VALUE="Login" ALIGN=CENTER>
  </FORM>
  <jsp:include page="public_services.html" flush="true"/>
  <% } else { %>
  Logged in as <%= user.getName(  ) %>
  <jsp:include page="./logged_in_services.html" flush="true"/>
  <li><a href="logout.jsp">Log out</a>
  <% } %>
  </FONT>
</TD>
</TABLE>
</BODY>
</HTML>

As you can see, this code actually starts with a "page" tag (%@page) to specify an error handling page (the error page just prints the stack trace neatly, along with an apology). Then the output of the AdRotator servlet is included; the program just randomly selects a banner advertisement and outputs it as an HTML anchor around an IMG tag. Then I get the HttpSession object and, from that, the current User object, which is null if there is not a currently logged-in user. The User class was discussed when we talked about JavaBeans in JSPs (see Simplifying Your JSP with a JavaBean); it's used as an ordinary object in most of these JSPs, but as a bean in the newuser.jsp page, when the user has entered all the fields on the "Create an Account" page.

Then there's an HTML table, which basically divides the rest of the page into two large columns. The left side of the page is fairly wide and contains the news stories, their headlines, the submitter's name, the time, optionally a URL, and the text of the news article. A future version will allow the user to send comments on the stories; as Slashdot has demonstrated, this is an important part of "community building," part of the art of keeping people coming back to your web site so you can show them more banner ads. :-)

The navigator part is displayed differently depending on whether you are logged in or not. If you're not, it begins with a login form, then lists the few services that are publicly available as HTML anchors, with the unavailable services in italic text. If you are logged in, there is a full list of links and a logout page at the end.

Before you log in, you must create an account. The trick here is that we require the user to give a valid email address, which we'll use for various authentication purposes and, just possibly, to send them a monthly newsletter by email. To ensure that the user gives a valid email address, we email to them the URL from which they must download the password. Figure 18-12 shows the entry page for this. This form is processed by newuser.jsp.

Screen shot.
Figure 18-12. newuser.jsp in action.

Example 18-16 is the source for newuser.jsp. As mentioned previously, this gets a User object as a JavaBean (see Simplifying Your JSP with a JavaBean).


Example 18-16: newuser.jsp

<%@page errorPage="oops.jsp" import="jabadot.*, java.io.*" %>
<%! java.util.Random r = new java.util.Random(  ); %>
<jsp:useBean id="newUserBean" scope="request" class="jabadot.User">
  <jsp:setProperty name="newUserBean" property="*"/>
</jsp:useBean>
<jsp:useBean id="mailBean" scope="request" class="jabadot.Mailer"/>
<html>
<%@include file="header.html" %>
<%
  User user = (User)session.getAttribute("jabadot.login");
  if (user != null) {
%>
<TITLE>You're already logged on!</TITLE>
<H1>You are logged on!</h2>
<P>Please <A href="logout.jsp">log out</A> before
trying to create a new account.  Thank you!
  }
%>
<% // Now see if they already filled in the form or not...
  if (!newUserBean.isComplete(  )) {
    // out.println("<!-- in new -->");
%>
    <TITLE>Welcome New User - Please fill in this form.</TITLE>
    <BODY BGCOLOR=White>
    <H1>Welcome New User - Please fill in this form.</h2>
    <TABLE>
    <TD>
    <FORM ACTION="newuser.jsp" METHOD=post>
      <table><!-- inner table so fields line up -->
      <tr><td>Nickname:</td>
        <td><INPUT TYPE=TEXT SIZE=10 NAME="name"> (required)</td></tr>
      <tr><td>Full name:</td>
        <td><INPUT TYPE=TEXT SIZE=10 NAME="fullName"> (required)</td></tr>
      <tr><td>E-mail:</td>
        <td><INPUT TYPE=TEXT SIZE=10 NAME="email"> (required)</td></tr>
      <tr><td>City:</td>
        <td><INPUT TYPE=TEXT SIZE=10 NAME="city"></td></tr>
      <tr><td>Province/State:</td>
        <td><INPUT TYPE=TEXT SIZE=10 NAME="prov"></td></tr>
      <tr><td>Country</td>
        <td><select name="location">
        <jsp:include page="country_select.html" flush="true"/>
        </select>
        </td></tr>
      <tr><td colspan=2 align="center">
        <INPUT TYPE=SUBMIT VALUE="Create My JabaDot!"></tr>
      </table>
    </FORM>
    <TD>
    <P>If you've done one of these before, you may be wondering where
    the "Password" field is. It's not there. Believing somewhat in
    security, we'll make up a fairly good password for you.
    We won't email it to you, but will email to you the location
    from which you can learn it, so watch your email closely
    after you complete the form. Thank you!
    </TABLE>
<%    return;
    }
 
    // out.println("<!-- in get -->");
    String nick = newUserBean.getName(  );
    if (UserDB.getInstance(  ).getUser(nick) != null) {
%>
      <P>It seems that that user name is already in use!
      Please go back and pick another name.
      <% return;
      } %>
<%
    String fullname = newUserBean.getFullName(  );
    String email = newUserBean.getEmail(  );
%>
    <!-- Give the user a welcome -->
    Welcome <%= fullname %>.
    We will mail you (at <%= email %>) with a URL
    from which you can download your initial password.
 
    <jsp:setProperty name="newUserBean"
      property="editPrivileged" value="false"/>
    <jsp:setProperty name="newUserBean"
      property="adminPrivileged" value="false"/>
<%
    // Generate initial random password and store it in the User
    String newPass = Password.getNext().toString(  );
    newUserBean.setPassword(newPass);
 
    // NOW add the user to the persistent database.
    UserDB.getInstance(  ).addUser(newUserBean);
 
    // Create a temporary HTML file containing the full name
    // and the new password, and mail the URL for it to the user.
    // This will confirm that the user gave us a working email.
    // NEVER show the nickname and the password together!
    String tempDir = JDConstants.getProperty("jabadot.tmp_links_dir");
    File tempLink = File.createTempFile(
      r.nextInt(  )+"$PW", ".html", new File(tempDir));
    PrintWriter pw = new PrintWriter(new FileWriter(tempLink));
    pw.print("<HTML><BODY>");
    pw.print("Greetings ");
    pw.print(newUserBean.getFullName(  ));
    pw.print(". Your new password for accessing JabaDot is <B>");
    pw.print(newPass);
    pw.print("</B>. Please remember this, or better yet, ");
    pw.print("<a href=\"/jabadot/index.jsp\">");
    pw.print("login</a> now!");
    pw.print("You may want to visit \"My Jabadot\"");
    pw.print("and change this password after you log in.");
    pw.println("</HTML>");
    pw.close(  );
 
    // Now we have to mail the URL to the user.
    mailBean.setFrom(JDConstants.getProperty("jabadot.mail_from"));
    mailBean.setSubject("Welcome to JabaDot!");
    mailBean.addTo(email);
    mailBean.setServer(JDConstants.getProperty("jabadot.mail.server.smtp"));
 
    // Get our URL, strip off "newuser.jsp", append "/tmp/"+tmpname
    StringBuffer getPW_URL = HttpUtils.getRequestURL(request);
    int end = getPW_URL.length(  );
    int start = end - "newuser.jsp".length(  );
    getPW_URL.delete(start,end).append("tmp/").append(tempLink.getName(  ));
    mailBean.setBody("To receive your JabaDot password,\n" +
      "please visit the URL " + getPW_URL);
 
    // Now send the mail.
    mailBean.doSend(  );
 
    // AVOID the temptation to sess.setAttribute(  ) here, since
    // the user has not yet verified their password!
%>

Once you create an account and read the email containing the link for the password, you can return to the site and log in normally. The login form is handled by login.jsp, shown in Example 18-17.


Example 18-17: login.jsp

<%@page errorPage="oops.jsp" import="jabadot.*" %>
<HTML>
<%
  User user = (User)session.getAttribute("jabadot.login");
  if (user != null) {
    session.setAttribute("jabadot.message",
      "<H1>You're already logged on!</h2>"+
      "(as user " + user.getName(  ) + "). Please" +
      "<a href=\"logout.jsp\">" +
      "logout</a> if you wish to log in as a different user.");
    response.sendRedirect("/jabadot/");
  }
  String nick = request.getParameter("nick");
  String pass = request.getParameter("pass");
  if (nick == null || nick.length(  ) == 0 ||
    pass == null || pass.length(  ) == 0) {
%>
    <!-- Must use jsp include not @ include here since
     ** tomcat complains if it sees the @ include twice.
     ** Can't just include it once at beginning, since we
     ** do a redirect at the end of this jsp if successful.
     -->
    <jsp:include page="./header.html" flush="true" />
    <TITLE>Missing name/password!</TITLE>
    <BODY BGCOLOR=WHITE>
    <H1>Missing name/password!</h2>
    <P>Please enter both a name and a password in the form.
<%    return;
  }
  
  User u = UserDB.getInstance(  ).getUser(nick);
  if (u == null || !u.checkPassword(pass)) {  
%>
    <jsp:include page="./header.html" flush="true" />
    <TITLE>Invalid name/password</TITLE>
    <BODY BGCOLOR=WHITE>
    <H1>Invalid name/password</h2>
    <P>We could not find that name and password combination.
    Please try again if you have an account, else go create one.
<%    return;
  }
 
  // Hallelujeah! WE FINALLY GOT THIS ONE LOGGED IN.
 
  session.setAttribute("jabadot.login", u); // login flag
  //session.setAttribute("jabadot.ads", new AdServlet(  ));
  session.setAttribute("jabadot.message",
    "<H1>Welcome back, " + u.getFullName(  ) + "</h2>");
 
  // For non-admin logins, provide a 3-hour timeout
  if (!u.isAdminPrivileged(  )) {
    session.setMaxInactiveInterval(3600*3);
  }
 
  // Send Redirect back to top, so user sees just this in URL textfield.
  response.sendRedirect("/jabadot/");
%>

After ensuring that you're not already logged in, this page gets the username and password from the HTML form, checks that both are present, looks up the name in the password database and, if found, validates the password. If either the name or the password is wrong, I report a generic error (this is deliberate security policy to avoid giving malicious users any more information than they already have (This ancient advice comes from the early days of Unix; you'd be surprised how many sites still don't get it). If you log in, I put the User object representing you into the HttpSession, set a little greeting, and pass control to the main page via a redirect.

Whether logged in or not, you can send a general comment to the system's administrators via the submit.jsp page. This simply generates the HTML form shown in Figure 18-13.

Screen shot.
Figure 18-13. Input form for comments.jsp.

This form is processed by comments.jsp, shown in Example 18-18, when you press the "Submit Article" button.


Example 18-18: comments.jsp

<%@page errorPage="oops.jsp" %>
<%@page import="jabadot.*, javax.mail.*" %>
<jsp:useBean id="mailBean" scope="request" class="jabadot.Mailer">
  <jsp:setProperty name="mailBean" property="*"/>
</jsp:useBean>
<%
  User user = (User)session.getAttribute("jabadot.login");
 
  mailBean.setFrom(JDConstants.getProperty("jabadot.mail_from"));
  mailBean.setSubject("Comment from jabadot site");
  mailBean.addTo(JDConstants.getProperty("jabadot.mail_comments_to"));
  mailBean.setServer(JDConstants.getProperty("jabadot.mail.server.smtp"));
 
  String message = request.getParameter("message");
  if (message != null)
    mailBean.setBody(message);
 
  // See if they already filled in the form or not...
  if (mailBean.isComplete(  )) {
    try {
      mailBean.doSend(  );
 
      // Now attach a thank you note and send them to the index page
      session.setAttribute("jabadot.message",
        "<H1>Mail sent</h2><p>Your commentary has been sent to our chief" +
        " pickle.<b>Thank you.</b></p>");
      response.sendRedirect("/jabadot/");
      // No return from sendRedirect
    } catch (MessagingException ex) {
      throw new IllegalArgumentException(ex.toString(  ));
    }
  }
  // ELSE - mailbean is NOT complete, put up form.
%>
  <%@include file="header.html" %>
  <P ALIGN=CENTER><jsp:include page="/servlet/AdServlet" flush="true"/></P>
  <TITLE>Send Comments</TITLE>
  <BODY BGCOLOR="white">
 
  <DIV ALIGN="CENTER">
  <BLOCKQUOTE>
 
  <FORM METHOD="POST" ACTION="comments.jsp">
 
  <P>Please send us your feedback on this site.
<%  if (user != null) { %>
    <P>Since you are logged in, you can use
    <A href="mailto:<%=JDConstants.getProperty(
      // This subject= without quotes WORKS but doesn't feel good :-)
      "jabadot.mail_comments_to")%>?subject=Comments about JabaDot">
    this <I>mailto</I> link</A>.</P>
<%  } %>
  <P>Name: <INPUT TYPE="TEXT" NAME="name" SIZE="15"
  VALUE="<%= user==null?"":user.getFullName(  )%>">&nbsp;
    Email:<INPUT TYPE="TEXT" NAME="from" SIZE="15"
    VALUE="<%= user==null?"":user.getEmail(  )%>"></P>
  <P>City:&nbsp;&nbsp;&nbsp;&nbsp; <INPUT TYPE="TEXT" NAME="City" SIZE="15"
  VALUE="<%= user==null?"":user.getCity(  ) %>">&nbsp;
    State: <INPUT TYPE="text" NAME="State" SIZE="15"
    VALUE="<%= user==null?"":user.getProv(  ) %>"></P>
  <P>Country:&nbsp;<INPUT TYPE="text" NAME="country" size="15"
  VALUE="<%= user==null?"": user.getCountry(  ) %>"></P>
  <TEXTAREA NAME="message" ROWS="8" COLS="50" WRAP="physical">
<%= message %>
  </TEXTAREA>
  <P><INPUT TYPE="submit" VALUE="Send Comments"></P>
  </FORM>
 
  </BLOCKQUOTE>
  </DIV>
  </BODY>

This page starts off like the first one. I particularly like the code that displays a mailto: URL only if the user is logged in. SPAM perpetrators (see Chapter 19) are notorious for automatically loading entire web sites just to look for mailto: URLs. This is a good way to fence these rodents out, since they normally won't go to the trouble of signing up for a service and providing a real (working) email address just to get one mailto: URL from your site. There are easier ways to find mailto:'s on other sites; hopefully the SPAM perps will go there. For extra fun, make up a unique email address for each user to send mail to, so if you do get spammed, you have an idea who might have done it.

See Also

There is more to servlets and JSPs than I've got room to tell you about. These technologies offer an interesting partitioning of code and functionality. The JSP can be concerned primarily with getting the input and displaying the results. A JSP can forward to a servlet, or can include or jump to any other local web resource, like an audio file. Servlets and JSP are primary parts of the Java Enterprise Edition, and are becoming very important web server technologies.

For an opposing view (that JSPs are the wrong solution to the wrong problem), surf on over to http://www.servlets.com/. For more information on servlets and JSPs, refer to the O'Reilly books Java Servlet Programming and JavaServer Pages.


Return to ONJava.com.