Meeting Survex

   

Meeting Survex

In the rest of this chapter, you'll build the Survex . Survex is a generic survey application. It is configured for a specific survey through an XML file, such as Listing 2.1.

Listing 2.1 survey.xml
 <?xml version="1.0"?> <survey>    <question>       <name>email</name>       <title>Welcome to our XML book survey</title>       <label>Thank you for participating in our survey.</label>       <input>Enter your email address</input>       <next><text>usingxml</text></next>    </question>    <question>       <name>usingxml</name>       <title>XML and You</title>       <label>Do you use XML?</label>       <choice>          <option>             <label>I do</label>             <value>yes</value>          </option>          <option>             <label>No but I plan to use it</label>             <value>planning</value>          </option>          <option>             <label>No and I don't plan to use it</label>             <value>no</value>          </option>       </choice>       <next>          <if>             <eq>                <answer>usingxml</answer>                <text>no</text>             </eq>             <block>                <save><answer>email</answer></save>                <text>done</text>             </block>             <text>booktraining</text>          </if>       </next>    </question>    <question>       <name>booktraining</name>       <title>XML Books</title>       <label>Do you need more XML books?</label>       <choice>          <option>             <label>Yes, I would like more XML books</label>             <value>yes</value>          </option>          <option>             <label>No, I have all the books I need</label>             <value>no</value>          </option>       </choice>       <next>          <if>             <eq>                <answer>booktraining</answer>                <text>yes</text>             </eq>             <text>timeframe</text>             <block>                <save><answer>email</answer></save>                <text>done</text>             </block>          </if>       </next>    </question>    <question>       <name>timeframe</name>       <title>Timeframe</title>       <label>When do you plan to buy new XML books?</label>       <choice>          <option>             <label>Now</label>             <value>now</value>          </option>          <option>             <label>Within 3 months</label>             <value>months</value>          </option>          <option>             <label>Within a year</label>             <value>year</value>          </option>          <option>             <label>I don't know yet</label>             <value>unknown</value>          </option>       </choice>       <next>          <block>             <save><answer>email</answer></save>             <text>done</text>          </block>       </next>    </question>    <question>       <name>done</name>       <title>Thank you</title>       <label>Thank you for your time!</label>       <next><text>done</text></next>    </question> </survey> 

The survey is a list of question in which each question has the following:

  • A name, which uniquely identifies a section

  • A title, which is presented to the visitor

  • A label or the text of the question itself

  • Either a list of options from which the visitor can choose an answer (for closed questions) or an input field (for open questions)

  • A small script to decide on the next question

The first question in the listing will be rendered as in Figure 2.1. Note that the script could not be simpler; it unconditionally moves to the next question:

 <question>      <name>email</name>      <title>Welcome to our XML book survey</title>      <label>Thank you for participating in our survey.</label>   <input>Enter your email address</input>      <next><text>usingxml</text></next>   </question> 
Figure 2.1. The survey first asks for your email address.

graphics/02fig01.gif

Designing Survex

The model behind Survex is shown in Figure 2.2. The main classes are

  • Survex ”The servlet that runs it all.

  • Survey ”The list of questions in the current survey.

  • Question ”Stores information on one question.

  • Option ”Stores information on an option in a list of options.

  • Statement ”It and its descendants are used for scripting.

  • SurveyReader ”Parses the XML file and builds the corresponding Survey object .

Figure 2.2. The script is modeled as Statement descendants.

graphics/02fig02.gif

The Data Structure

At the heart of the data structure is the Question class (see Listing 2.2). The Question defines a number of properties: the name , title , and label , as well as the input or the list of options . Note that the code enforces an exclusive on the input and the list of options . Finally, the script is used.

Listing 2.2 Question.java
 package com.psol.survex; import java.io.*; import java.util.*; public class Question {    protected String name,                     title,                     label,                     input;    protected Option[] options;    protected Statement script;    public String getName()    {       return name;    }    public void setName(String name)    {       this.name = name;    }    public String getTitle()    {       return title;    }    public void setTitle(String title)    {       this.title = title;    }    public String getLabel()    {       return label;    }    public void setLabel(String label)    {       this.label = label;    }    public Option[] getOptions()    {       return options;    }    public void setOptions(Option[] options)    {       this.options = options;       input = null;    }    public String getInput()    {       return input;    }    public void setInput(String input)    {       this.input = input;       options = null;    }    public void setScript(Statement script)    {       this.script = script;    }    public Statement getScript()    {       return script;    } } 

