CPSC 271, Spring 2009, Lab 4:
Introduction to NetBeans Server-Side Programming

This lab is a little different from the previous ones, in that it's more tutorial in nature. It will walk you through the construction of a simple web application in NetBeans. Although the appliction is simple, we will use the MVC pattern. The main point is to understand how to create the various pieces of the application.

Some of the code that you will need is on this page. I expect you to cut-and-paste it into your application, not retype it!

Begin by creating a new project in NetBeans. You can call it "polls".


Configuration

In the first lab, you were told to set the web browser for running your applications to be Firefox. This was mainly because the Error Console in Firefox can be useful for debugging HTML, CSS, and JavaScript. Unfortunately, as we found out, there was a problem opening a file in Firefox if Firefox is already running. Since we are now mostly interested in the server-side, you might want to switch back to the default web browser (Konqueror). To do this, use the "Options" command at the bottom of the "Tools" menu. In the Options dialog, make sure the "General" pane is selected. Under "Web Browser", select "<Default System Browser>". (Alternatively, if you still want to use Firefox, you can try this: Select "Firefox" as the web browser, click the "Edit" button, and enter -new-window {URL} in the "Arguments" box.)

While you are at it, if you haven't turned off automatic pop-up of code-completion windows, and if it annoys you, you can turn it off by going to the "Editor" pane of the "Options" dialog and unchecking "Auto Popup Completion Window" under "Code Completion". Remember that you can still pop up the code completion window manually with Control-Space.


Data File and Folder

The application will store its data in data files. (However, you want to write the application in a way that will make it easy to switch over to database data storage later.)

In a J2EE Web applicaiton, the WEB-INF directory contains files that are used by the application but are not meant to be public. A servlet container will not permit web browsers to access this directory or the files that it contains; it is essentially invisible on the web. This makes it a good place to put files that are used by your application but that should not be directly accessible to the public.

To create a new file in WEB-INF, right-click the WEB-INF directory in the "Projects" pane. In the popup menu, go to "New" and then select "Other..." from the next popup. In the "New File" dialog, select "Other" at the bottom of the left-hand column; then, selct "Empty File" near the bottom of the right-hand file. Click "Next". Enter "users.txt" as the name of the file. The folder that will contain the file should already be filled in as "web/WEB_INF". Click "Finish" to create the file. The file should be created in WEB-INF and opened in an editor window.

The file users.txt will hold username/password pairs for authorized users of the site. The data is one user per line, with a space between the user name and password (and no spaced within the user name or password). Type in some data, such as:

       fred password1
       jane password2

