390 likes | 508 Views
JSP and XML Working Together. Presented by: Lin Yang Mar 5, 2001. Outline. JSP and XML A Profiled Newsletter- A Case Study The Configuration Component The Database Component XML Data Access Component The Presentation Component The Control Component. JSP and XML. XML. Presentation
E N D
JSP and XML Working Together Presented by: Lin Yang Mar 5, 2001
Outline • JSP and XML • A Profiled Newsletter- A Case Study • The Configuration Component • The Database Component • XML Data Access Component • The Presentation Component • The Control Component
JSP and XML XML Presentation Layer: XML within JSP Controller Servlet/JSP Java Middleware Information Source
JSP functions best in the Transformation and Presentation layer as template pages that can easily incorporate dynamic material from the Java classes of the application (usually JavaBeans). JSp can also function as the main entry point and controller of the application, the role frequently played by a servlet. • XML is best know as a standard for defining both presentation formats (language) and formats for data representation and interchange. XML can also be used for dynamic configuration of J2EE applications. The tree structure of XML documents is well suited for describing alll kinds of structured entries, including networks of Java objects that instantiate and use each other. • In this case study, associated with each login is an XML configuration file that instantiates one of a number of applications.Each application is internally a different set of JavaBeans, instantiated from the XML configuration file. The JavaBeans query different information sources, including relational databases and XML files.
Case Study Design Features • The use interacts with the program via JSP pages. • Each user maintains an XML configuration document that the user can view and edit. • Configuration documents are kept as text strings in a database table. • The content comes from relational databases and XML document. In either case, the content is sent to the application as a stream of SAX events filtered through XSLT stylesheets.
Application Structure XML data access Information sources Presentation Login Request response Control Database access Configuration To output
Presentation Component • top.jsp is the login page that submits login data to frames.jsp • frames.jsp is the main user interface page that consists of a control frame (spControl.jsp) and a data frame (spQuery.jsp) • errorpage.jsp is for errors • spLogout.jsp is for logout (invalid session and say goodby Configuration Component • spConfigure.jsp is where the user can view and edit his configuration string • spSaveconfig.jsp saves the editedconfiguration string to a database table and forwards to frame.jsp Control Component • StampPageFactory.java produces a StampPage bean for the session, configured to the user’s XML configuration string or default XML configuration file • The stampPage bean interacts with the database and XML information sources to produce the results for the spQuery.jsp page
Database Access Component • DBConnector interface and its default implementation, DBConnectorImpl • QueryHandler that extends DBConnectorImpl • ResultSetParser that converts a result set into a stream of SAX DocumentHandler calls XML Data Access Component • DomSearch parses an XML document into a DOM tree and outputs the entire tree or a subtree satisfying certain search conditions • Dom2Sax parses a DOM node into a stream of SAX DocumentHandler calls
The Configuration Component • All configuring is done by the static method Config ( ). This method receives an XML description of the objects it needs to configure and operates on that description. • There are three overloaded versions of Config ( ). The really important one takes a DOM node as an argument and works with it to configure an application. The other two receive an XML document, parse it into a DOM object and look for <top-config> element nodes in it. If found, they pass the first <top-config> to the version Config ( ) that expects a DOM node. • The configuration process consists of Java code operating on an XML document.
//Config.java public static Object Config(String fileName) throws Exception { Document doc = DomSearch.readFile(fileName); NodeList topConfigL = doc.getElementsByTagName("top-config"); if(null == topConfigL || topConfigL.getLength() < 1) throw new Exception("no 'top-config nodes' in document " + fileName); return config(topConfigL.item(0));} public static Object Config(InputSource in) throws Exception {Document doc = DomSearch.readInputSource(in); NodeList topConfigL=doc.getElementsByTagName("top-config"); if(null == topConfigL || topConfigL.getLength() < 1) throw new Exception("no 'top-config' nodes in in inputsource document");return config(topConfigL.item(0));} public static Object config(Node node)throws Exception { short nodeType = node.getNodeType(); if(nodeType != Node.ELEMENT_NODE) { return nonNullString(node.getNodeValue()); } Element elt = (Element)node; String obClassName = elt.getAttribute("obClass"); if("".equals(obClassName) || "string".equals(obClassName)) { return nonNullStringValue(elt); } Object ob; if("".equals(elt.getAttribute("cons"))) { ob = newObject(obClassName); } else {ob = newObject(obClassName,elt.getAttribute("arg")); } configChildren(ob, elt.getChildNodes()); return ob;}
The Database Component • The database consists of an interface and three classes: • DBConnector declares the most general functionality of databse interaction, including Connection manager • DBConnectorImpl is a do-nothing default implementation of DBConnector • QueryHandler extends DBConnectorImpl to provide functionality actually used in our case study • ResultSetParser plays the same role in the database component as Dom2Sax plays in the XML component: it coverts query results (ResultSet objects) produced by QueryHandler into a sequence of DocumentHandler calls
Interface DBConnector Class DBConnectorImpl implements implements Class ResultSetParser Interface ResultSet Class QueryHandler Interface Org.xml.sax.Parser
DBConnector Interface import java.sql.*; public interface DBConnector { // The first three methods have to do with connection management Connection getConnection() throws Exception; void freeConnection() throws SQLException; void close() throws SQLException; // The remaining four methods have to do with information // that is needed to open a connection void setDriver(String driverName); // The name of the JDBC driver to use void setURL(String dbURL); // The name of the "database URL" // The syntax varies from driver to driver void setUser(String user); // User name for the database void setPwd(String pwd); // Password }
QueryHandler QueryHandler contains an inner class that implements the “named query” abstraction. In QueryHandler, this is a protected inner class called DBQuery. QueryHandler contains two Hashtables of such DBQuery objects, indexed by their names: one Hashtable for SELECT queries, the other for UPDATE queries. SELECT * FROM FORCAST WHERE zip=12345 This command would proceed as follows: • Creat a query string with question marks for query parameters, in this case “SELECT * FROM FORCAST WHERE zip=?” • Give the query a name, e.g. “weather” • Add the query string to the QueryHandler either by adding material to the XML initialization file, or directly by calling the addQuery( ) method of QueryHandler • Using the query by running this line of code: ResultSet rs = spBean.GetQueryHandler ( ). query( “weather”, “12345”);
The DBQuery Class protected class DBQuery { String name = null, queryStr = null; PreparedStatement pS = null; public DBQuery(String name, String qStr) { this.name = name; queryStr = qStr;} // Create PreparedStatement, run the query public ResultSet query(String[] params) throws Exception { if (pS == null) { // create PreparedStatement the first time it's run pS = getConnection().prepareStatement(queryStr);} for (int i = 0; i < params.length; i++) { setString(i + 1, params[i]);} return pS.executeQuery();} public int update(String[] params) throws Exception { if (pS == null) { pS = getConnection().prepareStatement(queryStr);} for (int i = 0; i < params.length; i++) { setString(i + 1, params[i]);} return pS.executeUpdate();} public void setString(int i, String S) throws SQLException { if (S == null || S.length() < 256) { pS.setString(i, S); // default ok here } else { // use setObject, with explicit type conversion pS.setObject(i, S, java.sql.Types.LONGVARCHAR); } } public void close() { if (pS == null) { return; } try {pS.close();} catch (Exception ex) {} pS = null;} }
XML Data Access Component The output of a DomSearch is a DOM node that satisfies the search. To make that node available to the rest of the applications, we pass it through Dom2Sax. Dom2Sax uses a SAX parser to traverse a DOM Node and output its contents as a sequence of SAX events. Dom2Sax plays exactly the same role in the XML data access component that ResultSetParser plays in the data access component. In both cases, the contents of a Java data structure is serialized as a sequence of SAX events and sent to a Sax2Writer object. In this case study, the writer in question is the JSPWriter associated with spQuery.jsp: DOM DOM SAX Writer XML Date Source DomSearch Dom2Sax Sax2Writer
Instantiating DomSearch - DSNextMatch A DomSearch object is created within this line of code in frames.jsp: StampPage spBean = spBeanF.getStampPage ( ); getStampPage ( ) calls findStampPageByString ( ) or findStampPageByFile ( ). Either of those calls Config. Config ( ) to create a StampPage object and its possessions. In the process, Config. Config ( ) uses XML configuration data, coming either from the XML configuration string stored in the database or from an XML configuration file. public class DSNextMatch extends DomSearch { protected String initFile() throws Exception { super.initFile(); return nextMatch();} public String doCommand() { // Assumes values read in from dict try { if ("reset".equalsIgnoreCase(domCmd)) { resetNode();return nextMatch(); } else {return super.doCommand();}} catch (Exception ex) { lg.logIt("DSNextMatch.doCommand: ", ex); return ("Error: DSNextMatch.doCommand " + ex);} }}
Configuration XML data for DomSearch //spStampPage.xml <top-config obClass="StampPage"> <!-- configure DSNextMatch, subclass of DomSearch --> <set fieldName="domSearch" obClass="DSNextMatch"> <set fieldName="dict" obClass="PropDict"> <apply name="setDef"><ob>outerTag</ob><ob>item</ob></apply> <apply name="setDef"><ob>outerAttr</ob><ob>type</ob></apply> <apply name="setDef"><ob>outerAVal</ob><ob>stamp</ob></apply> <apply name="setDef"><ob>innerTag</ob><ob>country</ob></apply> <apply name="setDef"><ob>innerCVal</ob><ob>Germany</ob></apply> <apply name="setDef"><ob>domCmd</ob><ob>initFile</ob></apply> <apply name="setDef"><ob>fileName</ob><ob>C:\TextSourceCode\Chapter15\data\stamps.xml</ob> </apply></set> <apply name="initFromDict" /></set> ……
The Presentation Component //Top.jsp: <%@ page errorPage="errorpage.jsp" %> <html> <head><title> The Tele-Philately Follies </title> </head><body><center> <h1> The Tele-Philately Follies </h1> <p><%= new java.util.Date() %>. </p> </center> <form method="post" action="frames.jsp" > User: <input name="userName" type="text" size="10" /><br /> Password: <input name="userPwd" type="password" size="10" /><br /> <input type="submit" value="go!" /></form> </body></html>
The Main Frames Page and Initialization- frame.jsp <%@ page %><jsp:useBean id="spBeanF“ class="StampPageFactory" scope="session"/> <%// Validation section boolean userNameAndPasswordOkay = true; // Bean could check login data here if(!userNameAndPasswordOkay) { %><jsp:forward page="badLogin.jsp" /> <%}// End validation section; start initialization section// This code is best revisited after // Set up PropDict to hold request data, // for transfer to StampPageFactory and StampPage PropDict pDict = new PropDict(request); spBeanF.setUserName(pDict.getDef("userName")); // User name will be used to retrieve user profile String configFileName = "/data/spStampPage.xml"; configFileName = application.getRealPath(configFileName); spBeanF.setConfigFile(configFileName); // Create a StampPage instance and transfer request data to it StampPage spBean = spBeanF.getStampPage();spBean.setDict(pDict); Logger lg = new Logger();lg.logIt("check pDict: " + pDict); // Obtain instance of DomSearch to retrieve XML data// and get it ready for action DomSearch domSrch = spBean.getDomSearch();domSrch.updateFromDict(pDict); lg.logIt("domSrch.updateFromDict complete");domSrch.doCommand("initFile"); lg.logIt("domSrch.doCommand complete");%> <html><head><title> StampPage </title></head> <frameset cols="20%,80%"><frame name="ctlFrame" src="spControl.jsp"> <frame name="dataFrame" src="spQuery.jsp“></frameset></html>
The initialization process starts by transferring the user name from Request to StampPageFactory spBeanF via a PropDict. We also set the property of spBeanF to provide default configuration in case there is no XML configuration string associated with the user name. • With userName and configFile properties set, we call getStampPage ( ) to obtain the StampPage bean for the session. With a SatmpPage in hand, we transfer Request data to it via a PropDict and obtain its DomSearch object. The DomSearch object is also initialized from Request data. • There are three forms. Their action attributes are spQuery.jsp, spConfigure.jsp and spLogout.jsp. //spConfigure.jsp <%@ page errorPage="errorpage.jsp" %> <jsp:useBean id="spBeanF" class="StampPageFactory" scope="session"/> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html><head><title>Configuration page for StampPage </title></head> <body><form target="_parent" method="post" action="spSaveConfig.jsp" ><input type="submit" value="Save" /> <input name="userName" type="hidden" value="<%= spBeanF.getUserName() %>" /> <textarea name="configString" rows="20" cols="50"> <%=spBeanF.getConfigString()%></textarea></form></body></html>
If you click Save, the submitted string goes to spSaveConfig.jsp that invokes the methods of SatmpPageFactory to the configuration data in the database for future sessions and also to use it in configuring the current StampPage: //spSaveConfig.jsp <%@ page errorPage="errorpage.jsp" %> <jsp:useBean id="spBeanF" class="StampPageFactory" scope="session"/> <% Logger lg=new Logger(); String cfg=request.getParameter("configString"); lg.logIt("now setting new configString: " + cfg); spBeanF.setConfigString(cfg); // spBeanF.setConfigString(request.getParameter("configString")); spBeanF.saveUserData(); // for next time lg.logIt("saveUserDate completed"); spBeanF.findStampPageByString(); // for this time. lg.logIt("findStampPageByString completed - forwarding back to frames.jsp"); %> <jsp:forward page="frames.jsp" />
spQuery.jsp • We start with a page directive and a useBean action. <%@ page errorPage="errorpage.jsp" contentType="application/xml" %><jsp:useBean id="spBeanF" class="StampPageFactory" scope="session"/> <% out.clear(); // don't send the empty lines from directives above%><?xml version="1.0" ?> • Next, we output the Processing Instruction for a stylesheet to go with our XML output: <?xml-stylesheet href="http://localhost:8080/Chapter15/styles/stamps.css" type="text/css"?> • We start running the application. Our first task is to transfer request data to the StampPage bean: <% Logger lg=new Logger(); StampPage spBean = spBeanF.getStampPage(); PropDict dict = new PropDict(request); spBean.getDict().setDef(dict); // Adds new definitions from request if(zipCode.length() > 0) spBean.setZipCode(zipCode); spBean.getDomSearch().updateFromDict(dict); // Update DomSearch String domCmd= dict.getDef("domCmd"); // Get new domCmd for DomSearch if(domCmd.length() > 0) // Run the command with new settings from request spBean.getDomSearch().doCommand(domCmd);%>
Now we can produce output, combing template data with dynamic content produced by bean: <toptag><field>This is an XML page, demonstrating JSP inclusion of multiple XML sources and minimal CSS tyling for parts.</field> <name> Your name is <%= spBeanF.getUserName() %></name>, and <zipcode> your zip code is <%= spBean.getZipCode() %> </zipcode> • Next, we obtian a DomSearch, and have it produce output from stamps.xml styled by our stylesheet stamps.css: <%DomSearch domSrch = spBean.getDomSearch(); domSrch.doCommand("reset");domSrch.setWriter(out); domSrch.writeData() ;%> • The next produces database output from the ch15weather database: <% try { QueryHandler qh = spBean.getQueryHandler(); ResultSetParser rsp = new ResultSetParser(); Sax2Writer s2w = new Sax2Writer(rsp, out); // Send output to JSPWriter out rsp.setResultSet(qh.query("weather", spBean.getZipCode())); rsp.parseLoop(); // Don't generate xml headers } catch(Exception ex) {}%>
The Control Component • The factory bean is first used in frames.jsp to initialize a session: PropDict pDict = new PropDict(request); spBeanF.setUserName(pDict.getDef("userName")); // User name will be used to retrieve user profile String configFileName = "/data/spStampPage.xml"; configFileName = application.getRealPath(configFileName); spBeanF.setConfigFile(configFileName); // Create a StampPage instance and transfer request data to it StampPage spBean = spBeanF.getStampPage();
The code of getStampPage ( ): public class StampPageFactory { protected Logger lg;protected StampPage stampPage=null; protected String configString=null, userName=null, configFile=null; protected UserDataHandler userDataHandler=null;public StampPageFactory(){ lg=new Logger();} public StampPageFactory(String defaultFile){ this(); setConfigFile(defaultFile); } public StampPage getStampPage() { if (null != stampPage) { // Not the beginning of session return stampPage;} else if (null != configString) { // configString already set return findStampPageByString(); } else if (null != getUserData()) { // Get configString from database return findStampPageByString();} else { return findStampPageByFile(); // No configString; use default } }
getUserData ( ) uses the methods of UserDataHandler to retrieve the configuration string: public String getUserData() { if (userName == null) {return null;} if (null != configString) {return configString;} if (userDataHandler == null) { userDataHandler = UserDataHandler.getInstance();} configString = userDataHandler.getUserData(userName); return configString;} • One way or another, from a string or from a file, getStampPage ( ) configures a StampPage using XML configuration data. This is where the Control component connects to the Configuration component, and we finally get to see the XML configuration file for the application.
Configuration Methods public String getUserName(){return userName;} public StampPage findStampPageByString() { try {// Create an InputSource from String, give it to Config.config() InputSource in = new InputSource(new java.io.StringReader(configString)); stampPage = (StampPage) Config.Config(in);} catch (Exception ex) { lg.logIt("findStampPageByString [\n" + configString + "]", ex);return null;} return stampPage;} public StampPage findStampPageByFile() { try {return stampPage = (StampPage) Config.Config(configFile); } catch (Exception ex) { lg.logIt("findStampPageByFile [\n" + configFile + "]", ex); return null; } }
From spStampPage.xml, we configure three fields, a zip code, a DomSearch and a QueryHandler. (Since DomSearch and QueryHandler are objects that have fields of their own, they are configured recursively, from a recursive structure.) in the case of DomSearch, we actually configure a subclass of DomSearch and DSNextMatch. Whichever way we configure, the StampPage comes equipped with those three fields. • In frames.jsp, it has obtained an instance of StampPage: spBean.setDict(pDict); Logger lg = new Logger(); lg.logIt("check pDict: " + pDict); // Obtain instance of DomSearch to retrieve XML data // and get it ready for action DomSearch domSrch = spBean.getDomSearch(); domSrch.updateFromDict(pDict); lg.logIt("domSrch.updateFromDict complete"); domSrch.doCommand("initFile");
The StampPage Bean • This is an empty shell of a class. All the work is done by the objects it owns. It has four protected fields and get/set methods for all of them. import java.io.*; import java.util.Properties; public class StampPage { protected PropDict dict; protected DomSearch domSearch; protected String zipCode; protected QueryHandler qHandler;Logger lg; // This class handles a session's data, customized to a user; // all will be initialized from a single xml file, in nested structure public StampPage() {lg = new Logger();} // Set and get methods public void setDict(PropDict defs) {dict = defs;} public void setDomSearch(Object ds) {domSearch = (DomSearch) ds;} public void setZipCode(String zip) {zipCode = zip;} public void setQueryHandler(Object qh) {qHandler = (QueryHandler) qh; }
public PropDict getDict() {return dict;} public DomSearch getDomSearch() {return domSearch;} public String getZipCode() {return zipCode;} public QueryHandler getQueryHandler() {return qHandler;} // to provide access to PropDict public void setDef(String name, String val) { if (null == dict) { dict = new PropDict();} dict.setDef(name, val);} public String getDef(String name) { if (null == dict) {dict = new PropDict();} return dict.getDef(name); } } // end of StampPage class
Chapter15\ Data\ Ch15Weather.mdb SpStampPage.xml Stamps.xml Styles\ Stamp.class WEB_INF\ Classes\ Config.class PropDicty.class DBConnector.class QueryHandler$DBQuery.class DBConnectorImpl.class QueryHandler.class Dom2Sax.class ResultSetParser.class DomSearch.class Sax2Writer.class DSNextMatch.class StampPage.class Logger.class StampPageFactory.class UserDataHAndler.class errorpage.jsp frames.jsp spConfigure.jsp spCopntrol.jsp spQuery.jsp spSaveConfig.jsp top.jsp
Questions? Thank You!