Question uses the Option class , in Listing 2.3, to store the properties for the various options. Each option has a label and a value .

Listing 2.3 Option.java
 package com.psol.survex; import java.io.*; public class Option {    protected String label,                     value;    public void setLabel(String label)    {       this.label = label;    }    public String getLabel()    {       return label;    }    public void setValue(String value)    {       this.value = value;    }    public String getValue()    {       return value;    } } 

At the root of the data structure is the Survey class (see Listing 2.4). It maintains the list of questions in a dictionary for fast retrieval.

Listing 2.4 Survey.java
 package com.psol.survex; import java.io.*; import java.util.*; public class Survey {    protected String rootName;    protected Dictionary questions = new Hashtable();    public Enumeration getKeys()    {       return questions.keys();    }    public void addQuestion(Question question)    {       if(questions.isEmpty())          rootName = question.getName();       questions.put(question.getName(),question);    }    public Question getQuestion(String name)    {       return (Question)questions.get(name);    }    public Question getRootQuestion()    {       return (Question)questions.get(rootName);    } } 

Building a Script Interpreter

A script is an object that implements the Statement interface (see Listing 2.5). The interface is trivial, defining only one method, apply() , which executes the statement and returns a string. For simplicity, the string is the only data type. Also, no local variables exist, only global parameters.

Listing 2.5 Statement.java
 package com.psol.survex; import java.util.Dictionary; import javax.servlet.ServletException; public interface Statement {    public String apply(Dictionary parameters)       throws ServletException; } 

Looking back at Listing 2.1, you can identify the following statements:

  • <text> ”Used for a text constant

  • <answer> ”Retrieves an answer chosen by the visitor

  • <eq> ”Tests for equality

  • <if> ”Is the classical if/then/else construct

  • <save> ”Saves the results to a file

  • <block> ”Combines several statements

The <text> statement is implemented in the class named Constant (see Listing 2.6). Constant has one property, text , and its apply() method returns the value of the text property.

Listing 2.6 Constant.java
 package com.psol.survex; import java.util.Dictionary; import javax.servlet.ServletException; public class Constant    implements Statement {    protected String text;    public void setText(String text)    {       this.text = text;    }    public String apply(Dictionary parameters)       throws ServletException    {       return text;    } } 

The <answer> XML element is implemented in the Parameter class in Listing 2.7. This class has one property, name , and its apply() method returns the parameter whose name matches the name property. As you will see, the servlet loads the parameters with the visitor's choices.

Listing 2.7 Parameter.java
 package com.psol.survex; import java.util.Dictionary; import javax.servlet.ServletException; public class Parameter    implements Statement {    protected String name;    public void setName(String name)    {       this.name = name;    }    public String apply(Dictionary parameters)       throws ServletException    {       String st = (String)parameters.get(name);       return null != st ? st : "";    } } 

Equal (in Listing 2.8) supports the <eq> statement . Equal has two properties, arg1 and arg2 , both of which are Statement s themselves . Equal executes the two Statement s (by calling their apply() method) and compares the results.

Note

Equal illustrates how Statement s are combined. The scripting language has a distinct functional style: Each Statement is a function (it takes one or more parameters and returns a value). Also, no global variables exist.

A functional style is simpler to understand and, remember, you are looking for a simple-to-use scripting language. For more sophistication, you would have turned to an existing scripting language.


Listing 2.8 Equal.java
 package com.psol.survex; import java.util.Dictionary; import javax.servlet.ServletException; public class Equal    implements Statement {    protected Statement arg1, arg2;    public void setArgs(Statement arg1,Statement arg2)    {       this.arg1 = arg1;       this.arg2 = arg2;    }    public String apply(Dictionary parameters)       throws ServletException    {       String value1 = arg1.apply(parameters),              value2 = arg2.apply(parameters);       return value1.equals(value2) ? "true" : "false";    } } 