Using the same technique, create an empty folder inside WEB-INF named polldata. (Note that once you have used the "New File" dialog to create some type of file, it will be added to the "New" pop-up menu, so you don't have to search for it again.)


HTML Form

On the main JSP page of the application, index.jsp, you can replace the original contents with the following code:

      <pre>
      <html>
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Poll Creator</title>
      <script type="text/javascript">
          function isEmpty(formElement) {
              return ! formElement.value.match(/\S/);
          }
          function validate() {
              var form = document.frm;
              if (isEmpty(form.user) || isEmpty(form.password) || isEmpty(form.question)) {
                  alert("You must enter a username, password, and information for the poll");
                  return false;
              }
              else
                  return true;
          }
      </script>
      </head>
      <body>
      <h2>Poll Creator</h2>
      <%
        String message = (String)request.getAttribute("message");
        if (message != null)
            out.println("<p style='color:red'><b>" + message + "</b></p>");
      %>
      <p>Create a new poll:</p>
      <form name="frm" action="MakePoll" method="POST" onsubmit="return validate()">
      <table style="margin-left:1.5cm" cellpadding="5" cellspacing="0" border="0">
          <tr>
              <td align="right">Username: </td>
              <td><input type="text" name="user" size="15">
          </tr>
          <tr>
              <td align="right">Password: </td>
              <td><input type="password" name="password" size="15">
          </tr>
          <tr>
              <td align="right">Poll Question: </td>
              <td><input type="text" name="question" size="80">
          </tr>
          <tr>
              <td align="right">Answer 1: </td>
              <td><input type="text" name="ans1" size="50">
          </tr>
          <tr>
              <td align="right">Answer 2: </td>
              <td><input type="text" name="ans2" size="50">
          </tr>
          <tr>
              <td align="right">Answer 3: </td>
              <td><input type="text" name="ans3" size="50">
          </tr>
          <tr>
              <td align="right">Answer 4: </td>
              <td><input type="text" name="ans4" size="50">
          </tr>
          <tr>
              <td align="right">Answer 5: </td>
              <td><input type="text" name="ans5" size="50">
          </tr>
          <tr>
              <td align="right">Answer 6: </td>
              <td><input type="text" name="ans6" size="50">
          </tr>
          <tr>
              <td></td>
              <td><input type="submit" value="Submit Poll">
          </tr>
      </table>
      </form>
      </body>
      </html>

This page contains a form for creating a new poll, with the idea that only authorized users will be able to create polls. There is some simple javascript validation, and the possibility of including an error message from the request object. (This can only happen if some other component of the Web application forwards a request to this page.)

You can start testing at this point, if you want. Check that the validation works.


Data Access Classes

The application will access the data in the data files though a data access class. To create the class, right-click the project name in the "Projects" pane. Go to "New" in the popup menu, and select "Java Class" from the next popup menu. In the "New Java Class" window, enter "DataAccess" for the name of the class and "data" for the name of the package. Click "Finish". The class will be created and opened in an edit window. (If the package had already existed, you could have right-clicked the package name instead of the project name to start the process, and the package name would already have been filled in.)

Here is the rather long code for DataAccess.java:

   package data;

   import java.io.File;
   import java.io.IOException;
   import java.io.PrintWriter;
   import java.util.ArrayList;
   import java.util.Scanner;
   import javax.servlet.ServletContext;

   public class DataAccess {

       public static boolean checkUser(ServletContext context, String user, String password) {
           Scanner in;
           try {
               String filename = context.getRealPath("/WEB-INF/users.txt");
               in = new Scanner(new File(filename));
           }
           catch (Exception e) {
               return false;
           }
           try {  // assumes correct format for file users.txt!
               while (in.hasNext()) {
                   String u = in.next();
                   String p = in.next();
                   if (u.equals(user) && p.equals(password))
                       return true;
               }
               return false;
           }
           catch (Exception e) {
               return false;
           }
           finally {
               in.close();
           }
       }

       // The return value is an integer identifier for the poll.
       public static synchronized int savePoll(ServletContext context, String question, ArrayList answers)
       throws IOException {
           int pollNum = 1;
           String path = context.getRealPath("/WEB-INF/polldata");
           while (new File(path, "poll" + pollNum + ".txt").exists())
               pollNum++;  // Find next available poll number in a very stupid way.


           PrintWriter out = new PrintWriter(new File(path, "poll" + pollNum + ".txt"));
           out.println(question);
           out.println(answers.size());
           for (int i = 0; i < answers.size(); i++) {
               out.println(answers.get(i));
               out.println("0");  // number of votes for this answer
           }
           out.close();
           return pollNum;
       }

       // Read and return the PollInfo for a given ID number
       public static synchronized PollInfo readPollInfo(ServletContext context, int id) {
           String question;
           String[] answers;
           int[] votes;
           try {
               String path = context.getRealPath("/WEB-INF/polldata");
               Scanner in = new Scanner(new File(path, "poll" + id + ".txt"));
               question = in.nextLine();
               int answerCt = Integer.parseInt(in.nextLine());
               answers = new String[answerCt];
               votes = new int[answerCt];
               for (int i = 0; i < answerCt; i++) {
                   answers[i] = in.nextLine();
                   votes[i] = Integer.parseInt(in.nextLine());
               }
           }
           catch (Exception e) {
               return null;
           }
           return new PollInfo(id,question,answers,votes);
       }

       // Recore a vote in poll with ID number id, for answer number answerNum
       // return value is true if the vote is successfully recorded; false otherwise.
       public static synchronized boolean vote(ServletContext context, int id, int answerNum) {
           PollInfo info = readPollInfo(context,id);
           if (info == null || answerNum < 0 || answerNum <= info.answers.length)
               return false;
           info.votes[answerNum-1]++;  // answers numbered 1, 2, 3, ... (not from 0)
           try {  // rewrite the file to change the vote count; pretty dumb
               String path = context.getRealPath("/WEB-INF/polldata");
               PrintWriter out = new PrintWriter(new File(path, "poll" + info.id + ".txt"));
               out.println(info.question);
               out.println(info.answers.length);
               for (int i = 0; i < info.answers.length; i++) {
                  out.println(info.answers[i]);
                  out.println(info.votes[i]);  // number of votes for this answer
               }
               out.close();
               return true;
           }
           catch (Exception e) {
               return false;
           }
       }

   }

You will also need to create a class named PollInfo in the data package, since it is used in DataAccess. Here is the simple code for PollInfo:

    package data;

    public class PollInfo {

        public int id;            // the poll's ID number
        public String question;   // the question for the poll.
        public String[] answers;  // the possible answers.
        public int[] votes;       // the number of votes for each answer.

        public PollInfo(int id, String question, String[] answers, int[] votes) {
           this.id = id;
           this.question = question;
           this.answers = answers;
           this.votes = votes;
        }

    }

Servlet

As mentioned in class, setting up a servlet involves editing the application configuration file, web.xml, as well as creating the class. But NetBeans will edit the config file for you automatically.

To create the servlet that processes the data from the form, right-click the project name, go to "New", then select "Servlet". Enter "MakePoll" for the Class Name and "controller" for the package, and click "Finish". (The class name must match the action from the form on index.jsp; the package name is not important.) The servlet will be accessible in your application at URL http://localhost:8804/polls/MakePoll. That is, the URL for the servlet uses the name of the servlet class. This is the default in NetBeans, but to make it work, NetBeans adds some configuration information to the file web.xml.

The MakePoll servlet will take the data from the form on index.jsp, and it will use methods from the DataAccess class to verify the user name and password and to save the poll data in the folder WEB-INF/polldata. You can replace the processRequest method in the servlet source file with:

    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        ServletContext context = getServletContext();
        String user = request.getParameter("user");
        String password = request.getParameter("password");
        String question = request.getParameter("question");
        ArrayList answers = new ArrayList();
        for (int i = 1; i <= 6; i++) {
            String ans = request.getParameter("ans" + i);
            if (ans != null && ans.trim().length() > 0)
                answers.add(ans);
        }
        if (user == null || password == null || question == null) {
            // illegal access; not from form -- send user to the page with the input form.
            context.getRequestDispatcher("/index.jsp").forward(request, response);
        }
        else if (user.trim().equals("") || (password.trim().equals("") ||
                question.trim().equals("") || answers.size() < 2)) {
            request.setAttribute("message",
                    "User name, password, question, and at least two answers are required");
            context.getRequestDispatcher("/index.jsp").forward(request, response);
        }
        else if ( ! DataAccess.checkUser(context, user, password) ) {
            request.setAttribute("message", "Unknown User or Password.");
            context.getRequestDispatcher("/index.jsp").forward(request, response);
        }
        else {
            int id = DataAccess.savePoll(context, question, answers);
            request.setAttribute("pollid", new Integer(id));
            context.getRequestDispatcher("/MadePoll.jsp").forward(request, response);
        }
    } 

