Класс Recorder
Класс Recorder отвечает за хранение ответов пользователя на каждый вопрос и за их запись в файл для дальнейшего анализа. Каждому пользователю отводится экземпляр класса Recorder, который связан с данным сеансом и записывает ответы только этого пользователя.
Корневым элементом документа XML является тег Questionnaire, у которого имеется несколько атрибутов, используемых классом Recorder. Как показано в следующем примере тега Questionnaire, эти атрибуты называются title, author, date, method и file:
<Questionnaire title="Survey 1" author="WBB" date="May 19, 2000" method="xml" file="e:\scripts\questionnaire\test result.xml" >
Атрибуты title, author и date просто записываются в класс Recorder и отображаются позже, но атрибуты method и file управляют действиями класса Recorder. Мы включили атрибут method для того, чтобы можно было записывать результаты опроса в формате, отличном от XML. Например, если вы предпочитаете использовать программу анализа таблиц, можете добавить методы, которые запишут результаты в виде строки текста, разделенного запятыми. Атрибут file задает путь к стандартному файлу, куда будут записываться результаты, если не назначен другой файл в теге Terminal.
В формате XML, который мы здесь используем, ответы каждого пользователя записываются в тег Qresults, для каждого вопроса отводится тег Ques, а для каждого выбранного ответа — тег Qopt. Конечно, такая структура является более объемной, чем запись в одну строку, но зато она очень гибкая. Получившаяся структура не является законченным документом XML, так как в ней отсутствует корневой элемент. Мы покажем, как решается этот вопрос в разделе «Варианты анализа анкеты».
В листинге 7.20 показаны инструкции импорта, переменные экземпляра и единственная статическая переменная класса Recorder. Статическая переменная filelock является объектом типа String, который используется в инструкции synchronized для того, чтобы гарантировать, что в каждый момент времени только один экземпляр объекта Recorder фактически осуществляет запись в файл.
Листинг 7.20. Начало исходного кода класса Recorder (Recorder.java)
package com.XmlEcomBook.Chap07;
import org.w3c.dom.* ; import com.sun.xml.tree.* ; import java.io.*; import java.util.* ; import javax.servlet.*; import javax.servlet.http.*;
public class Recorder { // this String is used to prevent more than one Recorder from // writing anywhere at the "same time" static String filelock = "RecorderLock" ;
// these are instance variables String userid, usertype, sessionid ; String qresultStr ; String source ; // the xml file String method, output ; // how and where we save Hashtable record ; // one string per response public boolean terminated = false ;
Конструктор класса Recorder, показанный в листинге 7.21, вызывается из метода doGet сервлета. Он задает несколько переменных, характеризующих конкретного пользователя. Также он создает хэш-таблицу record, которая будет использоваться для записи ответов на вопросы.
В листинге 7.21 также показан метод setMethods, который определяет.метод записи результатов и атрибутов файла с помощью документа XML. Также он создает открывающий тег Qresults и сохраняет его в переменной qresultStr для дальнейшего использования.
Листинг 7.21. Конструктор класса Recorder и метод setMethods (Recorder.java)
public Recorder(String id, String typ, String ses,String src ){ userid = id ; usertype = typ ; sessionid = ses ; source = src ; record = new Hashtable(); }
/* method information from <Questionnaire> attributes <Questionnaire title="First Test Quest" author="wbb" date="May 19, 2000" method="xml" file="e:\scripts\questionnaire\testresult.xml" > */ public void setMethods( Document doc ){ NamedNodeMap nnm = doc.getDocumentElement().getAttributes(); method = nnm.getNamedItem("method").getNodeValue(); output = nnm.getNamedItem("file").getNodeValue(); // for xml method StringBuffer sb = new StringBuffer( 50 ); sb.append("<Qresults source=\""); sb.append( source ); sb.append( "\" date=\"" ); sb.append( new Date().toString() ); sb.append( "\" userid=\""); sb.append(userid); sb.append( "\" usertype=\"" ) ; sb.append( usertype ); sb.append( "\" sessionid=\""); sb.append( sessionid ); sb.append("\">\r\n"); qresultStr = sb.toString(); }
В листинге 7. 22 показаны методы terminal, record и toString. Метод terminal отвечает за запись собранных ответов данного пользователя в отведенный для этого файл. Метод toString используется как вспомогательный при отладке.
Метод record вызывается из метода doPost объекта Interpreter после того, как приходит ответ на очередной вопрос. Отметим, что если вам нужно будет создать какой-либо новый тип, требующий специального способа записи, метод record допускает переключение между типами вопросов. Например, если вам потребуется принимать введенный пользователем текст, он будет записан в раздел CDATA, чтобы в случае наличия в этом тексте каких-либо символов, имеющих специальное назначение в XML, не возникло бы затруднений.
При каждом вызове метода record создается строка, содержащая тег Ques, которая записывается в хэш-таблице record и ключом для которой служит переменная quesid. Поскольку хэш-таблица не сохраняет порядок добавления в нее элементов, очередность расположения тегов Ques в ней непредсказуема. Но это не страшно, поскольку для определения порядка следования вопросов мы можем использовать документ XML, содержащий сценарий опроса.
Листинг 7.22. Код класса Recorder, продолжение (Recorder.java)
// called when a <Terminal block is reached // if altdest is not "" this changes the default output public void terminal(String altdest ) throws IOException { if( altdest != null && altdest.length() > 4 ) output = altdest ; if( output == null || output.length() < 5 ){ System.out.println("QARG output is: " + output ); return ; } terminated = true ; // write in append mode synchronized( filelock ){ FileWriter fw = new FileWriter( output,true ); PrintWriter pw = new PrintWriter( fw ); pw.print( qresultStr ); Enumeration e = record.elements(); while( e.hasMoreElements() ){ pw.print( (String)e.nextElement() ) ; } pw.print("</Qresults>\r\n"); pw.close(); } // end synchronized block } // recording format in xml /*<Qresults source=.... date= > <Ques id="start:1"> <Qopt val="a"></Qopt><Qopt val="b"></Qopt> </Ques>
</Qresults> */ public void record( String quesid, int type, String[] optS ){ if( terminated ) return ; // prevent backing up from terminal Q // System.out.println("Start record: " + quesid ); StringBuffer sb = new StringBuffer( 100 ); sb.append("<Ques id=\"" ); sb.append( quesid ); sb.append("\" >"); switch( type ){ case Interpreter.QMC : case Interpreter.QMCM : if( optS == null || optS.length == 0 ) break ; for(int i =0 ; i < optS.length ; i++ ){ sb.append("<Qopt val=\""); sb.append( optS[i] ); sb.append("\"></Qopt>"); } break ; default : sb.append("UNKNOWN TYPE"); } sb.append("\r\n</Ques>\r\n"); String tmp = sb.toString(); // note this will replace answer if user backed up with browser back record.put( quesid, tmp ); return ; }
public String toString() // for debugging { StringBuffer sb = new StringBuffer( "Recorder user: " ); sb.append( userid ); sb.append(" type: " ); sb.append( usertype ); sb.append(" session: " ); sb.append( sessionid );sb.append(" method: "); sb.append( method ); sb.append( " output: " ); sb.append( output ); // how and where we save return sb.toString() ; }
}
В листинге 23 показаны результаты ответа одного пользователя на простой опрос. Атрибут source указывает, какой файл XML использовался для создания анкеты. В атрибут date записывается дата первого вхождения пользователя в систему и открытия страницы введения в анкету. Мы также включили атрибут sessionid для помощи в отладке, но, вероятно, без него можно обойтись.
Листинг 7.23. Запись результатов опроса одного пользователя на XML
<Qresuits source="e:\scripts\javatest.xml" date="Mon May 22 22:30:20 CDT 2000" userid="unknown" usertype="passed" sessionid="9590526208594804">
<Ques id="studying:4" >
<Qopt val="a"></Qopt><Qopt va1="b"></Qopt> <Qopt val="d"></Qopt><Qopt val=*e"> </Qopt> <Qopt val="k"></Qopt><Qopt val="l"> </Qopt> <Qopt val="m"> </Qopt> </Ques>
<Ques id="studying:3"><Qopt val="l"></Qopt> </Ques> <Ques id="studying:2"><Qopt val="2"> </Qopt> </Ques> <Ques id="studying:1" > <Qopt val="a"> </Qopt> <Qopt val="f"></Qopt> </Ques> </Qresults>