If has three properties: cond , then , and _else . It executes the first statement: cond , the condition. Depending on the result, it next executes the then or _else statement (see Listing 2.9).

Listing 2.9 If.java
 package com.psol.survex; import java.util.Dictionary; import javax.servlet.ServletException; public class If    implements Statement {    protected Statement cond,                        then,                        _else;    public void setArgs(Statement cond,                        Statement then,                        Statement _else)    {       this.cond = cond;       this.then = then;       this._else = _else;    }    public String apply(Dictionary parameters)       throws ServletException    {       if(cond.apply(parameters).equals("true"))          return then.apply(parameters);       else          return _else.apply(parameters);    } } 

Save is a special function because it has a side effect: It creates a file and writes the parameters. The script uses this before terminating the survey (see Listing 2.10).

Tip

Listing 2.10 saves the survey results under the visitor's email address. Email addresses are a simple mechanism to identify visitors . Obviously, some people have several email addresses, whereas some families share email addresses, but it's accurate enough for our needs.

An added bonus is that when a visitor changes his mind and answers differently, the new answer overrides the older one.


Listing 2.10 Save.java
 package com.psol.survex; import java.io.*; import java.util.*; import javax.servlet.ServletException; public class Save    implements Statement {    protected Statement filename;    public void setFilename(Statement filename)    {       this.filename = filename;    }    public void escape(Writer w,String s)       throws IOException    {       for(int i = 0;i < s.length();i++)       {          char c = s.charAt(i);          if(c == '<')             w.write("&lt;");          else if(c == '&')             w.write("&amp;");          else if(c == '\ '')             w.write("&apos;");          else if(c == '"')             w.write("&quot;");          else if(c > '\ u007f')          {             w.write("&#");             w.write(Integer.toString(c));             w.write(';');          }          else             w.write(c);       }    }    public String apply(Dictionary parameters)       throws ServletException    {       try       {          String fname = filename.apply(parameters);          File file = new File("results",fname + ".xml");          Writer writer = new FileWriter(file);          writer.write("<?xml version='1.0'?><survex>");          Enumeration keys = parameters.keys();          while(keys.hasMoreElements())          {             String name = (String)keys.nextElement();             writer.write("<question><name>");             escape(writer,name);             writer.write("</name><answer>");             escape(writer,(String)parameters.get(name));             writer.write("</answer></question>");          }          writer.write("</survex>");          writer.close();          return fname;       }       catch(IOException e)       {          throw new ServletException(e);       }    } } 

Block offers a solution to combine several Statement s. It executes each Statement and returns the result of the last one. In effect, this is similar to the { } construct in Java (see Listing 2.11).

Listing 2.11 Block.java
 package com.psol.survex; import java.util.*; import javax.servlet.ServletException; public class Block    implements Statement {    protected Statement[] statements;    public void setStatements(Statement[] statements)    {       this.statements = statements;    }    public String apply(Dictionary parameters)       throws ServletException    {       String result = "";       // on the stack they are collected in reverse order       for(int i = statements.length - 1;i >= 0;i--)          result = statements[i].apply(parameters);       return result;    } } 

Reading the Configuration File

The extensive data structure must be read from the XML configuration file. This is the role of SurveyReader , a class that implements the SAX's ContentHandler interface . SurveyReader is demonstrated in Listing 2.12.

Listing 2.12 SurveyReader.java
 package com.psol.survex; import org.xml.sax.*; import java.util.*; public class SurveyReader    implements ContentHandler {    protected Stack stack;    protected StringBuffer buffer;    public Survey getSurvey()    {       return (Survey)stack.pop();    }    public void setDocumentLocator(Locator locator)       {}    public void startDocument()    {       stack = new Stack();    }    public void endDocument()       {}    public void startElement(String namespaceURI,                             String localName,                             String tag,                             Attributes atts)    {       if(tag.equals("survey"))          stack.push(new Survey());       else if(tag.equals("question"))          stack.push(new Question());       else if(tag.equals("choice"))          stack.push(new Vector());       else if(tag.equals("option"))          stack.push(new Option());       else if(tag.equals("input"))          buffer = new StringBuffer();       else if(tag.equals("text"))       {          stack.push(new Constant());          buffer = new StringBuffer();       }       else if(tag.equals("answer"))       {          stack.push(new Parameter());          buffer = new StringBuffer();       }       else if(tag.equals("if"))          stack.push(new If());       else if(tag.equals("eq"))          stack.push(new Equal());       else if(tag.equals("save"))          stack.push(new Save());       else if(tag.equals("block"))          stack.push(new Block());       else if(tag.equals("name")                tag.equals("title")                tag.equals("label")                tag.equals("value"))          buffer = new StringBuffer();    }    public void endElement(String namespaceURI,                           String localName,                           String tag)    {       if(tag.equals("question"))       {          Question question = (Question)stack.pop();          Survey survey = (Survey)stack.peek();          survey.addQuestion(question);       }       else if(tag.equals("choice"))       {          Vector vector = (Vector)stack.pop();          Option[] options = new Option[vector.size()];          vector.copyInto(options);          Question question = (Question)stack.peek();          question.setOptions(options);       }       else if(tag.equals("option"))       {          Option option = (Option)stack.pop();          Vector vector = (Vector)stack.peek();          vector.addElement(option);       }       else if(tag.equals("input"))       {          Question question = (Question)stack.peek();          question.setInput(buffer.toString());          buffer = null;       }       else if(tag.equals("text"))       {          Constant constant = (Constant)stack.peek();          constant.setText(buffer.toString());          buffer = null;       }       else if(tag.equals("answer"))       {          Parameter parameter = (Parameter)stack.peek();          parameter.setName(buffer.toString());          buffer = null;       }       else if(tag.equals("if"))       {          Statement _else = (Statement)stack.pop(),                    then = (Statement)stack.pop(),                    cond = (Statement)stack.pop();          If _if = (If)stack.peek();          _if.setArgs(cond,then,_else);       }       else if(tag.equals("eq"))       {          Statement arg1 = (Statement)stack.pop(),                    arg2 = (Statement)stack.pop();          Equal equal = (Equal)stack.peek();          equal.setArgs(arg1,arg2);       }       else if(tag.equals("save"))       {          Statement filename = (Statement)stack.pop();          Save save = (Save)stack.peek();          save.setFilename(filename);       }       else if(tag.equals("block"))       {          Vector vector = new Vector();          Statement s = (Statement)stack.pop();          while(!(s instanceof Block))          {             vector.addElement(s);             s = (Statement)stack.pop();          }          Statement[] statements = new Statement[vector.size()];          vector.copyInto(statements);          ((Block)s).setStatements(statements);          stack.push(s);       }       else if(tag.equals("name"))       {          Question question = (Question)stack.peek();          question.setName(buffer.toString());          buffer = null;       }       else if(tag.equals("title"))       {          Question question = (Question)stack.peek();          question.setTitle(buffer.toString());          buffer = null;       }       else if(tag.equals("label"))       {          Object o = stack.peek();          if(o instanceof Question)             ((Question)o).setLabel(buffer.toString());          else             ((Option)o).setLabel(buffer.toString());          buffer = null;       }       else if(tag.equals("value"))       {          Option option = (Option)stack.peek();          option.setValue(buffer.toString());          buffer = null;       }       else if(tag.equals("next"))       {          Statement script = (Statement)stack.pop();          Question question = (Question)stack.peek();          question.setScript(script);       }    }    public void characters(char ch[],int start,int len)    {       if(null != buffer)          buffer.append(ch,start,len);    }    public void ignorableWhitespace(char ch[],                                    int start,                                    int length)       {}    public void processingInstruction(String target,String data)       {}    public void skippedEntity(String name)       {}    public void startPrefixMapping(String prefix,String uri)       {}    public void endPrefixMapping(String prefix)       {} } 

Notice that this class uses a different approach to tracking states than the DocumentHandler from Chapter 1, "Lightweight Data Storage." Specifically, instead of using constants, it uses a stack.

In startElement() , it pushes objects on the stack:

 else if(tag.equals("option"))      stack.push(new Option()); 

And in endElement() , it pops. In most cases, it will pass them (as properties) to their parents, which are also in the stack:

 else if(tag.equals("option"))   {      Option option = (Option)stack.pop();      Vector vector = (Vector)stack.peek();      vector.addElement(option);   } 

Putting It All Together in the Servlet

From these building blocks, building the servlet is not difficult. The servlet class, Survex , is shown in Listing 2.13.

Listing 2.13 Survex.java
 package com.psol.survex; import java.io.*; import java.util.*; import org.xml.sax.*; import javax.servlet.*; import javax.servlet.http.*; import org.xml.sax.helpers.*; public class Survex    extends HttpServlet {    public static final String PARSER_NAME =       "org.apache.xerces.parsers.SAXParser";    protected Survey survey;    public void init()       throws ServletException    {       try       {          XMLReader xmlReader =             XMLReaderFactory.createXMLReader(PARSER_NAME);          SurveyReader sreader = new SurveyReader();          xmlReader.setContentHandler(sreader);          xmlReader.parse("survey.xml");          survey = sreader.getSurvey();       }       catch(IOException e)       {          throw new ServletException(e);       }       catch(SAXException e)       {          throw new ServletException(e);       }    }    public Dictionary getParameters(HttpServletRequest request)    {       Dictionary parameters = new Hashtable();       Enumeration keys = survey.getKeys();       while(keys.hasMoreElements())       {          String name = (String)keys.nextElement(),                 value = request.getParameter(name);          if(null != value)             parameters.put(name,value);       }       return parameters;    }    public void writeHTML(Question question,                          String servletpath,                          Writer writer,                          Dictionary parameters)       throws IOException    {       writer.write("<HTML><HEAD><TITLE>");       writer.write("A Survex Survey: ");       writer.write(question.getTitle());       writer.write("</TITLE></HEAD><BODY>");       writer.write("<FORM ACTION='");       writer.write(servletpath);       writer.write("'METHOD='POST'>");       writer.write("<INPUT TYPE='HIDDEN'NAME='name'VALUE='");       writer.write(question.getName());       writer.write("'>");       writer.write("<TABLE ALIGN='CENTER'BORDER='1'>");       writer.write("<TR><TD BGCOLOR='black'><B>");       writer.write("<FONT COLOR='white'>");       writer.write(question.getTitle());       writer.write("</B></FONT></TD></TR><TR><TD><P>");       writer.write(question.getLabel());       if(null != question.getOptions())       {          writer.write("<P>");          Option[] options = question.getOptions();          for(int i = 0;i < options.length;i++)          {             writer.write("<INPUT TYPE='RADIO'NAME='");             writer.write(question.getName());             writer.write("'VALUE='");             writer.write(options[i].getValue());             writer.write("'>");             writer.write(options[i].getLabel());             writer.write("<BR>");          }       }       else if(null != question.getInput())       {          writer.write("<P>");          writer.write(question.getInput());          writer.write(": <INPUT TYPE='TEXT'NAME='");          writer.write(question.getName());          writer.write("'>");       }       if(null != question.getOptions()           null != question.getInput())          writer.write("<P><INPUT TYPE='SUBMIT'VALUE='Next'>");       writer.write("</TD><TR></TABLE>");       Enumeration keys = parameters.keys();       while(keys.hasMoreElements())       {          String parameter = (String)keys.nextElement();          writer.write("<INPUT TYPE='HIDDEN'NAME='");          writer.write(parameter);          writer.write("'VALUE='");          writer.write((String)parameters.get(parameter));          writer.write("'>");       }       writer.write("</FORM></BODY></HTML>");       writer.flush();    }    public void doGet(HttpServletRequest request,                      HttpServletResponse response)       throws ServletException, IOException    {       Question question = survey.getRootQuestion();       if(null != question)          writeHTML(question,                    request.getServletPath(),                    response.getWriter(),                    new Hashtable());       else          response.sendError(HttpServletResponse.SC_NOT_FOUND);    }    public void doPost(HttpServletRequest request,                       HttpServletResponse response)       throws ServletException, IOException    {       Dictionary parameters = getParameters(request);       String name = request.getParameter("name");       Question question = null;       if(null == name)          question = survey.getRootQuestion();       else       {          question = survey.getQuestion(name);          if(null != question)          {             Statement script = question.getScript();             name = script.apply(parameters);             question = survey.getQuestion(name);          }       }       if(null != question)          writeHTML(question,                    request.getServletPath(),                    response.getWriter(),                    parameters);       else          response.sendError(HttpServletResponse.SC_NOT_FOUND);    } } 

Review the following listing step by step. The first method is init() , which reads the XML configuration file upon loading.

Next, the class defines two helper methods : getParameters() and writeHTML() . getParameters() collects the answers for all the questions. As you will see, the browser always has the entire list of answers and passes them to the servlet with each request:

 public Dictionary getParameters(HttpServletRequest request)    {       Dictionary parameters = new Hashtable();       Enumeration keys = survey.getKeys();       while(keys.hasMoreElements())       {         String name = (String)keys.nextElement(),                value = request.getParameter(name);         if(null != value)            parameters.put(name,value);       }       return parameters;    } 

writeHTML() prints a Question as an HTML page. Notice that the page includes the various answers as hidden input fields. Another hidden field contains the name of the current question. These hidden fields are returned to the server by the browser with each request, as in the following:

 public void writeHTML(Question question,                          String servletpath,                          Writer writer,                          Dictionary parameters)       throws IOException    {       writer.write("<HTML><HEAD><TITLE>");       writer.write("A Survex Survey: ");       writer.write(question.getTitle());       writer.write("</TITLE></HEAD><BODY>");       writer.write("<FORM ACTION='");       writer.write(servletpath);       writer.write("'METHOD='POST'>");       writer.write("<INPUT TYPE='HIDDEN'NAME='name'VALUE='");       writer.write(question.getName());       writer.write("'>");       writer.write("<TABLE ALIGN='CENTER'BORDER='1'>");       writer.write("<TR><TD BGCOLOR='black'><B>");       writer.write("<FONT COLOR='white'>");       writer.write(question.getTitle());       writer.write("</B></FONT></TD></TR><TR><TD><P>");       writer.write(question.getLabel());       if(null != question.getOptions())       {          writer.write("<P>");          Option[] options = question.getOptions();          for(int i = 0;i < options.length;i++)          {             writer.write("<INPUT TYPE='RADIO'NAME='");             writer.write(question.getName());             writer.write("'VALUE='");             writer.write(options[i].getValue());             writer.write("'>");             writer.write(options[i].getLabel());             writer.write("<BR>");          }       }       else if(null != question.getInput())       {          writer.write("<P>");          writer.write(question.getInput());          writer.write(": <INPUT TYPE='TEXT'NAME='");          writer.write(question.getName());          writer.write("'>");       }       if(null != question.getOptions()           null != question.getInput())          writer.write("<P><INPUT TYPE='SUBMIT'VALUE='Next'>");       writer.write("</TD><TR></TABLE>");       Enumeration keys = parameters.keys();       while(keys.hasMoreElements())       {          String parameter = (String)keys.nextElement();          writer.write("<INPUT TYPE='HIDDEN'NAME='");          writer.write(parameter);          writer.write("'VALUE='");          writer.write((String)parameters.get(parameter));          writer.write("'>");       }       writer.write("</FORM></BODY></HTML>");       writer.flush();    } 

doGet() outputs the first (or root) question to get the user started. doPost() , on the other hand, is where all the fun is because it uses the script to decide on which question to post.

doPost() first retrieves the name of the current question and the answers to the various questions. Next, it calls the script to compute the name of the next question. It couldn't be simpler! The following demonstrates this:

 public void doPost(HttpServletRequest request,                       HttpServletResponse response)       throws ServletException, IOException    {       Dictionary parameters = getParameters(request);       String name = request.getParameter("name");       Question question = null;       if(null == name)          question = survey.getRootQuestion();       else       {          question = survey.getQuestion(name);          if(null != question)          {             Statement script = question.getScript();             name = script.apply(parameters);             question = survey.getQuestion(name);          }       }       if(null != question)          writeHTML(question,                    request.getServletPath(),                    response.getWriter(),                    parameters);       else          response.sendError(HttpServletResponse.SC_NOT_FOUND);    } 
   


Applied XML Solutions
Applied XML Solutions
ISBN: 0672320541
EAN: 2147483647
Year: 1999
Pages: 142

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net