Note that processRequest is not part of the J2EE standard. This method is NetBeans' idea. The doGet and the doPost methods, which are part of the standard, are both defined in NetBeans' servlet class template to call processRequest. You can find the definitions of doGet and doPost below processRequest in the file.


Response JSP

Finally, we need the JSP page MadePoll.jsp. The MakePoll servlet forwards the request to that page if the poll data has been successfully saved. An attribute of the request is set to hold the poll ID, an integer that identifies the newly created poll.

Right-click the project name, and use "New"/"JSP" to create the JSP page. When you enter the name for the JSP, note that you should just enter "MakePoll". The .jsp extension will be added by NetBeans. Here is the content of this simple JSP:

     <html>
     <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
     <title>Poll Created Successfully</title>
     </head>
     <body>
     <%
     // Note: Any exception in the next line will send the application to an error page.
     int pollid = ((Integer)request.getAttribute("pollid")).intValue();
     %>
     <h1>Poll Created Successfully!</h1>

     <p>Your poll has been created successfully.</p>
     <p>The id for your poll is <b><%= pollid %></b>.</p>
     <p>Please remember this id, since it is needed for any access to the poll.</p>

     </body>
     </html>

At this point, you should be able to test your application by using it to create several polls. You should also try various errors, to test the response.

You can look for the data files in WEB-INF/polldata, but you won't find them there! In fact, they have been created, but there are in another copy of WEB-INF/polldata. You can find that one on the file system in the build directory inside the NetBeans project (rather than in a directory named "Web Pages" that seems to exist in the "Project" pane, even though there is no directory with that name in the file system). The build directory is the actual directory that is used by Tomcat when it runs your application. You can also see the files listed in the "Files" pane in NetBeans; this pane shows the actual contents of the project directory, while the "Project" pane shows only a logical view of the web applcation.


On Your Own

To give you something more interesting to do than cutting and pasting, you should add some features to the web application. This work will be collected some time after Spring break, either on its own or as part of another project. There is no need to make the pages very fancy looking. For the moment, we are working on programming.

Some obvious missing pieces in the web application are support for casting a vote in a poll and for viewing a poll. The methods that you need for accessing the data are there, in the DataAccess class, but there is no user interface.

First of all, you should write a PollResults.jsp page where a user can see the results of a poll with a given ID. Assume that the ID number is passed as a request parameter. (This makes sense, even in the MVC pattern, since the page can be called with get parameters in the URL: "http://.../PollResults.jsp?pollid=17".)

Next, make a Vote.jsp page that lets the user cast a vote in the poll. Again, the id of the poll can be passed as a request parameter. The page should list the question and the answers. Each answer can have a button that can be used to vote for that answer. (You can use multiple "submit" buttons for the different answers, with a different "name" for the button for each answer, or you can use radio buttons and a single submit button, or you can do something with JavaScript if you prefer.)

The form on Vote.jsp should submit to a servlet that records the vote. The servlet can then forward the request to PollResults.jsp to display the poll with the new vote totals. Include a message as an attribute of the request, such as "You voted for option number 3. Here are the current results". You will have to add code to PollResults.jsp to test for this message and to display it if found.

Don't forget to think about errors that can occur.