Oracle9i Case Studies - XML Applications Release 1 (9.0.1) Part Number A88895-01 |
|
This chapter contains the following sections:
The Dynamic News application uses Oracle XML platform components together with the Oracle9i database to build a web-based news service. It combines Java, XML, XSL, HTML, and Oracle9i.
Dynamic News application shows you how to do the following tasks:
Dynamic News pulls news items (headlines) from the database to build HTML pages. The HTML pages are customized according to user preferences.
The pages present lists of items, with each item hyperlinked to a complete article. Each news item has attributes including:
Dynamic News uses these attributes to offer three levels of customization:
Table 5-1 describes these usage choices.
Here's the SQL from nisetup.sql, that defines the structure of a news item:
CREATE TABLE news.NEWS_ITEMS ( ID NUMBER NOT NULL, TITLE VARCHAR2(200), URL VARCHAR2(200), DESCRIPTION VARCHAR2(2000), ENTRY_DATE DATE, CATEGORY_ID NUMBER, SUB_CATEGORY_ID NUMBER, TYPE_ID NUMBER, SUBMITTED_BY_ID NUMBER, EXPIRATION_DATE DATE, A APPROVED_FLAG VARCHAR2(1) );
Table 5-2 lists the servlets used in the Dynamic News application. These servlets provide entry points to the application logic:
Dynamic News generates XML documents to build HTML pages:
Figure 5-1 gives an overview of how Dynamic News performs these steps:
Dynamic News generates static pages to display all available news items. These pages are built at intervals set by the news system administrator, for example, every hour on the hour; otherwise, they don't change.
Static pages are useful in any application where data doesn't change very often. For example, when publishing daily summaries from ERP or customer applications. Because the content is static, it's more efficient to pregenerate a page than to build one for each user who requests it.
The admin executes a batch process, implemented from the Administration servlet, that queries the database and generates an XML document. When an end-user invokes Dynamic News to display all news, a servlet gets the browser type from the user-agent header of the HTTP request, then reads the XML document, and applies the appropriate XSL stylesheet.
Finally, it returns an HTML page formatted for the end-user's browser, as shown in Figure 5-2.
Another approach would be to apply XSL stylesheets as part of the batch process, generating one HTML file for each stylesheet. In this case, you end up with more files to manage, but the runtime servlet is smaller.
The application builds semi-dynamic pages by combining pregenerated lists. The lists of items per category are pregenerated by the administrator (one XML file for each category), but pages that contain them are customized for each user. End-users choose categories such as Sports, Business, and Entertainment.
The semi-dynamic approach is useful when the data doesn't change very often and you want to give the end-user a relatively small number of choices. An application that offers more choices has to pregenerate more documents, and benefits degrade proportionally.
Figure 5-3 shows how semi-dynamic generation works. There are two phases:
The application builds dynamic pages on demand by pulling items directly from the database. End-users access the "Create/Edit User Preference Page" to choose categories, subcategories, and types (for example, Entertainment - Movies - Review).
Dynamic pages are useful for delivering up-to-the-minute information, such as breaking news. They are also useful for delivering historical data, such as the closing price of any specified stock on any day in the last 10 years. It would be impractical to pregenerate documents for every possible request, but straightforward and efficient to pull the figures from the database.
Figure 5-4 shows how dynamic generation works. Unlike the other runtime models, the administrator does not pregenerate XML documents. Instead, the Dynamic Servlet queries the database for news items based on the end-user's customization choices.
The servlet stores user preferences both in the database and in a client-side cookie, and reads them from the cookie where possible to improve performance. Using the query results, the servlet generates an XML file and transforms it using an XSL stylesheet into an HTML page for the user's browser. As with the other approaches, the application gets the browser type from the user-agent header of the HTTP request.
Oracle9i makes Dynamic News flexible. Because news items are stored in the database, Dynamic News can customize content on demand. The code examples in this section show how the application personalizes pages by retrieving news items in categories specified by the end-user. The main tasks are:
Logic for processing preferences is distributed throughout the application, which stores the data both in the database and in client-side cookies. The application reads preference data from a cookie whenever possible to improve performance. If it can't get the data from a cookie (for example, because the end-user is visiting the site for the first time, or the end-user's browser does not accept cookies), the application reads preference data from the database.
The two methods below show how the application processes preference data stored in a cookie. Both methods come from xmlnews.common.UserPreference. Here's a sample cookie:
DynamicServlet=3$0$0#4$2$1***242
The cookie uses dollar signs to separate preference values, pound signs to separate categories, and three asterisks as a token to separate user ID and preference data. The sample cookie above shows that user 242 wants items from categories 3 and 4. In category 3, the user wants items of all types in all subcategories (a value of 0 selects all items). In category 4, the user wants items from subcategory 2 only, and within that subcategory, only items of type 1.
The sample application processes such cookies in two steps:
getNewsCookie
gets the "DynamicServlet" cookie from the browser that issued the HTTP request.
loadPreferenceFromCookie
parses it to get a String that contains that user's ID and preferences.
public Cookie getNewsCookie(HttpServletRequest request)
throws Exception { Cookie c[] = request.getCookies(); Cookie l_returnCookie = null; for (int i = 0; (c!= null) && (i < c.length); i++) { if (c[i].getName().equals("DynamicServlet")) { l_returnCookie = c[i]; } } return l_returnCookie; } public Vector loadPreferenceFromCookie(Cookie p_cookie) throws Exception { Vector l_prefId = new Vector(2); String l_Preferences = p_cookie.getValue(); StringTokenizer l_stToken = new StringTokenizer(l_Preferences, "***"); String l_userId = ""; while (l_stToken.hasMoreTokens()) { // First Token is User Preference. l_Preferences = l_stToken.nextToken(); // Second Token is User ID. l_userId = l_stToken.nextToken(); } l_prefId.addElement(l_Preferences); l_prefId.addElement(l_userId); return l_prefId; }
If it can't read preferences from a cookie, the application queries the database. The class xmlnews.common.GenUtility
implements methods that connect to the database and fetch news categories, sub-categories, and types.
The semi-dynamic servlet and the dynamic servlet both call these methods and the methods loadInitalPreference
and constructUserPreference
. These are both implemented in xmlnews/common/UserPreference.java.
Method loadInitalPreference
calls getSubCategories
, then loops through the result set, combining category values with separator characters to build a preference string.
public String loadInitialPreference(Vector p_category, Vector p_subcategory, Vector p_types, Connection p_con) throws Exception { GenUtility m_general = new GenUtility(); ... for (int i = 0; i < p_category.size(); i++) { String l_cat[] = (String []) p_category.elementAt(i); l_category = l_cat[0]; Vector l_subcategory = m_general.getSubCategories(p_con,l_cat[0]); for(int l_j = 0, l_k = 0; l_j < l_subcategory.size(); l_j++, l_k++) { ... // Append the next preferences to the constructed string l_userPref = l_userPref+"#"+l_category+"$"+l_subCat+"$"+l_typeStr; } } ... return l_userPref; } public static Vector getSubCategories(Connection p_conn, String p_categoryId) throws Exception { Vector l_subCats = new Vector(); PreparedStatement l_pstmt = p_conn.prepareStatement( "Select id, name from sub_categories where category_id = ? "); l_pstmt.setString(1, p_categoryId); ResultSet l_rset = l_pstmt.executeQuery(); while (l_rset.next()) { String[] l_subCat = new String[2]; l_subCat[0] = new String(l_rset.getString(1)); l_subCat[1] = new String(l_rset.getString(2)); l_subCats.addElement(l_subCat); } l_pstmt.close(); return l_subCats; }
For example, the following code comes from xmlnews.dynamic.DynamicServlet.service
.
It calls these methods to read end-user preferences from the database, then uses the preferences to build an HTML page.
public void service(HttpServletRequest p_request, HttpServletResponse p_ response) throws ServletException { // The following are declared elsewhere as class variables // and initialized in the servlet's init method. // GenUtility m_general = null; // m_general = new GenUtility(); // UserPreference m_userPreference = null; // m_userPreference = new UserPreference(); ... // If the database connection has been closed, reopen it. if (m_connection == null || m_connection.isClosed()) m_connection = m_general.dbConnection(); ... String l_preference = m_userPreference.loadInitialPreference( m_general.getCategories(m_connection), null, m_general.getTypes(m_connection), m_connection); m_userPreference = m_userPreference.constructUserPreference ( l_preference,m_status); // Display the Dynamic Page this.sendDynamicPage(l_browserType, p_response, l_userName, m_userPreference, m_servletPath + "?REQUEST_TYPE=SET_ADVANCED_USER_PREFS", m_servletPath + "?REQUEST_TYPE=LOGIN_REQUEST", m_servletPath + "?REQUEST_TYPE=LOG_OUT_REQUEST", m_servletPath); ... }
The following code, from xmlnews.admin.AdminServlet.performGeneration
and xmlnews.admin.AdminServlet.staticProcessingHtml
, shows how the application queries the database for news items in each available category and converts each result set to a XML document.
The database stores the XML for each category as a CLOB (Character Large OBject), so the application can handle very long lists.
public void performGeneration(String p_user, String p_genType, HttpServletResponse p_response) throws ServletException, IOException { ... try { String l_fileSep = System.getProperty("file.separator"); String l_message = ""; // Holds status message if (p_genType.equals("BATCH_GEN")) { // Batch Generation String l_htmlFile = "BatchGeneration"; String l_xslFile = "BatchGeneration"; String l_xmlFile = "BatchGeneration"; // Generate the XML and HTML content and save it in a file this.staticProcessingHtml( m_dynNewsEnv.m_dynNewsHome+l_fileSep+l_htmlFile+".html", m_dynNewsEnv.m_dynNewsHome+l_fileSep+m_dynNewsEnv.m_batchGenXSL, m_dynNewsEnv.m_dynNewsHome+l_fileSep+l_xmlFile+".xml" ); ... } ... }
The method xmlnews.admin.AdminServlet.staticProcessingHtml
defines and executes a query to fetch the news items. Then it uses the Oracle XML SQL Utility (XSU) to build an XML document from the result set and create an HTML page by applying an XSLT transformation.
public void staticProcessingHtml(String p_htmlFile,String p_xslfile, String p_xmlfile) throws Exception { String l_query = "select a.id, a.title, a.URL, a.DESCRIPTION, " + " to_char(a.ENTRY_DATE, 'DD-MON-YYYY'), a.CATEGORY_ID, b.name, a.SUB_CATEGORY_ID, c.name, a.Type_Id, d.name, " + " a.Submitted_By_Id, e.name, to_char(a.expiration_date, 'DD-MON-YYYY'), a.approved_flag " + " from news_items a, categories b, sub_categories c, types d, users e where " + " a.category_id is not null and a.sub_category_id is not null and "+ " a.type_id is not null and a.EXPIRATION_DATE is not null and "+ " a.category_id = b.id AND a.SUB_CATEGORY_ID = c.id AND a.Type_ID = d.id AND " + " a.SUBMITTED_BY_ID = e.id AND "+ " a.EXPIRATION_DATE > SYSDATE AND "+ " a.APPROVED_FLAG = \'A\' ORDER BY b.name, c.name "; Statement l_stmt = m_connection.createStatement(); ResultSet l_result = l_stmt.executeQuery(l_query); // Construct the XML Document using Oracle XML SQL Utility XMLDocument l_xmlDocument = m_xmlHandler.constructXMLDoc(l_result); l_stmt.close(); // Get the HTML String by applying corresponding XSL to XML. String l_htmlString = m_xmlHandler.applyXSLtoXML(l_xmlDocument,p_xslfile); File l_file = new File(p_htmlFile); FileOutputStream l_fileout = new FileOutputStream(l_file); FileOutputStream l_xmlfileout = new FileOutputStream(new File(p_xmlfile));. l_fileout.write(l_htmlString.getBytes()); l_xmlDocument.print(l_xmlfileout); l_fileout.close(); l_xmlfileout.close(); }
The final step in personalizing content is converting XML documents into HTML pages according to end-user preferences.
The following code comes from xmlnews.generation.SemiDynamicGenerate.dynamicProcessing
.
It retrieves the CLOBs corresponding to categories chosen by the user, converts each CLOB to an XML document, then combines them into one XML document. The process of converting the XML document to an HTML page is described in the next section.
public XMLDocument semiDynamicProcessingXML(Connection p_conn, UserPreference p_ prefs) throws Exception { String l_htmlString = null ; XMLDocument l_combinedXMLDocument = null ; XMLDocument[] l_XMLArray = new XMLDocument[p_prefs.m_categories.size()]; int l_arrayIndex = 0 ; PreparedStatement l_selectStmt = p_conn.prepareStatement( " SELECT PREGEN_XML FROM CATEGORIES_CLOB WHERE CATEGORY_ID = ?"); // Process each preference. for ( ; l_arrayIndex < p_prefs.m_categories.size(); ++l_arrayIndex ){ l_selectStmt.setString(1, p_prefs.m_categories.elementAt(l_ arrayIndex).toString()); OracleResultSet l_selectRst = (OracleResultSet)l_ selectStmt.executeQuery(); if (l_selectRst.next()) { CLOB l_clob = l_selectRst.getCLOB(1); l_XMLArray[l_arrayIndex] = convertFileToXML(l_clob.getAsciiStream()); } else l_XMLArray[l_arrayIndex] = null ; } l_selectStmt.close(); XMLDocHandler l_xmlHandler = new XMLDocHandler(); l_combinedXMLDocument = l_xmlHandler.combineXMLDocunemts(l_XMLArray ); return l_combinedXMLDocument ; }
After fetching news items from the database, Dynamic News converts them to XML documents. XML separates content from presentation, making it easy to build custom HTML pages.
Dynamic News uses different XSL stylesheets to convert XML documents into HTML pages customized for various browsers:
It's a four-step process:
Each time it receives an HTTP request, the application inspects the user-agent header to find out what kind of browser made the request. The following lines from xmlnews.dynamic.DynamicServlet.service show how the servlet creates a RequestHandler object (implemented in xmlnews/common/RequestHandler.java) and parses the request to get the browser type. Then the servlet uses this information to return an HTML page based on the end-user's preferences and browser type.
public void service(HttpServletRequest p_request, HttpServletResponse p_ response) throws ServletException { ... // Instantiate a Request Handler (declared elsewhere) m_reqHandler = new RequestHandler(m_userPreference, m_general,m_ status); RequestParams l_reqParams = m_reqHandler.parseRequest(p_request, m_ connection); String l_browserType = l_reqParams.m_browserType; ... // Display the Dynamic Page this.sendDynamicPage(l_browserType,p_response,l_userName,m_ userPreference, m_servletPath+"?REQUEST_TYPE=SET_ADVANCED_USER_ PREFS", m_servletPath+"?REQUEST_TYPE=LOGIN_REQUEST", m_servletPath+"?REQUEST_TYPE=LOG_OUT_REQUEST", m_servletPath); ... }
The code that actually extracts the browser type from the user-agent header resides in xmlnews.common.GenUtility.getBrowserType
, which follows:
public String getBrowserType(HttpServletRequest p_request) throws Exception { // Get all the Header Names associated with the Request Enumeration l_enum = p_request.getHeaderNames(); String l_Version = null; String l_browValue = null; String l_browserType = null; while (l_enum.hasMoreElements()) { String l_name = (String)l_enum.nextElement(); if (l_name.equalsIgnoreCase("user-agent")) l_browValue = p_request.getHeader(l_name); } // If the value contains a String "MSIE" then it is Internet Explorer if (l_browValue.indexOf("MSIE") > 0 ) { StringTokenizer l_st = new StringTokenizer(l_browValue, ";"); // Parse the Header to get the browser version. l_browserType = "IE"; while (l_st.hasMoreTokens()) { String l_tempStr = l_st.nextToken(); if (l_tempStr.indexOf("MSIE") > 0 ) { StringTokenizer l_st1 = new StringTokenizer(l_tempStr, " "); l_st1.nextToken(); l_Version = l_st1.nextToken(); } } // If the value contains a String "en" then it is Netscape } else if (l_browValue.indexOf("en") > 0) { l_browserType = "NET"; String l_tVersion = l_browValue.substring(8); int l_tempInd = l_tVersion.indexOf("["); l_Version = l_tVersion.substring(0, l_tempInd); } // Return the Browser Type and Version after concatenating return l_browserType + l_Version; }
After getting the end-user's browser type, the DynamicServlet's service method passes it to xmlnews.dynamic.DynamicServlet.sendDynamicPage
.
This method generates HTML by fetching XML documents from the database and converting them to HTML by applying an XSL stylesheet appropriate for the end-user's browser type.
public void sendDynamicPage(String p_browserType,HttpServletResponse p_response, String p_userName,UserPreference p_pref,String p_userPrefURL, String p_signOnURL,String p_logout, String p_servletPath) throws Exception { String l_finalHTML = ""; // Holds the html if (p_browserType.startsWith("IE4") || (p_browserType.startsWith("IE5"))) { // Send the XML and XSL as parameters to get the HTML string. l_finalHTML = m_handler.applyXSLtoXML( this.dynamicProcessingXML(m_connection, p_pref), m_dyEnv.m_dynNewsHome + "/DynamicIE.xsl" ); String l_thisbit = m_general.postProcessing(l_finalHTML,p_userName, p_userPrefURL,p_signOnURL,p_logout,p_servletPath); PrintWriter l_output = p_response.getWriter(); l_output.print(l_thisbit); l_output.close(); } else if (p_browserType.startsWith("NET4") || (p_browserType.startsWith("NET5"))) { // Do the same thing, but apply the stylesheet "/DynamicNS.xsl" ... // When the Browser is other than IE or Netscape. } else { // Do the same thing, but apply the stylesheet "/Dynamic.xsl" ... } }
The key methods are:
xmlnews.dynamic.DynamicServlet.dynamicProcessingXML
This queries the database for news items that match the end-user's preferences. It converts the result set into an XML document by calling xmlnews.common.XMLDocHandler.constructXMLDoc
.
xmlnews.common.XMLDocHandler.applyXSLtoXML
This converts an XML document into HTML using a specified XSL stylesheet. It uses XSL Transformation capabilities of Oracle XML Parser Version 2.0. More specifically, it uses the Document Object Model (DOM) parser to create a tree that represents the structure of the XML document. To build the final HTML string, it creates an element to serve as the root of the tree, then appends the parsed DOM document.
Dynamic News can also import and export XML documents that conform to the Resource description framework Site Summary (RSS) standard. Developed by Netscape as a way to share data channels, RSS is used at Web sites such as my.netscape.com and slashdot.org.
An application can use RSS to syndicate its news pages (making them available to RSS hosts) and to aggregate news from other RSS sites. For example, Dynamic News includes the xmlnews.admin.RSSHandler
class. It uses a specified DTD to parse and extract news items from a specified file, and then it stores the items in a hashtable. The class also provides a method that returns the elements in that hashtable.
|
Copyright © 1996-2001, Oracle Corporation. All Rights Reserved. |
|