API для JSP-страниц
Многочисленные попытки разработать системы, позволяющие включать в статический контекст HTML-страницы динамические данные с помощью специальных тегов, встроенных в код HTML, основаны на следующей идее: при передаче страницы специальный процессор распознает эти теги и использует их для того, чтобы динамически вставить данные по мере передачи страницы. Часто тип файла, содержащего такую страницу, обозначается специальным образом, чтобы указать web-серверу на необходимость специальной обработки данного файла.
В качестве очень удачных примеров реализации такой системы можно назвать сервер Cold-Fusion (www.allaire.com) и страницу ASP (Active Server Pages) компании Microsoft (http://msdn.microsoft.com/workshop/server/default.asp).
Компания Sun для динамического генерирования web-страниц использует технологию JavaServer Pages QSP). Версия JSP 1.1 (на момент написания книги) входит в J2EE и играет важную роль при написании серверов web-приложений на языке Java.
Технология JSP основана на технологии сервлетов. По сути, процессор JSP преобразует статические элементы web-страниц и динамические элементы, определенные тегами JSP, в исходный код Java для класса сервлетов. Когда на web- сервер поступает запрос, адресованный JSP-странице, для создания ответа выполняется этот класс сервлетов. До тех пор пока статические элементы страницы не изменятся, создание ответа на запрос происходит очень быстро, потому что класс остается в памяти.
Одна из ведущих компаний-производителей программного обеспечения недавно провела тест на производительность для приложений ASP и аналогичных приложений JSP. Результаты теста показали, что реализация приложения на основе Orion JSP гораздо более быстродействующая, чем ASP-реализация. Постоянно обновляемые результаты тестов публикуются по адресу www.orionservr.com/ benchmarks/benchmark.html.
API для объектной модели документа
В этой книге мы в основном будем использовать набор инструментальных средств JAXP (Java API for XML Parsing — интерфейс прикладных программ Java для анализа XML) компании Sun. Основной интерфейс API для манипулирования фрагментами документов XML согласован с формальной спецификацией DOM (Document Object Model — объектная модель документа) Консорциума W3C. Этот интерфейс API дает наиболее полный доступ ко всем элементам документа XML, с чем связана его сложность по сравнению с другими интерфейсами API. Существуют более простые интерфейсы API, поддерживающие DOM, но версия Консорциума W3C является наиболее распространенной.
Текущую версию набора инструментальных средств JAXP вы можете загрузить с web-сайта компании Sun или использовать версию, представленную на сайте Tomcat по адресу jakarta.apache.org. Этот набор состоит из пакетов Java, которые представляют WSC-версию интерфейса API, и пакетов, реализующих различные анализаторы и служебные программы. На момент написания книги этот набор не входил в стандартную библиотеку расширений Java, и его придется загрузить с сайта разработчиков по адресу http://developer.java.sun.com/developer/ products/xml.
API для сервлетов Java
Технология сервлетов Java используется на самом базовом уровне web-сервера и важна для всех Java-функций сервера. Технология сервлетов подходит для выполнения вообще всех функций web-сервера, а серверы, построенные на других технологиях, могут быть сконфигурированы так, чтобы перенаправлять определенные типы запросов специальным расширениям, предназначенным для обработки сервлетов (процессорам сервлетов).
ПРИМЕЧАНИЕ
Полноценное описание API для сервлетов Java требует отдельной книги. В этом разделе мы приводим только краткий обзор по данной тематике.
На момент написания книги текущей версией API для сервлетов Java является версия 2.2, а версия 2.3 находится в стадии тестирования. Ко времени издания этой книги версия API 2.2, вероятно, будет уже широко поддерживаться специализированными web-серверами, а на существующих web-серверах будут установлены соответствующие расширения.
Если web-сервер снабжен расширением для обработки сервлетов, то соответствующие настройки конфигурации этого сервера позволяют определить, какие запросы должны обрабатываться сервлетами. На сайте компании Sun Microsystems по адресу http://java.sun.com/products/servlet/industry.html представлен список доступных расширений для сервлетов и специализированных web- серверов.
Ниже перечислены web-серверы, соответствующие критерию 100% Pure Java.
Tomcat. Открытый проект Apache Software Foundation (http://jakarta.apache.org).
Enhydra. Недорогой коммерческий сервер приложений, написанный на Java (www.lutris.com) и поддерживающий технологии сервлетов JavaServer Pages и Enterprise JavaBeans.
Orion. Коммерческий сервер приложений (www.ononserver.com), поддерживающий все последние технологии Java, включая Е2ЕЕ и Enterprise JavaBeans.
Resin. Этот сервер (www.caucho.com/index.xtp), соответствующий критерию 100% Pure Java, задуман как сервер приложений масштаба предприятия и специализируется на использовании XML и XSL.
Согласно терминологии Sun, web-сервер, обрабатывающий сервлеты Java, выполняет роль контейнера сервлетов (servlet container), так же как браузер играет роль контейнера апплетов. Контейнер сервлетов должен загружать и инициализировать требуемые классы и выполнять основные части транзакции HTTP. Контейнер сервлетов создает объект HttpServletRequest, который содержит удобное представление запроса пользователя, и объект HttpServl etResponse, который обеспечивает методы, необходимые для того, чтобы сервлет мог отослать ответ.
Контейнер сервлетов также создает поток (класс Thread), предназначенный для выполнения кода сервлета в ответ на запрос пользователя. Каждый запрос получает собственный поток, который независимо выполняет методы сервлета, причем обычно создается только один экземпляр сервлета. Это означает, что программистам следует быть очень внимательными при использовании переменных экземпляров.
Поскольку обычно экземпляр сервлета остается загруженным в память web- сервера в течение длительного времени, ответ на запрос, обращенный к сервлету, формируется очень быстро. Этот способ работает гораздо быстрее, чем технологии, в которых приходится порождать новый процесс и загружать приложение каждый раз, когда возникает запрос.
Форматирование описаний товаров
Пытаясь решить, как должны выглядеть описания товаров, мы пришли к выводу, что критериями выбора способа представления должны являться:
гибкость стилей — существенным требованием является отделение внешних аспектов стиля (таких, как цвет текста и размер шрифта) от кода, создающего изображение;
гибкость содержимого — нужен метод, позволяющий web-дизайнеру страницы выбирать содержимое любой части документа XML (каталога товаров), не меняя при этом классы Java.
Генерирование ответа сервлетом
Все ресурсы, необходимые для контроля за созданием ответа, содержатся в интерфейсах ServletResponse и HttpServletResponse. Например, приведенные ниже обращения к методу setHeader можно использовать для того, чтобы запретить браузеру кэшировать посылаемую ему страницу:
response. setHeader("Expires", "Mon, ,26 Jul 1990 05:00:00 GMT");
response.setHeader("Cache-Control" , "no-cache, must-revalidate");
response.setHeader("Pragma", "no-cache"); // для HTTP 1.0
Объект ServletResponse предоставляет программе-сервлету выходной поток, в который будет записано содержимое посылаемой страницы. Этот поток может быть типа PrintWriter, и тогда возможно преобразование содержимого в формат Unicode, или типа ServletOutputStream, то есть поток простых двоичных данных, когда преобразование не происходит.
Гибкость содержимого
Чтобы добиться гибкости внутреннего содержимого страницы, мы собираемся использовать форматирующий класс с именем ProductFormatter Этот класс выдает данные элемента XML product в формате, задаваемом с помощью списка имен полей, которому сопоставлен список стилей, применяемых к тексту каждого поля.
В качестве простого примера рассмотрим следующую ситуацию: для каждого товара требуется отобразить на странице его название в формате ch3 и цену в формате ch4. Для этого мы определяем два массива типа String:
Stnng[] elem = { "prname", "price" };
String[] shortSt = { "ch3", "ch4"
Также мы хотим, чтобы имя каждого товара было представлено в виде ссылки, щелчок на которой вызывает отображение полной информации о данном товаре. Для этого строковой переменной alink присваивается соответствующее значение, наподобие следующего:
"http://localhost/servlet/cattest?action=showproduct"
Также нам надо определить целочисленную переменную типа int с именем linkN, содержащую индекс (номер) поля, которое должно стать ссылкой. В нашем случае linkN0, так как такой ссылкой должно быть имя элемента (название товара). Когда эти параметры установлены, метод doOutput, приведенный в листинге 3.11, может форматировать данные для конкретного товара (элемента product), содержащиеся в документе XML catalog.xml. В результате выполнения метода doOutput получается строка, которую уже можно вставлять в HTML-страницу.
Листинг 3.11. Метод doOutput (productFormatter.java)
public String doOutput( Element el ){
StringBuffer sb = new StringBuffer( );
String pid = null ;
if( aLink != null ){
pid = "&id=" + el.getAttribute("id") ;
System.out.println("pid is " + pid );
}
else { System.out.println("aLink null");
}
for( int i = 0 ; i < elem.length ; i++ ){
if( i == linkN && pid != null ){
sb.append( "<a class=\"" );
sb.append( style[i] );
sb.append("\" href=\"");
sb.append( aLink );
sb.append( pid );
sb.append("\">");
addText( sb, elem[i], el );
sb.append( " </a>");
}
else {
sb.append( "<span class=\"");
sb.append( style[i] ); sb.append("\">");
addText( sb, elem[i], el );
sb.append( " </span>");
}
}
return sb.toString();
}
Например, для элемента product, данные о котором приведены в листинге 3.12, в результате выполнения метода doOutput получится следующая строка:
<а class="ch3" href= "http //localhost/servlet/cattest?action=showproduct">
Guide to Plants </a> <span class="ch4">price ea = $12 99 </span>
Листинг 3.12. Описание отдельного товара (элемента product) из каталога catalog.xml
<product id="bk0022" keywords='gardening, plants">
<name>Guide to Plants</name>
<description>
<paragraph>
<italics>Everything</italics>
you've ever wanted to know about plants.
</paragraph>
</description>
<price>$12 99</price>
<quantity_in_stock>4</quantity_in_stock>
<image format="gif" width="234" height="400" src="images/covers/plantsvl gif">
<caption>
<paragraph>This is the cover from the first edition </paragraph>
</caption>
</image>
<onsale_date>
<month>4</month>
<day_of_month>4</day_of_month>
<year>1999</year>
</onsale_date>
<shipping_infо type="UPS" value="l.0" />
</product>
До сих пор мы занимались форматированием данных для одного товара Теперь посмотрим, как создать на странице список товаров со ссылками на их полные описания В классе CatalogBean имеется массив ссылок на элементы с именем selected Метод setlmtialSelected (листинг 3 13) устанавливает, что будет содержаться в этом массиве — либо полный список всех товаров, либо список товаров какой-либо одной серии
Листинг 3.13. Метод setlmtialSelected из CatalogBean (CatalogBean.java)
public boolean setInitialSelect(String s){
boolean ret = false ;
if( s.equals("all") ){
selected = cat.getAllProduct(); ret = true ;
}
else {
selected = cat.getProductsByPL( s );
if( selected != null ) ret = true ;
else {
System.out.println("not working yet");
}
}
return ret ;
}
public String doOutput( int n ){
return pf.doOutput( selected[n] );
}
В классе Catal ogBean имеется также метод doOutput, который просто вызывает метод doOutput класса ProductFormatter Элемент (товар), к которому применяется последний метод, указывается как n-й элемент массива selected:
public String doOutput( int n ){
return pf doOutput( selected[n] );
}
Теперь мы можем объединить все написанные нами компоненты для создания форматированной HTML-страницы, отображающей весь каталог. В листинге 3 14 приведен метод сервлета doPost, который устанавливает заголовок страницы, затем создает теги <head> и <title>, за которыми следует строка, содержащая тег <link> для связывания HTML-страницы с таблицей стилей. Затем следует тег <boby> и вызывается метод completeCatalog Далее пишутся закрывающие теги и закрывается выходной поток PnntWnter
Листинг 3.14. Метод doPost сервлета, отображающий весь каталог (CatalogTestServ.java)
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
resp.setContentType("text/html");
PrintWriter out = new PrintWriter(resp.getOutputStream());
String action = req.getParameter("action");
out.println("<html>");
out.println("<head><title>CatalogTestServ Output</title>");
out.println( cssLink );
out.println("</head>\r\n<body>");
try {
if( "showcatalog".equals( action )){
completeCatalog( out );
}
else if( "selectkeyword".equals( action )){
doKeywordSelect( out );
}
}catch( Exception e ){
e.printStackTrace( out );
}
out.println("</body>");
out.println("</html>");
out.close();
}
Как показано в листинге 3.15, метод completeCatalog использует теги HTML для создания таблицы с тремя столбцами. Каждый столбец заполняется информацией о товарах одной из серий, причем данные по каждому из товаров форматируются методом doOutput, приведенным в листинге 3.11.
Листинг 3.15. Метод completeCatalog для создания полного каталога товаров (CatalogTestServ.java)
public void completeCatalog( PrintWriter out ){
CatalogBean cb = new CatalogBean();
out.println("<h2>Complete Catalog</h2>");
out.println("<table width=\"90%\" border=\"3\" align=\"center\" >");
out.println("<thead><tr><th>Books</th><th>CDs</th> <th>Gadgets</th>" + "</tr></thead>");
out.println("<tbody><tr valign=\"top\"><td>");
String link = alias + "?action=showproduct" ;
cb.setInitialSelect("Books");
int ct = cb.getSelectedCount();
out.println("We have " + ct + " titles." + brcrlf );
cb.setOutput("short", link);
for( int i = 0 ; i < ct ; i++ ){
out.println( cb.doOutput(i) );
out.println( brcrlf );out.println( brcrlf );
}
out.println("</td><td>");
cb.setInitialSelect("CDs");
ct = cb.getSelectedCount();
out.println("We have " + ct + " CD titles." + brcrlf );
cb.setOutput("short", link);
for( int i = 0 ; i < ct ; i++ ){
out.println( cb.doOutput(i) );
out.println( brcrlf );out.println( brcrlf ); }
out.println("</td><td>");
cb.setInitialSelect("widgets");
ct = cb.getSelectedCount();
out.println("We have " + ct + " kinds." + brcrlf );
cb.setOutput("short", link );
for( int i = 0 ; i < ct ; i++ ){
out.println( cb.doOutput(i) );
out.println( brcrlf );out.println( brcrlf );
}
out.println("</td></tr></table>");
}
В листинге З 16 приводится текст первой части получившейся HTML-страницы Обратите внимание, что многие строки разбиты на несколько частей, чтобы поместиться на страницу книги Несмотря на использование таблицы стилей, экономящей память, вся страница полностью занимает 17 213 байт
Листинг 3.16. Первая часть генерируемой сервлетом HTML-страницы
<html>
<head><title>CatalogTestServ Output</title>
<link rel="stylesheet" href= "http //localhost/XmlEcommBook/catalog.css" type="text/css" media="screen" >
</head>
<body>
<h2>Complete Catalog</h2>
<table width="90%" border="3" align='center" >
<thead><tr><th>Books</th><th>CDs</th>
<th>Gadgets</th></tr></thead>
<tbodyxtr valign="top"><td>
We have 28 titles <br />
<a class="ch3" href= "http //Iocalhost/servlet/cattest?action=showproduct">
Guide to Plants </a> <span class= "ch4">price ea = $12.99 </span>
<br />
<br />
<a class="ch3" href= "http //localhost/servlet/cattest?action=showproduct">
Guide to Plants, Volume 2 </a> <span class= "ch4">price ea = $12.99 </span>
<br />
<br />
<a class="ch3" href= "http //Iocalhost/servlet/cattest?action=showproduct" >
The Genius's Guide to the 3rd Millenium </a>
<span class= "ch4'>price ea = $59.95 </span>
<br />
Конечный результат наших усилий — отображенная в браузере HTML-страница с каталогом товаров (рис 32)
![](image/gibkost-soderzhimogo_1.gif)
Рис. 3.2. Отображение каталога товаров
В следующей главе мы расскажем, как можно расширить функциональность классов CatalogBean и ProductFormatter для создания представлений, необходимых при работе с корзиной покупателя.
Гибкость стилей
Самым очевидным инструментом для достижения гибкости в применении различных стилей к документу являются каскадные таблицы стилей. С помощью CSS задаются параметры стиля различных компонентов web-страницы. В настоящее время CSS является наиболее широко поддерживаемым стандартом и входит в официальную часть спецификации HTML 4.
Помещая информацию о стиле в отдельный файл — так называемую таблицу стилей (style sheet), мы значительно уменьшаем объем текста, который приходится генерировать сервлету. Если для всех страниц на вашем сайте используется одна и та же таблица стилей, то web-браузер пользователя может кэшировать ее и таким образом уменьшить время ожидания ответа от сервера для всего сайта.
В листинге 3.10 приводится простая таблица стилей, задающая стиль тегов HTML <body>, <hl>, <h2> и <р> — четыре именованных стиля, которые мы будем использовать в следующем примере.
СОВЕТ
Превосходное пособие по применению таблиц стилей вы найдете на сайте www.htlmhepl.com/reference/ess.
Листинг 3.10. Пример таблицы стилей (catalog.css)
body{font-family:Arial font-size:10.0pt}
h1{font-size:30pt; font-family:Arial; color:red ;}
h2{font-size:20pt; font-family:Arial; color:navy; }
p {font-size:10pt; font-family:Arial, Helvetica; background-color:#fef6df ;}
.ch1{font-size:30pt; font-family:Arial; color:red ;}
.ch2{font-size:20pt; font-family:Arial; color:navy ;}
.ch3{font-size:15pt; font-family:Arial; color:purple ;}
.ch4{font-size:10pt; font-family:Arial; color:black ;}
Таблицу стилей можно присоединить к HTML-странице с помощью тега link, помещенного внутрь тега <head>, как показано в следующем примере:
<head><title>Catalog Test Servlet Output</title>
<link rel="stylesheet" href="http://localhost/XmlEcommBook/catalog.css"
type="text/css" media="screen" >
</head>
Присоединив к странице такую таблицу стилей, можно очень легко задавать стиль любого элемента. Для этого нужно просто добавить к тегу атрибут, например style = "ch2". Указанный таким образом стиль замещает стиль, задаваемый браузером по умолчанию для этого элемента.
Чтобы понять, насколько использование таблиц стилей эффективнее, можно сравнить два фрагмента кода HTML, выполняющих одну и ту же функцию.
Пример использования таблицы стилей:
<а class="ch3" href= "http://localhost/servlet/cattest?action=showproduct">
Guide to Plants </a>
<span class="ch4">price ea = $12.99 </span>
Пример непосредственного задания стилей:
<font face="Anal" SIZE="15pt" color="purple" >
<a class="ch3" href= "http://localhost/servlet/cattest?action=showproduct">
Guide to Plants
</a>
</font>
<font face="Anal" SIZE="10pt" color="black" > price ea = $12.99 </font>
В этой главе мы приводим
Представление XML- каталога в сети
В этой главе мы приводим обзор интерфейсов API (Application Programming Interface — интерфейс прикладных программ) для сервлетов Java, для JSP-страниц и для манипулирования элементами XML. Перечисленные инструментальные средства весьма существенны для создания динамических web-страниц на основе данных XML. Затем, используя эти интерфейсы API, мы рассмотрим различные подходы к представлению в сети каталога, созданного нами в главе 2.
Индексация товаров
Пользуясь приведенным выше кратким обзором интерфейсов Java, дающих доступ к объектной модели документа, мы можем исследовать вопрос о создании
различных структур данных, которые ускорят процесс поиска товаров по каталогу, оставляя в то же время информацию о товарах в формате DOM. Ниже перечислены некоторые структуры данных и функциональные возможности, которые нам нужны:
перечень категорий product_line (серий товаров) каталога;
перечень всех товаров каждой серии;
быстрый поиск информации о конкретном товаре (содержимого элемента product) по указанному идентификатору товара (id);
список всех используемых ключевых слов, который можно предоставить пользователю для поиска нужных ему товаров;
быстрый поиск товаров по выбранным ключевым словам.
Метод scanCatalog, как показано в листинге 3.6, создает структуры данных, удовлетворяющие этим требованиям. Эти структуры данных — массивы типа String с именами productLineNames и keywords и объекты Hashtable с именами productLi neHT, productHT и prodByKeyHT. Мы используем классы коллекций, совместимые как с пакетом JDK Java 1.1, так и с JDK Java 1.2, поскольку (на момент написания книги) некоторые процессоры сервлетов до сих пор используют библиотеки Java 1.1.
Метод scanCatal og вызывается сразу же после того, как конструктор этого класса (листинг 3.4) завершил разбор файла XML. Заметим, что в методе scanCatalog первый метод, примененный к корневому элементу (гЕ), — это метод normalize(). Причина этого заключается в том, что у анализатора Sun имеется свойство воспринимать символы возврата каретки и дополнительные пробелы в тексте (которые авторы, пишущие на XML, часто используют для того, чтобы документ было легче просматривать) так, что этот текст разбивается указанными символами на несколько узлов типа Text. Метод normalize объединяет содержимое всех примыкающих друг к другу узлов Text в один узел Text.
Листинг 3.6. Метод scanCatalog инициализирует различные объекты Hashtable (theCatalog.java)
public void scanCatalog(){
Element rE = catDoc.getDocumentElement();
// the root
rE.normalize();
productLineNL = rE.getElementsByTagName("product_line");
productLineHT = new Hashtable();
productHT = new Hashtable();
prodByKeyHT = new Hashtable();
// note that in contrast to other get methods, getAttributes
// returns "" if the attribute does not exist
int i,j, ct = productLineNL.getLength();
productLineNames = new String[ ct ];
for( i = 0 ; i < ct ; i++ ){
Element plE = (Element)productLineNL.item(i);
productLineNames[i] = plE.getAttribute("name");
NodeList prodNL = plE.getElementsByTagName("product");
productLineHT.put( productLineNames[i], prodNL ); // node list
int pct = prodNL.getLength();
System.out.println( productLineNames[i] + " ct " + pct );
for( j = 0 ; j < pct ; j++ ){
Element prodE = (Element)prodNL.item(j) ;
String id = prodE.getAttribute("id");
if( id == null ){
System.out.println("No id - productLine " + productLineNames[i] + " product " + j );
}
else { productHT.put( id, prodE );
// product by id
String keys = prodE.getAttribute("keywords");
if( keys != null ){
addProdByKey( keys, prodE );
}
}
}
}
// end loop over product lines
ct = prodByKeyHT.size();
keywords = new String[ ct ];
i = 0 ;
Enumeration en = prodByKeyHT.keys();
while( en.hasMoreElements()){
keywords[i++] = (String)en.nextElement();
}
shellSortStr( keywords );
}
Метод addProdByKey создает объект prodByKeyHT, как показано в листинге 3.7. Этот метод должен разрешить некое затруднение, связанное с тем, что строка keywds может содержать не одно, а несколько ключевых слов (или фраз), разделенных запятыми. Для решения этой задачи используется класс StringTokenizer, но обратите внимание, что после разбора строки с помощью метода StringTokenizer нужно использовать метод trim, который убирает лишние пробелы перед строкой ключевых слов и после нее. Объект Vector, в котором хранятся ссылки на элементы, сохраняет исходный порядок расположения товаров, то есть такой же, какой был в файле XML.
Листинг 3.7. Метод addProdByKey (TheCatalog.java)
// разбивает строку keywds на отдельные ключевые слова,
// затем создает вектор v или добавляет элемент рЕ к уже
// существующему вектору в prodByKeyHT
private void addProdByKey( String keywds, Element pE ){
StringTokenizer st = new StringTokenizer( keywds, ",");
while( st.hasMoreTokens() ){
String key = st.nextToken().trim();
Vector v = (Vector)prodByKeyHT.get( key );
if( v == null ){
v = new Vector();
prodByKeyHT.put( key, v );
}
v.addElement( pE );
}
}
Информация для представления каталога в сети
Вообще говоря, перед тем как каталог отображается в окне браузера пользователя, содержащаяся в нем информация проходит несколько этапов обработки. Основной элемент DOM, который фигурирует на этих этапах, — Element, представляющий отдельный товар. Этапы создания виртуального магазина на основе каталога XML следующие.
Берется объектная модель документа (каталога), представленная в виде списка узлов (NodeLi st), каждый из которых соответствует некоторому товару.
К списку узлов применяются правила отбора. Возможные правила отбора включают идентификатор товара (id), ключевое слово или название серии товаров (productjtine).
При необходимости массив элементов сортируется.
Для каждого элемента (товара) генерируется и добавляется к создаваемой странице код HTML, задающий формат представления данных об этом товаре.
Инициализация сервлета
Когда контейнер сервлета загружает код и создает экземпляр класса сервлета, API гарантирует, что первым будет вызван метод init и что он будет выполнен прежде, чем начнется обработка любых запросов пользователя. API сервлета обеспечивает передачу параметров инициализации вновь созданному экземпляру, используя объект класса ServletCongigClass.
До появления версии 2.2 API сервлетов каждый производитель использовал свой способ конфигурирования настроек инициализации. Теперь, когда компания Sun выбрала основанную на XML спецификацию, можно рассчитывать, что появится единая стандартная конфигурация.
Листинг 3.3 содержит документ XML, используемый для задания параметров инициализации сервлетов, которые мы будем обсуждать в главе 7.
Листинг 3.3. Задание параметров инициализации сервлетов (web.xml)
<web-app>
<servlet><servlet-name>cattest</servlet-name>
<servlet-class>com.XmlEcomBook.catalog.CatalogTestServ </servlet-class>
</servlet>
<servlet><servlet-name>catalog</servlet-name>
<servlet-class>com.XmlEcomBook.catalog.CatalogServ </servlet-class>
<init-param>
<param-name>workdir</param-name>
<param-value>e:\\scripts\\XMLgifts</param-value>
</init-param>
</servlet>
<servlet><servlet-name>Questionnaire</servlet-name>
<servlet-class>com.XmlEcomBook.Chap07.QuestionnaireServ </servlet-class>
<init-param>
<param-name>homedir</param-name>
<param-value>e:\\scripts\\questionnaire</param-value>
</init-param>
</servlet>
<servlet><servlet-name>Qanalysis</servlet-name>
<servlet-class>com.XmlEcomBook.Chap07.QanalysisServ </servlet-class>
<init-param>
<param-name>homedir</param-name>
<param-value>e:\\scripts\\questionnaire</param-value>
</init-param>
</servlet>
</web-app>
В методе init сервлета QuestionnaireServ параметр с именем homedir используется для того, чтобы задать значение Srting с именем homedir:
homedir = config.getlnitParameter("homedir");
Здесь config —это объект ServletConfig, переданный методу init или полученный из метода getServletConfigO. Помимо этого, метод init обычно устанавливает связь с базами данных и открывает файлы регистрации.
Интерфейс Document
Объект Java, который инкапсулирует весь документ XML, реализует расширение Document интерфейса Node. Большая часть методов этого интерфейса связана с созданием или модифицированием DOM в памяти. Тот метод, который мы будем использовать (getDocumentElement), просто возвращает ссылку на корневой элемент данного документа:
Element rootE = catDoc.getDocumentElement();
Например, чтобы получить список узлов (объект NodeLi st) всех элементов product в документе catDoc, следует использовать следующие методы:
Element rootE = catDoc.getDocumentElement();
NodeList nl = rootE.getElementsByTagName("product");
Интерфейс Node
Исходный набор методов для всего пакета org.w3c.dom обеспечивается интерфейсом Node. В этом пакете имеется 13 интерфейсов, производных от интерфейса Node, которые представляют различные части документа. Хотя все они являются расширением Node, определенные методы этого интерфейса в некоторых производных интерфейсах не имеют смысла. В табл. 3.8 перечислены методы интерфейса Node. Обратите внимание на то, что интерпретация возвращаемых значений nodeName и nodeValue зависит от типа узла [Node — узел. — Примеч. перев. ].
Таблица 3.8. Методы интерфейса Node
Метод |
Возвращаемое значение |
Описание | |||
getNodeName |
NodeName |
Возвращаемое значение — строка, представляющая имя Node; интерпретация зависит от типа узла | |||
getNodeVal ue |
NodeVal ue |
Возвращаемое значение — строка, представляющая значение узла; интерпретация зависит от типа узла | |||
setNodeValue |
Пустое множество | ||||
getNodeType |
Целочисленное значение типа short |
Возвращаемое число идентифицирует тип узла согласно определению в интерфейсе Node | |||
getParentNode |
Ссылка на узел |
Возвращается ссылка на узел, являющийся родительским по отношению к данному в иерархии DOM. Не для всех типов узлов существуют родительские узлы | |||
getChildNodes |
Ссылка на семейство узлов NodeList |
Объекты NodeList обеспечивают доступ к упорядоченному списку ссылок на узлы | |||
getFirstChild |
Ссылка на узел |
Первый дочерний узел для данного узла или null, если дочерние узлы отсутствуют | |||
getLastChild |
Ссылка на узел |
Последний дочерний узел для данного или null, если дочерние узлы отсутствуют | |||
getPrevlous Sibling |
Ссылка на узел |
Узел, непосредственно предшествующий данному, или null, если таковой отсутствует | |||
getNextSibling |
Ссылка на узел |
Узел, непосредственно следующий за данным, или null, если таковой отсутствует | |||
getAttributes |
Ссылка на семейство NamedNodeMap |
Методы NamedNodeMap обеспечивают доступ к атрибутам по имени. Возвращает null, если атрибуты отсутствуют | |||
getOwnerDocument |
Ссылка на документ |
Объект Document, которому принадлежит данный узел, или null, если этот узел сам является объектом Document |
Тип узла, с которым мы будем в основном иметь дело, называется Element; эти объекты используют интерфейс org.w3c.dom.Element. Интерфейс Element добавляет несколько методов для работы с атрибутами и именованными узлами, содержащимися в узле Element.
ПРИМЕЧАНИЕ
Для упрощения терминологии мы будем называть объекты, реализующие интерфейсы Node, Element и др., объектами Node, Element (узел, элемент) и др. соответственно. Фактический тип объектов, реализующих интерфейсы, не играет роли, так как мы будем использовать только методы интерфейсов.
Поскольку за недостатком места мы не можем предоставить формальное изложение API org.w3c.dom в том виде, в котором оно приводится на сайте консорциума W3C, исследуем по крайней мере, каким образом фрагмент каталога, соответствующий какому-то товару, представляется объектами Java. В листинге 3.5 показан код XML для одного товара; объект Element для этого кода будет содержать иерархию объектов Node, представляющих XML.
Листинг 3.5. Код XML для одного товара (catalog.xml)
<product id="bk0022" keywords="gardening, plants">
<name>Guide to Plants</name>
<description>
<paragraph>
<italics>Everything</italics>
you've ever wanted to know about plants.
</paragraph>
</description>
<price>$12.99</price>
<quanti ty_i n_stock>4</quanti ty_in_stock>
<image format="gif" width="234" height="468"
src=»images/covers/plants.gif»>
<caption>
<paragraph>This is the cover from the first edition.</paragraph>
</caption>
</image>
<onsale_date>
<month>4</month>
<day_of_month>4</day_of_month>
<year>1999</year>
</onsale_date>
</product>
Например, если вы выполните метод getFirstChild элемента product, то получите ссылку на узел, представляющий элемент name. Элемент name содержит дочерний узел типа Text, а значением этого узла является строка Guide to Plants (справочник по растениям).
Доступ к XML-атрибутам элемента product осуществляется с помощью метода getAttribute, который по имени атрибута возвращает строку — значение атрибута, как в следующем примере:
String id = product.getAttribute("id")
String keywords = product.getAttribute("keywords");
Доступ к узлам первого по отношению к элементу product уровня иерархии осуществляется с помощью метода getChil dNodes. Этот метод возвращает объект, реализующий интерфейс NodeLi st. Объект NodeLi st отличается от других тем, что он содержит динамическое представление документа XML. Это значит, что если в иерархию узлов элемента product будет встроен какой-либо новый узел, это изменение автоматически отразится в объекте NodeLi st (списке узлов XML).
Интерфейс NodeList
В этом интерфейсе имеются только два метода:
int getLength() — возвращает текущее количество узлов, которое может быть равным нулю;
Node item ( int n ) — возвращает ссылку на n-й узел в списке или null, если данная позиция списка пуста.
Язык тегов JSP
В приведенном ниже коде JSP-страницы теги JSP начинаются с символов <*= и заканчиваются символами %>. После компиляции в Java-класс запрос, обращенный к этой JSP-странице, выдаст обычный статический текст HTML-страницы, куда будет вставлена динамически сгенерированная строка, созданная с помощью метода toStnng, примененного к новому объекту Date:
<HTML>
<HEAD><TITLE>JRun Date Demo</TITLE></HEAD>
<BODY>
<H2>Date And Time <%= new java util Date().toString() %></H2>
<hr>
</BODY>
</HTML>
Из-за больших различий между интерфейсами API для ранних версий JSP и для текущей версии 1.1 на данный момент существуют два стиля написания тегов JSP. Старый стиль пока применяется наряду с новым.
Таблица 3.5. Теги JSP, использующие символы <% (старый стиль)
Тег |
Назначение |
Пример | |||
<%-- --%> |
Комментарии |
<%--это комментарий--%> | |||
<%= %> |
Выражения (вычисляемые как объекты класса String) |
<%= new Date() %> | |||
<%! %> |
Объявления |
<%! Date myD = new Date(): %> | |||
<% %> |
Фрагменты кода |
<%for( int i = 0 : i < 10 ; i++ { %> | |||
<%@ %> |
Директивы |
<%@ page imprt="java.util.*" %> |
Как показано в табл. 3.6, новый стиль тегов JSP согласован с правилами форматирования, принятыми в XML. Вообще говоря, политика компании Sun в этом отношении сводится к тому, чтобы страницы JavaServer Pages соответствовали правилам языка XML.
Таблица 3.6. Теги JSP, согласованные с правилами XML (новый стиль)
Тег JSP |
Описание | ||
<jsp: include /> |
Включает в страницу текст из указанного файла | ||
<jsp: forward /> |
Переадресует запрос сервлету, другой JSP-страницеили статической web-странице | ||
<jsp:param /> |
Используется внутри тегов forward, include и plugin для добавления или модифицирования параметров в объекте request | ||
<jsp:getProperty /> |
Выдает значение свойства bean-компонента по его имени | ||
<jsp:setProperty /> |
Задает значения свойств bean-компонентов | ||
<jsp:useBean /> |
Создает или отыскивает bean-компонент с указанным именем и областью видимости | ||
<jsp:plugm /> |
Предоставляет полную информацию для загрузки подключаемых модулей Java (Java Plug-In) в web-браузер клиента |
Классы и интерфейсы для сервлетов Java
Пакеты javax.servlet и javax.servlet.http содержат классы и интерфейсы, используемые при создании сервлетов. Пакет javax.servlet в основном содержит обобщенные классы и интерфейсы, в то время как классы пакета javax.servlet.http специализированы для работы с протоколом HTTP. В табл. 3.1 перечислены интерфейсы пакета javax.servlet.
Таблица 3.1. Интерфейсы пакета javax.setvlet
Интерфейс |
Описание | ||
Servlet | Этот интерфейс определяет методы, которые должны быть реализованы в каждом сервлете. Интерфейс Servlet реализуется классом GenericServlet | ||
Servl etRequest | Доступ ко всей информации о запросе клиента осуществляется через объект, реализующий этот интерфейс. За создание объекта ServletRequest отвечает процессор сервлетов | ||
Serl etResponse | Объекты, реализующие этот интерфейс, создаются процессором сервлетов и передаются методу service сервлета для формирования ответа клиенту | ||
RequestDispatcher | Этот интерфейс позволяет переадресовать запрос от текущего сервлета к другому сервлету или JSP-странице для дальнейшей обработки запроса | ||
SerletConfng | Объекты, использующие этот интерфейс, применяются для хранения информации, которая помогает конфигурировать сервлет во время его инициализации | ||
Servl etContext | Объекты, использующие этот интерфейс, позволяют сервлету получать информацию о процессоре сервлетов и об окружении сервлета | ||
SingleThreadModel | В этом интерфейсе не содержится методов. Он используется для того, чтобы предотвратить одновременный доступ нескольких потоков к одному экземпляру сервлета. Процессор сервлетов выполняет это требование либо путем ограничения доступа и организации очереди запросов, либо путем создания отдельного экземпляра сервлета для каждого потока |
Классы пакета javax.servlet (табл. 3.2) обеспечивают только основной минимум необходимой функциональности. Вообще говоря, обычно программисты работают с классами, расширяющими классы этого пакета и предназначенными для более специализированных применений.
Таблица 3.2. Классы пакета javax.servlet
Класс |
Описание |
GenericServlet | Этот класс обеспечивает минимально необходимую функциональность |
ServletInputStream | Класс для чтения потока двоичных данных из запроса |
ServletOutputStream | Класс для записи потока двоичных данных, входящих в ответ |
Пакет javax.servlet.http добавляет интерфейсы, перечисленные в табл. 3.3, и классы, перечисленные в табл. 3.4. Это те интерфейсы и классы, с которыми вам как программисту придется работать при создании web-приложения с сервлетами.
Таблица 3.3. Интерфейсы пакета javax.servlet.http
Интерфейс |
Описание |
HttpServletRequest | Это расширение интерфейса ServletRequest добавляет методы, специфические для запросов HTTP, например getCookies, который возвращает содержимое заголовка Cookie |
HttpServletResponse | Это расширение интерфейса ServletResponse добавляет методы, специфические для протокола HTTP, например setHeader, который задает заголовки HTTP-ответов |
HttpSession | Объекты, реализующие этот интерфейс, составляют существенную часть приложения «корзина покупателя», так как они позволяют программисту хранить информацию о пользователе в промежутках между посещениями страницы или между транзакциями |
HttpSessionBinding Listener | Объекты, реализующие этот интерфейс, могут получить автоматические уведомление, когда они присоединяются к интерфейсу HttpSession или отсоединяются от него |
Таблица 3.4. Классы пакета javax.servlet.http
Класс |
Описание |
HttpServlet | Это абстрактный класс, расширениями которого являются все используемые web-сервлеты |
Cookie | Эти объекты используются, чтобы манипулировать информацией, которая содержится в файлах cookie и которая посылается сервером на браузер и возвращается при последующих запросах. Эта информация записывается в объект Cookie с помощью методов интерфейса HttpServletRequest |
HttpUtils | Статические методы этого класса оказываются полезными в различных ситуациях |
HttpSessionBinDingEvent | Класс событий, адресуемых объектам, которые реализуют интерфейс HttpSessionBindmgListener |
Обработка запросов JSP
Ниже описана последовательность событий, происходящих при обработке запроса, обращенного к JSP.
Запрос, обращенный к JSP, направляется web-сервером к процессору JSP (JSP engine).
Процессор JSP отыскивает соответствующий сервлет, основываясь на имени страницы. Если сервлет существует, то запрос передается методу сервлета _jspService с помощью объектов HttpServl etRequest и HttpServl etResponse, как это происходит с обычным сервлетом.
Если исходная страница изменилась или еще не была скомпилирована, то компилятор обрабатывает исходный код и создает эквивалентный исходный код Java для сервлета, реализующего интерфейс HttpJspPage.
Затем код компилируется и выполняется новый сервлет. Сервлет может оставаться в памяти, что позволяет очень быстро отвечать на следующий запрос.
Объекты request и response — это в точности те же самые объекты, которые используются в обычных сервлетах; разница заключается только в том, что метод service создается процессором JSP.
Обработка запросов сервлетами
В обычных коммерческих приложениях запросы пользователей обрабатываются в следующей последовательности.
Данные, передаваемые в запросе, используются для создания объекта HttpServletRequest, который содержит информацию из заголовка запроса и всю остальную дополнительную информацию. Также создается объект HttpServletResponse в ходе подготовки к созданию ответа на запрос.
Вызывается метод service сервлета со ссылками на указанные два объекта. На основании типа запроса принимается решение, какой из методов обработки запроса следует вызвать в данном случае. Специализированные сер- влеты обычно не отменяют метод service, но могут заменить методы doGet и/или doPost.
Метод doGet или doPost исследует запрос и определяет, какие действия должно осуществить приложение. Во всех приложениях, за исключением самых простых, сервлеты обычно используют другие объекты для выполнения запросов к базам данных или для вычислений.
Организация каталога
Каталог товаров, созданный нами в виде документа XML, организован последовательно. Однако мы хотели бы, чтобы пользователи имели возможность более гибкого доступа к элементам каталога, то есть чтобы им не приходилось просматривать страницу за страницей в поисках нужного товара. В этом разделе мы рассмотрим технологии Java, позволяющие организовать такой гибкий доступ к элементам каталога.
Организация поиска по ключевым словам
Поскольку мы уже проделали некоторую работу по кодированию ключевых слов для каждого товара в каталоге, мы, конечно, хотели бы, чтобы пользователю было удобно работать с этими ключевыми словами. Напомним, что, как сказано в предыдущем разделе, методы scanCatalog и addProdByKey создают массив ключевых слов (объектов типа String), а также поддерживают объект Hashtable, содержащий объект Vector, в котором хранятся ссылки на элементы Element, снабженные ключевыми словами.
Можно было бы, конечно, организовать поиск таким образом: пользователь набирает какое-либо слово, характеризующее искомый товар, в специальной форме на HTML-странице, а потом осуществляется проверка, содержится ли оно в списке ключевых слов. Но мы организуем поиск иначе, с помощью раскрывающегося списка. Для этого используется тег HTML SELECT и список всех ключевых слов и фраз. Получившаяся страница представлена на рис. 3.1.
Код HTML этой страницы (с небольшим количеством ключевых слов) представлен в листинге 3.8. Заметим, что помимо перечня ключевых слов мы включили в код скрытую переменную с именем action и значением keywordsearch.
Листинг 3.8. Код HTML для создания раскрывающегося списка ключевых слов
<center><h2>Select a KeyWord</h2>
<form raethod="POST" action="http://localhost/servlet/cattest" >
<input type="HIDDEN" name="action" value="keywdsearch" >
<select name="keyword" size="8">
<option value="animals" > animals
<option value="appliance" > appliance
<option value="area codes" > area codes
<option value="art" > art
<option value="aviation" > aviation
<option value="barbecue" > barbecue
<option value="basebaU" > baseball <option value="beer" > beer
<option value="writing" > writing
</select>
<input type="SUBMIT" value="Search" >
</form>
</center><hr>
![](image/organizacija-poiska-po-kljuchevym-slovam_1.gif)
Рис. З.1. Форма для выбора ключевого слова
Чтобы инкапсулировать функции форматирования каталога в один класс, который можно было бы использовать как с сервлетами, так и с JSP-страницами, мы написали класс CatalogBean. Включив как можно больше форматирующих функций в CatalogBean, мы значительно упростим код сервлета. В сервлет достаточно включить следующий код для генерации раскрывающегося списка ключевых слов:
public void doKeywordSelect( PrintWriter out ){
CatalogBean cb = new CatalogBeanO;
cb.setHidden( "action","keywdsearch");
out.println("<center><h2>Select a KeyWord</h2>");
out.print( cb.doKeywordSelect( alias ) );
out.println("</center><hr>") ; }
Вызывая метод setHidden, мы тем самым сообщаем классу CatalogBean, что при форматировании всех списков нужно добавить тег скрытой переменной. В данном случае получится следующий результат:
<input type= "HIDDEN" name= "action" value = "keywdsearch">
За счет того что метод doKeywordSelect возвращает String, в то время как этому методу передаются данные типа PrintWriter, мы избегаем необходимости связывать метод doKeywordSel ect с каким-либо конкретным типом выходного потока. Как показано в листинге 3.9, мы используем класс StringBuffer для создания всего текста раскрывающегося списка товаров. Заметим, что при обращении к методу getKeywords выдает массив Stri ng, созданный методом scanCatal og (листинг 3.6).
Листинг 3.9. Метод, форматирующий строковый массив в раскрывающийся список (Catalog Bean.java)
public String doKeywordSelect(String alias ){
StringBuffer sb = new StringBuffer( "<form method=\"POST\" action=\"" );
sb.append( alias ); sb.append("\" >\r\n");
String[] kwd = getKeywords();
int i ;
int ct = hiddenNames.size();
if( ct > 0 ){
for( i = 0; i < ct ; i++ ){
sb.append("<input type=\"HIDDEN\" name=\"");
sb.append( hiddenNames.elementAt(i) );
sb.append("\" value=\"");
sb.append( hiddenVals.elementAt(i) );
sb.append( "\" >\r\n");
}
}
sb.append("<select name=\"keyword\" size=\"8\">" );
for( i = 0 ; i < kwd.length ; i ++ ){
sb.append("<option value=\"" );
sb.append( kwd[i] );
sb.append( "\" > " );
sb.append( kwd[i] ); sb.append("\r\n");
}
sb.append("</select>\r\n");
sb.append("<input type=\"SUBMIT\" value=\"Search\" >\r\n" );
sb.append("</form>\r\n" );
return sb.toString();
}
Ответ web-сервера
Ответное сообщение web-сервера на запрос браузера также обязательно содержит заголовок. Заголовок начинается со строки состояния, в которой указан используемый протокол, численный код состояния и текстовая версия кода состояния. В следующих строках приводится дополнительная информация в формате ключевое слово: значение, затем следует одна пустая строка.
В заголовке ответа обычно указываются тип и размер содержимого тела сообщения. Заголовок ответа также может содержать строки, которые устанавливают в браузере значения элементов cookie. Ниже приведен заголовок, полученный в ответ на запрос, показанный в листинге 3.1. После этого заголовка идет пустая строка, а затем — тело сообщения, содержащее код HTML:
НТТР/1.0 200 ОК
Server: Microsoft-PWS/2.0
Date. Mon, 25 Sep 2000 14:15:55 GMT
Content-Type, text/html
Тело ответного сообщения сервера может быть каким угодно, от стандартной HTML-страницы до двоичных данных закодированного изображения (а также совокупностью фрагментов данных, представленных в любых других специализированных форматах). Заметим, что в предыдущем заголовке тип содержимого был указан как text/html.
СОВЕТ
Для более детального изучения HTML мы советуем обратиться на сайт www.piter.com, где вы найдете множество изданий по этой тематике.
Пользовательские библиотеки тегов
Удобным свойством интерфейса API для JavaServer Pages является возможность определять пользовательские библиотеки тегов. Это очень мощное средство, позволяющее задействовать специализированные инструментальные средства так же легко, как стандартные теги.
Пользовательские теги задействуют интерфейсы и классы пакета javax.se- rlet. jsp.tagext. Пользовательские библиотеки тегов намного упрощают работу по созданию JSP-страниц.
Простой пример сервлета
Обычное приложение с сервлетами включает в себя класс, который расширяет класс HttpServlet и реализует методы, необходимые для обработки различных типов запросов, адресованных приложению. В простом примере, показанном в листинге 3.2, сервлет должен отвечать только на запросы методом GET, поэтому в нем реализован только метод doGET. Обратите внимание, что ответ записывается в объект PrintWriter с именем out, который получен из объекта HttpServletResponse.
Листинг 3.2. Простой сервлет, обрабатывающий запрос GET (DateDemo.java)
import java.io.*;
import java.util.* ;
import javax.servlet.*;
import javax.servlet.http.*;
public class DateDemo extends HttpServlet
{
public void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException
{
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
String username = req.getParameter("uname");
if( username == null ) username = "unknown person" ;
out.println("<HTML>");
out.println("<HEAD><TITLE>Date Demo</TITLE></HEAD>");
out.println("<BODY>");
out.println("Hello " + username + "<br>");
out.println("Date and time now: " + new Date().toString() + "<br>");
out.println("</BODY>");
out.println("</HTML>");
out.close();
}
}
В этом примере метод doGet пытается отыскать параметр с именем uname в объекте HttpServl etRequest, чтобы использовать его в ответе. Заметим также, что в сервлете задействован метод setContentType, для того чтобы установить тип содержимого ответа как text/htral.
Роль JavaBeans
Огромный успех языка Java обусловлен его простой архитектурой, основанной на компонентах JavaBeans. Хотя сначала планировалось использовать их как компоненты графического интерфейса, вскоре оказалось, что они весьма полезны и в неграфических приложениях. JavaBeans — это просто класс Java, который удовлетворяет следующим критериям:
он должен быть открытым и реализовывать Seri al i zabl e;
класс должен иметь конструктор без параметра;
доступ к любым переменным, используемым другими классами, в классе JavaBeans осуществляется через методы setXxx и getXxx.
Создавая классы и называя методы в соответствии с этими простыми условиями, можно частично автоматизировать конструирование различных приложений, используя готовые компоненты, особенно в технологии JavaServer Pages. Вы, вероятно, слышали название Enterprise JavaBeans в контексте разговоров о серверах web-приложений. Технология Enterprise JavaBeans значительно отличается от JavaBeans и является гораздо более сложной.
Создание объектной модели документа для каталога товаров
Как мы говорили в главе 1, исходное создание объектной модели документов на Java очень просто, поскольку вся работа выполняется анализатором, входящим в набор инструментальных средств. В листинге 3.4 показан фрагмент кода, который на основе документа XML строит объект org.w3c.dom.Document. Основная часть кода состоит из инструкций, перехватывающих различные синтаксические ошибки.
Листинг 3.4. Пример синтаксического анализа документа (TheCatalog.java)
]
import javax.xml.parsers.* ;
import org.xml.sax.* ;
import org.w3c.dom.* ;
public class TheCatalog
{
org.w3c.dom.Document catDoc ;
public TheCatalog( File f, TextArea msg, TextField status ){
try {
timestamp = f.lastModified();
DocumentBuilderFactory dbf =
DocumentBuilderFactory.newInstance ();
// statements to configure the DocumentBuilder would go here
DocumentBuilder db = dbf.newDocumentBuilder ();
catDoc = db.parse( f );
}catch(ParserConfigurationException pce){
lastErr = pce.toString();
System.out.println("constructor threw " + lastErr );
}catch(SAXParseException spe ){
StringBuffer sb = new StringBuffer( spe.toString() );
sb.append("\n Line number: " + spe.getLineNumber());
sb.append("\nColumn number: " + spe.getColumnNumber() );
sb.append("\n Public ID: " + spe.getPublicId() );
sb.append("\n System ID: " + spe.getSystemId() + "\n");
lastErr = sb.toString();
System.out.print( lastErr );
}catch( SAXException se ){
lastErr = se.toString();
System.out.println("constructor threw " + lastErr );
se.printStackTrace( System.out );
}catch( IOException ie ){
lastErr = ie.toString();
System.out.println("constructor threw " + lastErr +
" trying to read " + f.getAbsolutePath() );
}
}
Структура данных DOM выстраивается в памяти в такую же иерархическую систему, какая была в документе XML. Объекты Java представляют различные части документа XML и связаны ссылками на соседние элементы, как показано на рис. 1.2 в главе 1. Программные интерфейсы объектов Java, представляющие различные части документа, определены в пакете org.w3c.dom. Каждая часть документа XML, включая корневой элемент, представлена в виде объекта, реализующего интерфейс, который является расширением фундаментального интерфейса Node.
Технологии представления
При создании коммерческого сайта в Интернете программисты, пишущие на языке Java, имеют очень широкий выбор различных вариантов. Все эти варианты, однако, ограничены возможностями web-протоколов. Наиболее существенным ограничением является следующий аспект взаимодействия web-браузера клиента и web-сервера: каждый запрос пользователя инициирует один ответ сервера. Такое взаимодействие называется неустойчивым (stateless), так как в протоколе отсутствует требование, чтобы на сервере сохранялась какая-либо информация о транзакции после того, как ответ сервера отослан клиенту.
Встроенные переменные в JSP-страницах
В табл. 3.7 перечислены встроенные переменные, которые доступны в JSP-страницах по умолчанию.
Таблица 3.7. Встроенные переменные в JSP-страницах
Имя переменной |
Тип |
Описание | |||
request |
Объект класса, являющегося подклассом javax.servlet.ServletRequest |
Представляет запрос пользователя | |||
response |
Объект класса, являющегося подклассом javax.servlet.ServletResponse |
Создает ответ на запрос | |||
pageContext |
Объект класса javax.servlet.jsp.PageContext |
Содержит атрибуты страницы | |||
session |
Объект класса javax.servlet.http.HttpSession |
Содержит произвольные переменные, связанные с данным сеансом | |||
application |
Объект класса javax.servlet.ServletContext |
Содержит атрибуты для всего приложения и влияет на интерпретацию некоторых других тегов | |||
out |
Объект класса javax.servlet.jsp.JspWriter |
Выходной поток для данного ответа | |||
config |
Объект класса javax.servlet.ServletConfig |
Содержит пары имя-значение для параметров инициализации сервлета и объект ServletContext | |||
page |
Ссылка на объект, синоним this |
Возвращает ссылку на сервлет | |||
exception |
Объект класса javax.lang.Throwable или одного из его подклассов |
Содержит только те страницы, которые обозначены в директиве страницы как ошибочные |
Взаимодействие по протоколу HTTP
Консорциум W3C (www.w3.org) поддерживает протокол HTTP 1.1 (это модификация предыдущей версии, HTTP 1, у которой было много недостатков) в качестве текущего стандарта для web-серверов. Этот стандарт определяет требования к формату запросов браузера и ответов web-сервера.
Запрос браузера
Сообщение-запрос браузера, отсылаемое на сервер, начинается с заголовка, состоящего из одной или нескольких строк ASCII-символов, каждая из которых заканчивается символом crl f (carriage-return-line-feed — возврат каретки и перевод строки). Первая строка нужна для указания метода,, идентификатора URI (Uniform Resource Identifier — универсальный идентификатор ресурса) и индикатора используемой версии HTTP. Стандартными методами для протокола HTTP 1.1 являются OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE и CONNECT, но для коммерческих сайтов обычно используются методы GET и POST. После заголовка могут следовать дополнительные данные.
В запросе на обычную HTML-страницу используется метод GET. Простые поисковые запросы также пересылаются методом GET, в то время как для отправки заполненных форм, например в приложении «корзина покупателя», обычно применяется метод POST. Практически разница между этими методами заключается в том, что в запросе по методу GET параметры присоединяются к строке заголовка, содержащей идентификатор URI, а в методе POST данные передаются в теле сообщения.
Заголовок также содержит строки, дающие дополнительную информацию о типах данных, которые может принять данный браузер, о предпочтительном типе соединения и о типе и версии самого браузера. В листинге 3.1 показан пример запроса, который посылается методом POST после щелчка на кнопке Send (Отправить) на обычной HTML-странице, содержащей формы для заполнения их пользователем и скрытую переменную с именем action и значением showkeywords. Заметим, что строки Accept: и User-Agent: разбиты на две части, так как иначе они бы не поместились на странице.
Листинг 3.1. Сообщение, пересылаемое браузером на сервер методом POST [Все представленные в книге тексты программ можно найти на сайте издательства по адресу www.piter.com. — Примеч. ред.]
POST /servlet/cattest HTTP/1.1
Accept: application/msword, application/vnd .ms-excel, image/gif, image/x-xbitmap, image/]peg, image/pjpeg, */*
Referer: http://localhost/XmlEcommBook/CTestSnoop.html
Accept-Language: en-us
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)
Host: localhost:9000
Content-Length: 19
Connection: Keep-Alive
action=showkeywords
API для класса HttpSession
Интерфейс HttpSession содержится в пакете javax.sevlet.http. В табл. 4.1 перечислены методы этого интерфейса согласно версии API 2.2 сервлетов.
Таблица 4.1. Методы класса HttpSession
Метод |
Возвращаемое значение |
Описание | |||
getAttribute (String name) |
Object |
Возвращает подключенный к сеансу объект с заданным именем name или null, если не найдено объекта с таким именем | |||
setAttribute (String name.Object obj) |
void |
Подключает к сеансу объект obj с именем name. Если к сеансу ранее был подключен другой объект с этим именем, прежняя связь теряется | |||
getAttribute Names() |
Enumeration |
Перечень объектов типа String, содержащий имена всех объектов, подключенных к сеансу | |||
removeAttri bute (String name) |
void |
Удаляет объект с указанным именем из сеанса | |||
getCreationTime() |
long |
Системное время (GMT) создания объекта такое же, как в System. currentTimeMillisO | |||
getLastAccessed Time() |
long |
Системное время последнего обращения клиента к сеансу. Формат такой же, как в getCreationTime | |||
getMaxInactive Interval() |
int |
Максимальный интервал времени (в секундах), в течение которого контейнер сервлета поддерживает сеанс открытым между обращениями к нему клиента | |||
setMaxInactive Interval (int interval) |
void |
Устанавливает интервал времени (в секундах) между обращениями клиента, по истечении которого контейнер сервлетов сделает данный сеанс недействительным | |||
invalidate() |
void |
Делает данный сеанс недействительным и прекращает все связи с объектами | |||
isNew() |
boolean |
Возвращает значение true, если клиент еще не знает о сеансе или клиент предпочел не присоединяться к сеансу. Обычно этот метод вызывается сразу после вызова метода getSession объекта HttpServlrtRequest | |||
getId() |
String |
Возвращает уникальный идентификатор, присвоенный данному сеансу |
В этой версии произошли некоторые изменения по сравнению с версией 2.1, которые необходимо указать, так как в некоторых процессорах сервлетов используется старая версия.
Методы getAttribute и setAttribute заменили прежние методы getValue и setValue. Метод getAttributeNames заменил прежний метод getVal ueNames. Эти изменения были проделаны в процессе общей модернизации спецификаций классов сервлетов.
Для хранения ссылки на объект сеанса в классе HttpSession и ее извлечения используется имя типа String, как в следующем примере, где session — это переменная класса HttpSession:
ShoppingCart cart = (ShoppingCart)session.getAttribute("cart");
if( cart == null ){ // предположительно первый проход
cart = new ShoppingCart();
session.setAttribute( "cart",cart );
}
Некоторые дополнительные изменения в API по сравнению с предыдущими версиями обусловлены соображениями безопасности. В версии API 2.1 можно было использовать метод getSessionContext для получения связанного объекта HttpSessionContext. Этот метод и интерфейс HttpSessionContext в нынешней версии отнесены к нерекомендуемым (deprecated), и они будут удалены из последующих версий этой библиотеки (иногда такие методы называются устаревшими).
В версии API 2.2 особое внимание уделено тому, чтобы существенная информация web-приложения оставалась в рамках этого приложения. В интерфейсе Servl etContext определены методы, которые сервлет может использовать для взаимодействия с контейнером и совместного использования одного и того же объекта с другими сервлетами этого приложения. Конкретный объект класса HttpSession может задействоваться более чем одним сервлетом, но только в случае, если эти сервлеты принадлежат тому же приложению. Участие сервлета или JSP-страни- цы в определенном приложении устанавливается с помощью параметров инициализации, которые использует процессор сервлетов.
Если механизм HttpSession только создавал бы атрибуты и не обеспечивал бы никакого способа избавления от них, процессор сервлетов вскоре исчерпал бы все ресурсы памяти. К счастью, имеется несколько методов организации приложения таким образом, чтобы избежать проблем с памятью.
Те процессоры сервлетов, с которыми мы работаем в наших примерах (JRun и Tomcat), по умолчанию удаляют объекты HttpSession, если они не использовались в течение 30 минут. Нужный интервал времени допустимого простоя можно установить для каждого приложения, задавая параметры инициализации в ядре сервлетов. В табл. 4.1 указан метод setMaxInactivelntarval, с помощью которого можно задать величину этого интервала. Установка отрицательного значения -1 означает, что для сеанса не задано время простоя, и в этом случае программист должен явным образом удалить объект с помощью метода invalidate.
Также можно явным образом удалить определенные объекты из класса HttpSession с помощью метода RemoveAttribute (в API 2.1 этот метод назывался remove Value). Программисту следует очень внимательно относиться к выбору объектов, которые он собирается хранить как объекты сеанса. Помните, что вы не можете предсказать, когда пользователь вернется к данному сеансу. Поэтому не рекомендуется хранение таких объектов, как, например, объекты соединения с базами данных, которые требуют значительных системных ресурсов.
Вы наверняка хотели бы, чтобы
Заполнение корзины покупателя
Вы наверняка хотели бы, чтобы посетители вашего web-сайта могли легко находить интересующие их товары, добавлять их в корзину и производить оплату, в соответствии со своим интуитивным представлением об этих действиях как о неразрывно связанных этапах покупки товара. К сожалению, основные используемые в настоящее время HTTP- и web-протоколы не позволяют обеспечить эту неразрывность. В настоящей главе исследуется закулисная работа, необходимая для того, чтобы пользователи могли без затруднений приобретать товары в вашем электронном магазине.
Инициализация сервлета
Как видно из листинга 4.4, в классе CatalogServ имеется некоторое количество статических переменных, которые определяют различные ресурсы Мы приводим типичные значения этих переменных В реальном сервлете эти значения заменяются специфичными для системы значениями, хранящимися в файле catalog.properties, откуда они считываются методом imt Мы будем использовать класс Properties — расширение класса Hashtable из пакета java.util, где содержатся очень удобные методы для загрузки текстовых параметров из файла
Листинг 4.4. Статические переменные и метод mit в сервлете CatalogServ (CatalogServ.java)
package com.XmlEcomBook.catalog;
import java.io.*;
import java.util.* ;
import javax.servlet.*;
import javax.servlet.http.*;
public class CatalogServ extends HttpServlet
{
static String brcrlf = "<br />\r\n" ;
static String version = "1.03 Oct 17, 2000";
static String cssLinkA = "<link rel=\"stylesheet\" href=\"" ;
// following is part of a web server URL for the style sheet
static String cssLinkB = "XmlEcommBook/catalog/catalog.css" ;
static String cssLinkC = "\" type=\"text/css\" media=\"screen\" >" ;
static String resourcepath = "XmlEcommBook/catalog/" ;
static String host = "http://localhost/";
// these are servlet engine aliases
static String servlet = "servlet/catalog" ;
static String checkout = "servlet/checkout" ;
// these are complete webserver paths
static String cssLink = cssLinkA + host + cssLinkB + cssLinkC ;
static String alias ; // for catalog servlet
static String checkoutalias ;
static String resources ; // for images, style sheets, etc
String catPath = "e:\\scripts\\XMLgifts" ; // for xml
String catName = "catalog.xml" ;
Properties catProp = new Properties();
public void init(ServletConfig config) throws ServletException
{ try {
super.init(config);
System.out.println("CatalogTestServ init called, version "
+ version );
String tmp = config.getInitParameter("workdir");
if( tmp != null ) catPath = tmp ;
File f = new File( catPath, "catalog.properties");
if( f.exists() && f.canRead() ){
FileInputStream fis = new FileInputStream(f) ;
catProp.load( fis );
fis.close();
tmp = catProp.getProperty("csspath");
if( tmp != null ) cssLinkB = tmp;
tmp = catProp.getProperty("host");
if( tmp != null ) host = tmp ;
tmp = catProp.getProperty("resourcepath" ) ;
if( tmp != null ) resourcepath = tmp ;
tmp = catProp.getProperty("catalogservlet");
if( tmp != null ) servlet = tmp ;
tmp = catProp.getProperty("checkoutservlet" );
if( tmp != null ) checkout = tmp ;
}
else { System.out.println ("CatalogServ can't read catalog.properties");
}
resources = host + resourcepath ;
alias = host + servlet ;
checkoutalias = host + checkout ;
System.out.println( "resources:" + resources );
System.out.println("servlet: " + alias );
System.out.println("checkout: " + checkoutalias );
CatalogBean.setTheCatalog( catPath, catName );
CatalogBean.setResourcePath( resources );
}catch( Exception e ){
System.out.println("CatalogTestServ init " + e );
}
}
Заметим, что метод init вызывает два статических метода из класса CatalogBean. Вызов метода setTheCatal од необходим для считывания данных из файла XML, а метод setTheResoursePath устанавливает путь, который будет использоваться для нахождения таких ресурсов, как изображения товаров. Определения класса Gala- togBean вы найдете далее в этой главе в разделе «Класс CalatogBean».
Интерфейс HttpSessionBindingListener
Как вспомогательное средство для управления системными ресурсами, которые могут быть задействованы в сеансах, и как средство отладки в API сервлетов предусмотрены интерфейс HttpSessionBindingListener и класс HttpSessionBinding- Event. В этом интерфейсе определены два метода.
void valueBoundCHttpSessionBindingEvent event). Когда объект, реализующий интерфейс HttpSessionBindingListener, присоединяется к сеансу HttpSession, вызывается данный метод. Параметр этого метода event передает двоякую информацию — имя, которое было использовано для присоединения объекта к сеансу, и идентификатор сеанса (объект типа String).
void valueUnbound(HttpSessionBindingEvent event). Этот метод вызывается, когда объект должен быть удален из сеанса. Обычно это происходит, когда в результате простоя выполняется метод invalidate класса HttpSession и сеанс становится недействительным.
В сервлете catalogServ мы демонстрируем использование этого интерфейса для выполнения простых операций по отладке.
Использование класса carttistener
Поскольку процессор сервлетов во многом определяет поведение объекта HttpSession, решение проблем отладки, связанных с сеансами, может оказаться непростым делом. API сервлетов предоставляет интерфейс HttpSessionBindingListener и класс HttpSessionBindingEvent для решения проблем отладки и для управления ресурсами, которые могут быть присоединены к объекту HttpSession.
В листинге 4.13 показан простой пример использования этого интерфейса в классе CartLi stener, который является внутренним по отношению к классу Catalog- Serv. В этом примере мы просто записываем системное время присоединения объекта к объекту HttpSession, и затем, когда сеанс закрывается, печатаем время жизни объекта. Объект CartLi stener присоединяется к объекту ShoppigCart, когда он создается в первый раз методом doPost (см. листинг 4.5).
Листинг 4.13. Внутренний класс CartListener (CatalogServ.java)
class CartListener implements
HttpSessionBindingListener {
long created ;
public void valueBound( HttpSessionBindingEvent evt ){
created = System.currentTimeMillis();
}
public void valueUnbound( HttpSessionBindingEvent evt ){
long del = System.currentTimeMillis() - created ;
System.out.println( "Session lifetime: " + ( del / 1000 )
+ " seconds ");
}
}
}
Использование объектов Cartltem и ShoppingCart
Как показано в листинге 4.14, CatalogBean создает объект Cartltem для некоторого товара на основе информации, содержащейся в экземпляре cat класса TheCatalog.
Метод doCartLnst, приведенный в листинге 4 14, контролирует вывод всех объектов Cartltem, содержащихся в экземпляре класса ShoppingCart Форматирование таблицы осуществляется методом doShowCart класса CatalogServ Метод doCartList обеспечивает отображение каждого заказанного товара в отдельной строке таблицы HTML, а также отображение количества заказанных экземпляров (numeberOrdered) для каждого из них Типичная страница, показывающая содержимое корзины покупателя, представлена на рис 4 6
Листинг 4.14. Методы CatalogBean, связанные с объектом ShoppingCart (CatalogBean.java)
public CartItem createCartItem( String id ){
Element pE = cat.getProductElByID( id );
return new CartItem( pE );
}
// we are in a <table>.. </table> pf was created with setOutput
public void doCartList( PrintWriter out,
ShoppingCart cart ){
Vector v = cart.getItems();
int ct = v.size();
for( int i = 0 ; i < ct ; i++ ){
CartItem item = (CartItem)v.elementAt(i);
out.print("<tr><td>");
String id = item.getId();
out.print( pf.doListOutput( cat.getProductElByID(id)));
out.print( "</td><td>");
out.print( "Number ordered: " + item.getNumberOrdered() );
out.print( "</td></tr>\r\n");
}
}
![](image/ispolzovanie-obektov-cartltem-i-shoppingcart_1.gif)
Рис. 4.6. Отображение содержимого корзины покупателя
Сложное форматирование при представлении полной информации о товаре (как показано на рис. 4 4 и 4 5) контролируется методом doFuTIItem, код которого приведен в листинге 4 15 Исходно мы строим таблицу с четырьмя или тремя ячейками в зависимости от наличия или отсутствия изображения товара В этой таблице содержатся сведения о товаре, его изображение (если оно есть), название и цена, а также количество заказанных экземпляров (если покупатель решил приобрести этот товар) Создается объект ProductFormatter, задающий стиль отображения всей этой информации о товаре, то есть стиль всех фрагментов текста, которые содержатся в документе XML, описывающем данный товар
+ " value=\"setcart\" >");
if( ci.getNumberOrdered() == 0 ){
out.print("<input type=\"HIDDEN\" name=\"itemct\" value=\"1\" >");
out.print("<input type=\"SUBMIT\" value=\"" );
out.print(" Add this item to cart\" >");
}
else {
out.print( "<i>To change the number ordered,
enter a new number here "
+ "and click the Change button.</i><br />" );
out.print("<input type=\"TEXT\" name=\"itemct\" size=\"5\"" +
" value=\"" + ci.getNumberOrdered() + "\" > ");
out.print("<input type=\"SUBMIT\" value=\"");
out.print("Change\" >");
}
out.print("</form></td></tr>\r\n");
}
Использование объектов класса HttpSession
Процессор сервлетов Java (который иногда называют контейнером сервлетов) управляет коллекцией объектов, реализующих интерфейс HttpSession. Наша программа может использовать один из этих объектов для хранения любой информации, необходимой при создании корзины покупателя. Каждый объект HttpSession имеет идентификатор (id), представляющий собой строку, которая создается одновременно с объектом и гарантированно является уникальной. Этот уникальный идентификатор создается одним из двух возможных методов.
Мониторинг файлов cookie. Значение идентификатора отсылается на web- браузер пользователя как заголовок файла cookie, сгенерированного объектом HttpServl etResponse. Стандартное имя такого файла cookie — jsessionid, а значением может быть строка, подобная следующей: "97187996250188366", то есть просто случайное число, генерируемое процессором сервлетов. Это значение автоматически считывается при поступлении следующего запроса от того же пользователя, так что наша программа просто запрашивает объект HttpSession, принадлежащий данному пользователю.
Перезапись URL. Значение идентификатора присоединяется к каждому URL-адресу, который посетитель сайта может использовать для отправки запроса на сервер. Поскольку сюда входят также URL-адреса, которые являются частью статических шаблонов или динамически генерируемого текста, программист должен приложить дополнительные усилия, чтобы гарантировать, что все URL-адреса будут переписаны.
Чтобы не усложнять наш пример, в сервлете, реализующем корзину покупателя, мы будем использовать подход с мониторингом файлов cookie:
HttpSession session = req.getSession(true);
if( session.isNew() ){
System.out.print("Session is new " + session.getld() ); }
Этот простой фрагмент кода метода doPost формирует объект HttpSession для данного пользователя. Переменная req является объектом класса HttpServl etRequest. Булева постоянная true в вызове метода getSession указывает процессору сервлетов, что нужно создать новый объект сеанса, если в пользовательском запросе отсутствует соответствующий идентификатор. Вторая строка содержит метод isNew, позволяющий определить, является ли данный сеанс новым или клиент (браузер) уже участвовал в сеансе.
В интерфейсе API для JSP-страниц переменная класса HttpSession называется session, поэтому желательно и в сервлетах для таких переменных использовать именно это имя. Теперь можно перейти к краткому обзору интерфейса API, который используется при работе с объектом HttpSession.
Класс CatalogBean
Как вы уже, вероятно, заметили при обсуждении методов класса CatalogServ, вся основная работа по созданию форматированного отображения информации о товарах выполняется методами класса CatalogBean. Эти методы форматирования выделены из сервлета в отдельный класс для того, чтобы упростить реализацию функций каталога в технологии JavaServer Pages. В этой главе мы за недостатком места не приводим JSP-версию каталога, а технологии JSP посвящена глава 5.
Большинство методов класса CatalogBean, используемых в этой главе, остались такими же, как в главе 3. Наиболее значительные изменения касаются объектов ShoppingCart и Cartltem. Напомним, что в CatalogBean имеется статическая переменная для объекта TheCatalog, который управляет объектной моделью документа, созданной на основе каталога (файла XML). В обновленной версии CatalogBean мы сделали эту переменную закрытой (private) и предложили следующий метод доступа:
private static TheCatalog cat ;
static void setTheCatalog( String path, String name ){
File f = new File( path, name );
cat = new TheCatalog( f, null, null );
cat.scanCatalog();
}
static TheCatalog getCat(){ return cat ; }
Мы также добавили статическую переменную resoursePath и метод setResourse- Path, который задает значение этой переменной при инициализации сервлета:
private static String resourcePath ; // используется для
// изображений, звуковых
// файлов и т.д.
static void setResourcePath( String s ){ resourcePath = s ; }
Класс CatalogServ
Класс, который мы написали для отображения элементов каталога и манипулирования корзиной покупателя (классом ShoppingCart), называется CatalogServ. Здесь мы используем усовершенствованные версии классов для представления каталога сети, описанных в главе 3, и добавляем функции для организации корзины покупателя и мониторинга сеанса. Возможности сервлета CatalogServ перечислены ниже.
Отображение компактных списков товаров каталога с использованием критерия отбора, в частности:
полный каталог;
товары одной серии;
товары, связанные с определенным ключевым словом (результат поиска по ключевому слову).
Отображение детальной информации о товаре, дополненное следующими функциями:
добавление данного товара в корзину;
изменение количества заказанных экземпляров.
Отображение списка выбранных на текущий момент товаров.
Для простоты будем считать, что заказ товара (добавление его в корзину и изменение количества заказанных экземпляров) может происходить только на странице с подробной информацией о данном товаре.
Кроме того, наш сервлет не будет выполнять никаких других функций, свойственных обычному коммерческому сайту. Как показано на рис. 4.1, наш сервлет просто размещает текст «Your site navigation could go here» [Здесь может быть расположен интерфейс для навигации по вашему сайту. — Примеч. перев. ], в то время как в этом месте обычного коммерческого сайта, как правило, находятся логотипы и навигационный интерфейс.
![](image/klass-catalogserv_1.gif)
Рис. 4.1. Отображение полного каталога с помощью класса CatalogServ
Перед тем как углубляться в изучение исходного кода CatalogServ, рассмотрим некоторые другие представления, которые он генерирует. На рис. 4.2 показана страница с раскрывающимся списком ключевых слов. Это тот же самый список, который представлен на рис. 3.1 в главе 3, но сервлет CatalogServ добавляет в нижнюю часть страницы дополнительные ссылки:
Full Catalog (Весь каталог);
Books (Книги);
CDs (Компакт-диски):
Widgets (Приборы и устройства);
Search (Поиск).
![](image/klass-catalogserv_2.gif)
Рис. 4.2. Отображение раскрывающегося списка ключевых слов
В табл. 4 2 представлен перечень команд отображения (значений параметра action), которые определяют ответ сервлета CatalogServ Команды, управляющие отображением, обычно модифицируются дополнительными параметрами
Таблица 4.2. Команды, распознаваемые сервлетом CatalogServ
Команда (параметр action) |
Дополнительные параметры |
Отображение |
showcatalog | Параметр select = "all" | Таблица со списком всех товаров (см. рис. 4.1) |
showcatalog | Параметр select =одна из серий товаров (books, CDs, widgets) | Таблица со списком товаров определенной серии (см. рис. 4.3) |
selectkeyword | Параметр select = "all" | Раскрывающийся список ключевых слов (см. рис. 4.2) |
showproduct | Параметр id из формы | Полная информация о товаре (см. рис. 4 4) |
keywdsearch | Параметр keyword из формы | Список товаров с этим ключевым словом |
setcart | Параметры id и itemct из формы | Полная информация о товаре с измененным количеством заказанных экземпляров (см. рис. 4.5) |
showcart | Параметры отсутствуют | Список всех товаров в корзине покупателя с указанием количества заказанных экземпляров (см. рис. 4.6) |
Класс ProductFormatter
Мы добавили множество методов в класс ProductFormatter, описанный в главе 3. Вообще говоря, эти методы просто расширяют те возможности, которыми уже обладал этот класс. В листинге 4.16 показаны статические переменные, которые определяют два различных стиля. «Краткий» (short) стиль используется для отображения списков товаров (всего каталога или одной серии), а полный (full) стиль используется в методе doFullItem класса CatalogBean для отображения полной информации об одном товаре. Также мы создали коллекцию fieldHash, которая связывает названия товаров с целочисленными константами.
Листинг 4.16. Статические переменные класса ProductFormatter (ProductFormatter.java)
package com.XmlEcomBook.catalog;
import java.util.* ;
import java.io.* ;
import org.xml.sax.* ;
import org.w3c.dom.* ;
public class ProductFormatter
{
static String brcrlf = "<br />\r\n" ; // xhtml style br
static String[] shortEl = { "prname", "price" // for product name
};
static String[] shortSt = { "ch3", "ch4"
};
// as used in doListOutput
static String[] fullEl = { "prname",
"author","artist","description",
"price" // for product name
};
static String[] fullSt = { "ch3",
"au1", "au1", "ch4", "ch4"
};
static Hashtable fieldHash ;
// field names for lookup
static String[] fields = { "id", "keywords",
"prname", "price", "author", "artist", "description",
"image", "caption", "quantity_in_stock", "onsale_date",
"shipping_info"
} ;
static { fieldHash = new Hashtable() ;
for( int i = 0 ; i < fields.length ; i++ ){
fieldHash.put( fields[i], new Integer( i ) );
}
}
В листинге 4.17 показано начало кода для методов и переменных экземпляра и конструктора ProductFormatter. Заметим, что конструктор задает формат в соответствии с переданной ему переменной frmt, которая может принимать значение "short" или "full".
Листинг 4.17. Начало кода методов и переменных экземпляра (Product Formatter.java)
String[] elem, style ;
String resourcePath ;
String aLink ;
int linkN ;
// when aLink is supplied, it should be something like
// "/servlet/catalog?action=showproduct", then the doListOutput will build a
// complete link adding &id=xxxxxx to attach to the first parameter
public void setALink(String s, int pos ) { aLink = s ; linkN = pos ;}
public void setResourcePath( String s ){ resourcePath = s ; }
// throws exception if unknown format
public ProductFormatter( String frmt ){
if( frmt.equals("short")){
elem = shortEl ; style = shortSt ;
}
else if( frmt.equals("full")){
elem = fullEl ; style = fullSt ;
}
else { throw new IllegalArgumentException ("ProductFormatter: " + frmt );
}
}
Классы Cartltem и ShoppingCart
Минимальным требованием к объекту, представляющему товар в корзине покупателя, является наличие идентификатора данного товара и количества заказанных экземпляров. Название товара, его цену и другие сведения можно найти в каталоге — документе XML. Но мы решили не ограничиваться минимальными требованиями и добавили в Cartltem название товара, цену и информацию по доставке, как показано в листинге 4.1.
Следует отметить несколько важных моментов, касающихся класса Cartltem. Во-первых, конструктор работает непосредственно с объектом El ement структуры DOM, представляющим данный товар. Это упрощает добавление различных дополнительных переменных в XML-каталог. Во-вторых, класс Cartltem реализует интерфейс Serializable. Это позволяет посылать коллекцию объектов Cartltem, представляющую собой список заказанных товаров, другой программе Java, используя сериализацию данных. Сериализация также требуется, если процессору сервлетов приходится хранить сеанс или пересылать его на другой сервер. Наконец, названия методов доступа, например getld и setNumberOrdered, соответствуют соглашению об именах, принятому в JavaBeans, чтобы упростить использование объекта Cartltem в коде JSP-страницы.
Листинг 4.1. Класс Cartltem (cartltem.java) [Все представленные в книге тексты программ можно найти на сайте издательства по адресу www.piter.com. — Примеч. ред. ]
package com.XmlEcomBook.catalog;
import java.util.* ;
import java.io.* ;
import org.xml.sax.* ;
import org.w3c.dom.* ;
public class CartItem implements java.io.Serializable
{ // be sure to change this if substantive variables change
static final long serialVersionUID = 3260689382642549142L;
// these are set from the constructor
private String id ; // from product element
private String name ; // from name element
private String price ; // from price element
private String shippingType ; // from shipping_info element
private String shippingValue ; // may be null if type is special
// these may change
private int numberOrdered ; // changes
public String getId(){ return id ;}
public String getName(){ return name ; }
public String getPrice() { return price ; }
public String getShippingType() { return shippingType ; }
public String getShippingValue() { return shippingValue ; }
public int getNumberOrdered(){ return numberOrdered ; }
public void setId(String s){ id = s ; }
public void setName(String s){ name = s; }
public void setPrice(String s){ price = s ;}
public void setShippingType(String s ){shippingType = s ;}
public void setShippingValue(String s) { shippingValue = s ;}
public void setNumberOrdered( int n ){ numberOrdered = n ;
System.out.println("setNumberOrdered " + n );
}
// needed for operation as a Bean
public CartItem(){
}
// constructor uses a <product> org.w3c.dom.Element
public CartItem( Element pe ){
id = pe.getAttribute("id");
NodeList nl = pe.getElementsByTagName( "name" );
name = nl.item(0).getFirstChild().getNodeValue() ;
nl = pe.getElementsByTagName( "price" );
price = nl.item(0).getFirstChild().getNodeValue() ;
nl = pe.getElementsByTagName( "shipping_info" );
Element ship = (Element) nl.item(0);
shippingType = ship.getAttribute("type");
shippingValue = ship.getAttribute("value"); // may be ""
}
// handy for debugging
public String toString() {
StringBuffer sb = new StringBuffer("CartItem name:");
sb.append( name ); sb.append(" numberOrdered: ");
sb.append( Integer.toString( numberOrdered ));
return sb.toString();
}
}
Класс ShoppingCart достаточно прост, так как все, что от него требуется, — манипулирование объектами класса Cartltem. Как показано в листинге 4.2, мы храним ссылки на объекты Cartltem в двух местах — Vector и HashTable. Причина этого заключается в том, что порядок размещения ссылок в объекте HashTable непредсказуем и может меняться по мере добавления новых товаров. Представляется разумным хранить эти ссылки в предсказуемой и воспроизводимой последовательности в объекте Vector и в то же время иметь возможность доступа к товарам по их идентификаторам через хэш-таблицу.
Заметим, что класс ShoppingCart реализует интерфейс Serializable, так что вся корзина (то есть объект класса Shoppi ngCart) может пересылаться между программами Java или записываться в файл посредством сериализации.
Листинг 4.2. Начало кода класса ShoppingCart (ShoppingCart.java)
package com.XmlEcomBook.catalog;
import java.io.*;
import java.util.* ;
public class ShoppingCart implements java.io.Serializable
{ private Vector items ; // maintains order of selection of items
private Hashtable itemsById ;
public ShoppingCart(){
items = new Vector();
itemsById = new Hashtable();
}
// items vector may be empty
public Vector getItems(){ return items ; }
// returns CartItem for this id or null if not in list
public CartItem getProdById(String s ){
return (CartItem) itemsById.get( s );
}
// CartItem is assumed to be unique
public int addItem( CartItem x ){
items.addElement( x );
itemsById.put( x.getId() , x );
return items.size();
}
В листинге 4.3 показаны остальные методы класса ShoppingCart. Поскольку мы храним ссылки на объекты Cartltem в двух коллекциях, для удаления элемента из объекта HashTable применяется метод removeByld с указанием идентификатора товара и затем вызывается метод removeEl eraent вектора items.
Листинг 4.3. Остальная часть кода класса ShoppingCart (ShoppingCart.java)
// remove an item from the cart by product id
public CartItem removeById( String s ){
CartItem ret = (CartItem)itemsById.get( s );
if( ret == null ) return null ;
itemsById.remove(s); // remove by key
items.removeElement( ret );
return ret ;
}
// remove all CartItem for which the numberOrdered is zero
// returns the count of items left
public int removeEmptyItems(){
Enumeration keys = itemsById.keys();
while( keys.hasMoreElements()){
String key = (String)keys.nextElement();
CartItem ci = (CartItem)itemsById.get(key);
if( ci.getNumberOrdered() == 0 ){
removeById( key );
}
}
return items.size();
}
// mainly for debugging
public String toString()
{ StringBuffer sb = new StringBuffer( "ShoppingCart has " +
items.size() + " items.\r\n" ) ;
Enumeration e = items.elements();
while( e.hasMoreElements()){
sb.append("Item: ");
sb.append( e.nextElement().toString() );
sb.append("\r\n");
}
return sb.toString();
}
}
Корзина покупателя на языке Java
В этой главе мы создадим сервлет, функции которого ограничены отображением каталога и оформлением заказа на товары. В реальном коммерческом сайте эти функции составили бы лишь небольшую часть всех возможностей, предложенных пользователю. В нашем примере новый объект класса HttpSession создается, когда сервлет CatalogServ в первый раз получает запрос от данного пользователя. В реальном виртуальном магазине сеанс может быть создан в другой части сайта.
Возможной «точкой входа» посетителя может быть просмотр всего каталога или списка товаров какой-либо одной серии или просмотр результатов поиска по ключевому слову. В любом случае, мы не создаем объект ShoppingCart до тех пор, пока пользователь не проявит достаточного интереса к товару, чтобы просмотреть его полное описание. В этот момент мы создаем объект Cartltem для просматриваемого товара непосредственно из его описания в документе XML и добавляем объект Cartltem в ShoppingCart, то есть в корзину.
Пока пользователь просматривает информацию о различных товарах, мы создаем объекты Cartltem для каждого выбранного товара и удаляем из корзины те товары, которые он раздумал покупать. На любой странице пользователь имеет возможность вернуться к полному каталогу, найти товар по ключевому слову, просмотреть содержимое корзины или перейти к оплате товара. Теперь рассмотрим детали реализации описанной функциональности.
Метод addText
Метод addText, приведенный в листинге 4.20, вызывается некоторыми другими методами из класса ProductFormatter. Ему передается объект StringBuffer, в который добавляется текст. Параметр name определяет, какой именно текст требуется добавить, а элемент Element соответствует некоторому товару в каталоге. Целое число, являющееся значением параметра name, управляет точками перехода в инструкции switch.
Листинг 4.20. Метод addText (ProductFormatter.java)
// заметим, что в большинстве случаев нам нужно
// значение узла
private void addText(StringBuffer sb, String name, Element el ){
Object obj = fieldHash.get( name );
if( obj == null ){
sb.append( "no " + name + " found " ); return ;
}
switch( ((Integer)obj).intValue()){
case 0 : // "id",
addID( sb, el ); break ;
case 1 : // "keywords",
case 2 : // "prname", product name
addProductName( sb, el ); break ;
case 3 : // "price"
addPrice( sb, el ); break ;
case 4 : // "author",
addAuthor( sb, el ); break ;
case 5 : // "artist",
addArtist( sb, el ); break ;
case 6 : // "description",
addExtendedText( sb, el ); break ;
case 7 : // "image",
addImageTag( sb, el ); break ;
case 8 : // "caption"
addExtendedText( sb, el ); break ;
case 9 : // "quantity_in_stock",
case 10 : // "onsale_date"
}
}
Различные методы, вызываемые методом addText, приведены в листингах 4.21 и 4.22. Эти методы извлекают тот или иной текст из элемента product и добавляют его в Stri ngBuffer.
Листинг 4.21. Различные методы, вызываемые методом addText (ProductFormatter.java)
// Элемент е - это товар <product>
private void addID(StringBuffer sb, Element e ){
String id = e.getAttribute("id" );
sb.append("product code: ");
if( id.length()== 0 ){ sb.append("not assigned");
}
else { sb.append( id );
}
}
// element is either a <product> or <name> as child of a product
private void addProductName( StringBuffer sb, Element e){
if( !e.getNodeName().equals("name") ){
NodeList nl = e.getElementsByTagName( "name" );
e = (Element) nl.item(0);
}
sb.append( getChildrenText( e ) );
}
// element is <author> tag
private void addAuthor( StringBuffer sb, Element e){
NodeList nl = e.getElementsByTagName( "name" );
sb.append( getChildrenText( (Element) nl.item(0)) );
}
private void addArtist( StringBuffer sb, Element e){
NodeList nl = e.getElementsByTagName( "name" );
sb.append( getChildrenText((Element) nl.item(0)) );
}
// example <author><name>Christoph Minwich</name></author>
// known to have price
private void addPrice( StringBuffer sb, Element e ){
NodeList nl = e.getElementsByTagName( "price" );
sb.append("price ea = ");
sb.append( nl.item(0).getFirstChild().getNodeValue() );
}
Служебный метод getChildrenText, приведенный в листинге 4.22, собирает вместе текст всех дочерних узлов данного элемента.
Листинг 4.22. Служебный метод getChildrenText (ProductFormatter.java)
private String getChildrenText( Element e ){
StringBuffer sb = new StringBuffer();
NodeList nl = e.getChildNodes();
for( int i = 0 ; i < nl.getLength() ; i++ ){
sb.append( nl.item(i).getNodeValue() );
}
return sb.toString();
}
Метод addlmageTag, приведенный в листинге 4.23, использует информацию из тега XML <iraage>. Ниже приводится пример из файла catalog.xml:
<image format="gif" width="234" height="4Q0"
src="images/covers/pi ants.gif">
<caption>
<paragraph>This is the cover from the
first edition.</paragraph>
</caption>
</image>
Помимо создания тега <img>, который вставляет в HTML-страницу изображение, этот метод также проводит анализ и отображает подпись к изображению.
Листинг 4.23. Метод, создающий теги изображения (ProductFormatter.java)
private void addImageTag( StringBuffer sb, Element img ){
String format = img.getAttribute("format");
String width = img.getAttribute("width");
String height = img.getAttribute("height");
String src = img.getAttribute("src");
String desc = "image ";
sb.append("<img src=\"");
// detect option for image source to point off site
if( !src.toUpperCase().startsWith("HTTP")){
sb.append(resourcePath );
}
if( sb.charAt( sb.length() - 1 ) == '/' &&
src.charAt(0) == '/' ){
sb.append( src.substring(1) );
}
else sb.append( src ) ;
sb.append( "\" alt=\"" );
NodeList imgNL = img.getElementsByTagName("caption");
if( imgNL.getLength() > 0 ){
sb.append( desc );
// addText(sb, "caption", (Element) imgNL.item(0) );
}
else sb.append( desc );
sb.append( "\" width=\"" );
sb.append( width ); sb.append( "\" height=\"" );
sb.append( height ); sb.append( "\" >" );
//NodeList imgNL = img.getElementsByTagName("caption") ;
if( imgNL.getLength() == 0 ) return ;
Element caption = (Element) imgNL.item(0);
addText( sb, "caption", caption );
return ;
}
Формат нашего XML-каталога допускает использование стиля running_text в подписях и описаниях товаров. Методы addExtendedText и doExtendedTextEl ement, показанные в листинге 4.24, могут объединить весь текст описания или подписи с соответствующей разметкой HTML, чтобы создать абзац или назначить начертание этого текста (курсив или полужирный шрифт).
Листинг 4.24 Метод addExtendedText()
// одним из вариантов стиля является
// <paragraph>, простой текст
private void addExtendedText( StringBuffer sb, Element e ){
NodeList nl = e.getChildNodes();
int ct = nl.getLength();
// sb.append("child count " + ct + brcrlf );
for( int i = 0 ; i < ct ; i++ ){
Node n = nl.item(i);
switch( n.getNodeType() ){
case Node.TEXT_NODE :
sb.append( n.getNodeValue().trim() ); break ;
case Node.ELEMENT_NODE :
Element en = (Element) n ;
// sb.append("Element Name " + en.getNodeName() );
doExtendedTextElement(en.getNodeName(), sb, en );
break ;
default :
sb.append("default Name " + n.getNodeName() );
sb.append(" Value " + n.getNodeValue() );
}
sb.append(' ' ); // because values get trimmed
//sb.append( brcrlf );
}
}
//
private void doExtendedTextElement( String name, StringBuffer sb, Element e){
if( name.equals("paragraph") ){
sb.append("<p>"); addExtendedText( sb, e );
sb.append("</p>");
}
else if( name.equals("italics")){
sb.append("<i>" ); addExtendedText( sb, e );
sb.append(" </i>");
}
else if( name.equals("bold")){
sb.append("<b>" ); addExtendedText( sb, e );
sb.append(" </b>");
}
else { addExtendedText( sb, e );
}
}
public String toString()
{ StringBuffer sb = new StringBuffer("ProductFormatter ");
return sb.toString();
}
}
Метод doListOutput
Метод doListOutput (листинг 4.19) вызывается из CatalogBean для создания объекта типа String, содержащего форматированные данные по отдельному товару. Этот метод используется при конструировании таблицы, подобной приведенной на рис. 4.3. Обратите внимание, что мы создаем объект StringBuffer для построения строки (объекта String), поскольку добавление новых фрагментов в StringBuffer гораздо эффективнее, чем конкатенация (объединение) объектов String. Также заметим, что если методу передается переменная aLink, то текст, соответствующий элементу с индексом linkN, отображается в виде гипертекстовой ссылки.
Листинг 4.19. Метод doListOutput (ProductFormatter.java)
// создается строка с данными об отдельном товаре,
// используемая во многих листингах
// внешний вид определяется содержимым elem style
// обычно добавляется ссылка на более подробную
// информацию
public String doListOutput( Element el ){
StringBuffer sb = new StringBuffer( );
String pid = null ;
if( aLink != null ){
pid = "&id=" + el.getAttribute("id") ;
}
for( int i = 0 ; i < elem.length ; i++ ){
if( i == linkN && pid != null ){
sb.append( "<a class=\"" );
sb.append( style[i] );
sb.append("\" href=\"");
sb.append( aLink ); // typically "http://xxxhost/servlet/serv
sb.append( pid );
sb.append("\">");
addText( sb, elem[i], el );
sb.append( " </a>");
}
else {
sb.append( "<span class=\"");
sb.append( style[i] ); sb.append("\">");
addText( sb, elem[i], el );
sb.append( " </span>");
}
}
return sb.toString();
} // end doListOutput
Метод doPageEnd
В этом примере метод doPageEnd (листинг 4.6) просто создает набор активных ссылок в нижней части страницы. Ссылки на различные варианты представления каталога присутствуют всегда, но некоторые ссылки, связанные с корзиной покупателя, появляются, только если в корзину добавлены какие-либо товары. Обратите внимание, мы используем метод removeEmptyElements, чтобы гарантировать, что переменная nitem правильно отражает содержимое корзины.
Листинг 4.6. Метод doPageEnd (CatalogServ.java)
public void doPageEnd( HttpServletRequest req, HttpServletResponse resp,
PrintWriter out, HttpSession session ){
ShoppingCart cart = (ShoppingCart)session.getValue("cart");
String a1 = "<a href=\"" + alias + "?action=" ;
int nitem = 0 ; // permit checkout if cart has any items
out.print("<center>");
if( cart != null &&
(nitem = cart.removeEmptyItems()) > 0 ){
// out.print( cart.toString()); // debugging
out.print( brcrlf );
out.print( a1 +"showcart\" > Show Cart (" + nitem +
" items)</a> " );
out.print("<a href=\"" + checkoutalias +
"?action=initial\" >Checkout Now</a> \r\n");
}
out.println( a1 + "showcatalog&select=all\" > Full Catalog</a> ");
String[] prodL = CatalogBean.getCat().getProductLineNames();
for( int i = 0 ; i < prodL.length ; i++ ){
out.print( a1 + "showcatalog&select=" + prodL[i] + "\" >");
out.println( " " + prodL[i] + " </a> ");
}
out.print( a1 + "selectkeyword&select=all\" > Search </a>" );
out.print("</center>\r\n");
out.println("<hr><center>" + version + "</center>\r\n");
}
Метод doPageMid
Метод doPageMid управляет ответом сервлета на запрос пользователя. Значение параметра action определяет выбор метода представления, который, в свою очередь, генерирует требуемое представление. Как показано в листинге 4.7, последовательность инструкций if определяет, какой из методов вызывается.
Листинг 4.7. Метод doPageMid (CatalogServ.java)
public void doPageMid( HttpServletRequest req, HttpServletResponse resp,
PrintWriter out, HttpSession session ){
String action = req.getParameter("action");
String select = req.getParameter("select");
if( "showcatalog".equals( action )){
if( select == null || select.equals("all") ){
completeCatalog( out );
}
else {
productLineCatalog( out, select );
}
}
else if( "selectkeyword".equals( action )){
if( select == null || select.equals("all") ){
doKeywordSelect( out );
}
}
else if( "keywdsearch".equals( action )) {
String keyword = req.getParameter("keyword");
if( keyword != null ){
keywordCatalog( out, keyword );
}
}
else if( "showproduct".equals( action ) ||
"setcart".equals( action) ){
doShowProduct( req, resp, out, session, action );
}else if( "showcart".equals( action ) ){
doShowCart( req, resp, out, session, action );
}
}
В методах completeCatalog (листинг 4.8) и productLineCatalog (листинг 4.9) используется один и тот же подход к генерированию таблицы, содержащей соответствующий список товаров (либо полный, либо одну серию). Основное различие заключается в том, что в методе completeCatalog, как видно на рис. 4.1, в таблице имеются три столбца — по одному на каждую серию товаров. Для выбора одной из этих серий следует вызвать метод setlnitialSelect класса CatalogBean. В случае если у вас имеется более четырех серий товаров, следует искать какой- нибудь другой метод отображения каталога, так как таблица с пятью и более столбцами будет выглядеть не слишком хорошо.
Текст HTML, относящийся к конкретному товару, будет иметь вид, подобный следующему:
<а class="ch3"
href="http://localhost/serviet/catalog?асtion=
showproduct&id=bk0022">
Guide to Plants </a>
<span class="ch4">price ea = $12.99 </span>
Этот код делает название товара активной ссылкой, которая отсылает параметры action и id сервлету, что ведет к отображению полной информации о данном товаре. Атрибут class контролирует тип, размер и цвет шрифта различных фрагментов текста путем выбора той или иной таблицы стилей из файла catalog.css.
Листинг 4.8. Методы doKeywordSelect и completeCatalog (CatalogServ.java)
public void doPageMid( HttpServletRequest req, HttpServletResponse resp,
PrintWriter out, HttpSession session ){
String action = req.getParameter("action");
String select = req.getParameter("select");
if( "showcatalog".equals( action )){
if( select == null || select.equals("all") ){
completeCatalog( out );
}
else {
productLineCatalog( out, select );
}
}
else if( "selectkeyword".equals( action )){
if( select == null || select.equals("all") ){
doKeywordSelect( out );
}
}
else if( "keywdsearch".equals( action )) {
String keyword = req.getParameter("keyword");
if( keyword != null ){
keywordCatalog( out, keyword );
}
}
else if( "showproduct".equals( action ) ||
"setcart".equals( action) ){
doShowProduct( req, resp, out, session, action );
}else if( "showcart".equals( action ) ){
doShowCart( req, resp, out, session, action );
}
}
Метод productLineCatalog, как показано в листинге 4.9, формирует таблицу, содержащую только один столбец. После того как внешний вид таблицы HTML определен, переменная line используется для вызова метода setlnitialSelect, который выбирает одну из серий товаров. Затем мы просто совершаем итерации по выбранному списку товаров и используем метод doListOutput для отображения форматированного текста HTML по каждому товару. Затем мы закрываем таблицу. Типичный результат представлен на рис. 4.3.
Листинг 4.9. Метод productLineCatalog (CatalogServ.java)
public void productLineCatalog( PrintWriter out, String line ){
CatalogBean cb = new CatalogBean();
out.println("<h2>" + line + " Catalog</h2>");
out.println("<table width=\"90%\" border=\"3\" align=\"center\" >");
out.println("<thead><tr><th>" + line + "</th>" + "</tr></thead>");
out.println("<tbody><tr valign=\"top\"><td>");
String link = alias + "?action=showproduct" ;
cb.setInitialSelect( line );
int ct = cb.getSelectedCount();
out.println("We have " + ct + " items." + brcrlf );
cb.setOutput("short", link);
for( int i = 0 ; i < ct ; i++ ){
out.println( cb.doListOutput(i) );
out.println( brcrlf );out.println( brcrlf );
}
out.println("</td></tr></table>");
}
![](image/metod-dopagemid_1.gif)
Рис. 4.3. Отображение одной серии товаров
В методе keywordCatal og (листинг 4.10) применяется тот же принцип, но выбор происходит на основе переменной keyword, которая передается в качестве параметра методу setKeywordSel ect класса CalatogBean.
Листинг 4.10. Метод keywordCatalog отображает только элементы, содержащие выбранное ключевое слово (CatalogServ.java)
public void keywordCatalog( PrintWriter out, String keyword ){
CatalogBean cb = new CatalogBean();
out.println("<h2>Selected by " + keyword + " Catalog</h2>");
out.println("<table width=\"90%\" border=\"3\" align=\"center\" >");
out.println("<thead><tr><th>" + keyword + " </th>" + "</tr></thead>");
out.println("<tbody><tr valign=\"top\"><td>");
String link = alias + "?action=showproduct" ;
cb.setKeywordSelect( keyword );
int ct = cb.getSelectedCount();
out.println("We have " + ct + " items." + brcrlf );
cb.setOutput("short", link);
for( int i = 0 ; i < ct ; i++ ){
out.println( cb.doListOutput(i) );
out.println( brcrlf );out.println( brcrlf );
}
out.println("</td></tr></table>");
}
Методы doGet и doPost
Все запросы, выполняющиеся методом GET, просто перенаправляются методу doPost, как показано в листинге 4.5. Метод doPost объединяет стандартный раздел HEAD файла HTML и результаты выполнения трех методов — doPageTop, doPage- Mid и doPageEnd. Заметим, что обращение к методу getSession с параметром true приводит к созданию нового сеанса, то есть объекта HttpSession, если он еще не существует для данного пользователя.
Для того чтобы продемонстрировать методику отладки, мы проверим, является ли данный сеанс новым. Если сеанс окажется новым, мы присоединим объект CartListener и напечатаем идентификатор нового сеанса. Функции объекта CartLis- tener мы обсудим позже. Все инструкции, связанные с отладкой, будут удалены из финальной версии приложения, так как конструирование и вывод на печать объекта Date требуют больших затрат времени.
Листинг 4.5. Методы doGet и doPost (CatalogServ.java)
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost( req, resp );
}
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
resp.setContentType("text/html");
PrintWriter out = new PrintWriter(resp.getOutputStream());
outputHead( out );
HttpSession session = req.getSession(true);
if( session.isNew() ){
session.putValue( "listener", new CartListener() );
// session.setAttribute( "listener", new CartListener() );
System.out.print("Session is new " + session.getId() +
" " + new Date().toString() );
}
try {
doPageTop( req, resp, out, session );
doPageMid( req, resp, out, session );
doPageEnd( req, resp, out, session );
}catch( Exception e ){
e.printStackTrace( out );
}
out.println("</body>");
out.println("</html>");
out.close();
}
private void outputHead( PrintWriter out ){
out.println("<html>");
out.println("<head><title>Catalog Information</title>");
out.println( cssLink );
out.println("</head>\r\n<body>");
}
// compose and output all material at the top of the page
public void doPageTop( HttpServletRequest req, HttpServletResponse resp,
PrintWriter out, HttpSession session ){
out.print("<h1>XMLgifts</h1>");
out.print("<h2>Your Site Navigation Could Go Here</h2>\r\n");
}
В этом примере метод doPageTop очень прост, но в реальном коммерческом сайте этот метод можно использовать для отображения средств навигации по сайту и выполнения других функций.
Методы, вызываемые методом doFullItem
Теперь рассмотрим методы, которые используются для полного описания товара. Эти методы, приведенные в листинге 4.18, вызываются методом doFullItem класса CatalogBean.
Листинг 4.18. Методы, используемые для полного описания товара (Product Formatter.java)
public String doImageTag( Element el ){
NodeList nl = el.getElementsByTagName( "image" );
int ct = nl.getLength();
if( ct == 0 ) { return null ;
}
Element img = (Element)nl.item(0);
StringBuffer sb = new StringBuffer( );
addText( sb, "image", img );
return sb.toString();
}
// element is the complete product
public String doProdName( Element el ){
NodeList nl = el.getElementsByTagName( "name" );
if( nl.getLength() == 0 ) return "";
StringBuffer sb = new StringBuffer( );
sb.append( "<span class=\"");
sb.append( "ch3" ); sb.append("\">");
addText( sb, "prname", (Element)nl.item(0) );
sb.append( " </span><br />");
return sb.toString();
}
// element is the complete product
public String doAuthorArtist( Element el ){
NodeList unl = el.getElementsByTagName( "author" );
NodeList rnl = el.getElementsByTagName( "artist" );
if( rnl.getLength() == 0 &&
unl.getLength() == 0 ) return "";
StringBuffer sb = new StringBuffer( );
int i ;
int ct = rnl.getLength();
if( ct > 0 ){
sb.append("<span class=\"au1\" >" );
if( ct == 1 ) sb.append( "<i>Artist:</i> " );
else sb.append("<i>Artists:>/i> ");
for( i = 0 ; i < ct ; i++ ){
addText( sb, "artist",(Element) rnl.item(i) );
if( ct > 1 && ( i + 1) < ct ) sb.append(", ");
}
sb.append("<br />");
}
ct = unl.getLength();
if( ct > 0 ){
sb.append("<span class=\"au1\" >" );
if( ct == 1 ) sb.append( "<i>Author:</i> " );
else sb.append("<i>Authors:</i> ");
for( i = 0 ; i < unl.getLength() ; i++ ){
addText( sb, "author",(Element) unl.item(i) );
if( ct > 1 && ( i + 1) < ct ) sb.append(", ");
}
sb.append("<br />");
}
return sb.toString();
}
// element is the complete product
public String doDescription( Element el ){
NodeList nl = el.getElementsByTagName( "description" );
if( nl.getLength() == 0 ) return "No Description Available";
StringBuffer sb = new StringBuffer( );
addText( sb, "description",(Element) nl.item(0) );
return sb.toString();
}
public String doPrice( Element el ){
NodeList nl = el.getElementsByTagName( "price" );
if( nl.getLength() == 0 ) return "Contact XMLgifts";
StringBuffer sb = new StringBuffer( );
addText( sb, "price", el );
return sb.toString();
}
Отображение полной информации о товаре
Метод doShowProduct класса Catal ogServ отвечает за отображение полной информации об одном конкретном товаре. Существует несколько вариантов формата этого отображения в зависимости от наличия в X ML-каталoгe ссылки на изображение этого товара и от того, находится ли уже данный товар в корзине покупателя.
На рис. 4.4 приведено типичное представление, сгенерированное для товара, которого еще нет в корзине покупателя и для которого имеется изображение. Кнопка, расположенная в правом нижнем углу, предназначена для добавления в корзину этого товара. При щелчке на этой кнопке генерируется запрос к сервле- ту, параметр action принимает значение setcart, а параметр itemct — значение 1, в результате в корзине оказывается один экземпляр данного товара.
![](image/otobrazhenie-polnoj-informacii-o-tovare_1.gif)
Рис. 4.4. Отображение полной информации о товаре вместе с его изображением
![](image/otobrazhenie-polnoj-informacii-o-tovare_2.gif)
Рис. 4.5. Информация о товаре без его изображения
Если товар уже был заказан покупателем и находится в корзине, то страница с информацией о нем включает поле текущего количества заказанных экземпляров товара и позволяет его изменять. Пример такой страницы показан на рис. 4.5, причем для данного товара не предусмотрено изображения. Если пользователь щеллкает на кнопке Change (Изменить), в запросе к сервлету значение параметра action будет равно setcart, а значение itemct — введенному числу.
Теперь рассмотрим подробно, как устроен метод doShowProduct, код которого приведен в листинге 4.11. Этот метод отображает подробную информацию о данном товаре и позволяет добавлять товар в корзину. В первую очередь этот метод должен получить экземпляр класса Choppi ngCart. В приведенном коде используется метод getAttribute класса HttpSession, который является предпочтительным по отношению в устаревшему методу getValue. Закомментированная строка показывает, как использовать метод getValue, если в вашем случае процессор сервлетов ориентирован на версию 2.1 API.
Когда вызывается метод doShowProduct, в объекте HttpServletRequest всегда будет содержаться параметр id, который однозначно идентифицирует отображаемый товар.
Когда пользователь щелкает на кнопке (такой, как на рис. 4.4 или 4.5), вызывается метод doShowProduct со значением параметра action, равным setcart. В этом случае значение параметра itemct интерпретируется как новое количество заказанных экземпляров данного товара.
Когда вызывается метод doShowProduct с параметром action, равным showproduct, количество заказанных товаров (numberOrdered) не изменяется.
Листинг 4.11. Метод doShowProduct, который отображает информацию о товаре и позволяет заказывать товар (CatalogServ.java)
public void doShowProduct( HttpServletRequest req, HttpServletResponse resp,
PrintWriter out, HttpSession session, String action ){
ShoppingCart cart = (ShoppingCart)session.getValue("cart");
// older servlet engines use getValue
// ShoppingCart cart = session.getAttribute("cart"); // API 2.2
if( cart == null ){ // presumably the first pass
cart = new ShoppingCart();
session.putValue("cart", cart ); // older
//session.setAttribute( "cart",cart ); // API 2.2
}
out.print( brcrlf );
CatalogBean cb = new CatalogBean();
String id = req.getParameter( "id" );
if( "setcart".equals( action ) ){
String tmp = req.getParameter("itemct");
int itemct = 0;
try {
itemct = Integer.parseInt( tmp );
}catch(NumberFormatException e){
System.out.println("doShowProduct " + e );
}
CartItem item = cart.getProdById( id );
if( item == null ){
item = cb.createCartItem( id );
cart.addItem( item );
}
item.setNumberOrdered( itemct );
cart.removeEmptyItems();
}
out.print("<table width=\"70%\" border=\"3\" align=\"center\" >\r\n");
cb.doFullItem( id, out, cart, alias );
out.print("</table>\r\n");
}
Если action = showcart, то вызывается метод doShowCart. Как показано в листинге 4.12, этот метод переписывает теги HTML, которые задают начало и конец таблицы. Строки таблицы заполняются с помощью метода doCartList класса CatalogBean.
Листинг 4.12. Метод doShowCart (catalogServ.java)
public void doShowCart( HttpServletRequest req, HttpServletResponse resp,
PrintWriter out, HttpSession session, String action ){
ShoppingCart cart = (ShoppingCart)session.getValue("cart");
// older servlet engines use getValue
// ShoppingCart cart = session.getAttribute("cart"); // API 2.2
if( cart == null ){
out.println("Serious problem with session data" + brcrlf );
return ;
}
CatalogBean cb = new CatalogBean();
String link = alias + "?action=showproduct" ;
cb.setOutput("short", link);
out.print("<table width=\"90%\" border=\"3\" align=\"center\" >");
cb.doCartList( out, cart );
out.print("</table>\r\n");
}
Проблема корзины покупателя
Для посетителей виртуального магазина интуитивно понятна аналогия «корзины покупателя» (shopping cart). Поэтому им кажется, что все должно происходить так же, как и в реальном магазине, где корзина, в которую они складывают выбранные товары, остается с ними в течение всего времени посещения магазина, пока они не подошли к кассе и не заплатили за покупки. К сожалению, то, что посетителю виртуального магазина представляется как протяженный во времени единый процесс просматривания информации, для web-сервера является последовательностью не связанных друг с другом запросов, поступающих от браузера, и высылаемых ему ответов. Так получается потому, что основные HTTP- протоколы неустойчивы, то есть не позволяют определить, что очередной запрос должен быть связан с предыдущим, поскольку оба запроса поступают от одного пользователя.
Задача сохранения информации в течение одного сеанса (то есть в течение времени посещения данным пользователем данного сайта) обычно называется задачей сеансового мониторинга (session-tracking). Существуют три способа решения этой задачи: файлы cookie, скрытые поля (переменные) формы и перезапись URL.
Файл cookie — это фрагмент текстовой информации, который ассоциируется браузером с адресом конкретного web-сайта Web-сервер посылает данные (cookie) вместе с ответом, а web-браузер хранит полученный текст. Каждый раз, когда web-браузер посылает запрос этому же web-серверу, файл cookie присоединяется к одному из заголовков сообщения, а сервер отыскивает и интерпретирует cookie. Текст файла cookie имеет хорошо знакомую вам форму атрибут = значение, похожую на имена и значения атрибутов XML.
Очень полезное свойство файла cookie заключается в том, что программист имеет возможность контролировать время жизни этого фрагмента данных. Вы можете создать файл cookie, который будет существовать только в течение одного посещения сайта пользователем, в течение фиксированного промежутка времени или иметь неограниченный срок действия. Коммерческие web-сайты часто используют файлы cookie с длительным сроком действия для того, чтобы запоминать пользователей, часто посещающих данный сайт.
Интерфейс прикладных программ (API) сервлетов Java предоставляет средства для того, чтобы посылать и принимать данные файлы cookie, и тем самым решает многие задачи мониторинга сеанса. Тем не менее здесь имеются определенные ограничения, связанные с тем, что web-браузер лимитирует объем текста, содержащегося в файле cookie, и общее число таких файлов, связанных с одним сайтом. Поэтому хранение всей информации о корзине пользователя в виде файла cookie непрактично, так что обычно в файле cookie хранится только уникальный идентификатор, который присваивается данному пользователю и используется как ключ для поиска на сервере требуемой информации об этом пользователе.
При использовании скрытых полей формы для мониторинга сеанса ваша программа должна заносить скрытые данные в каждую форму или ссылку на каждой HTML-странице. Например, если нам нужно отследить значение переменной customer Id, в каждой форме должен содержаться следующий элемент:
<input type="hidden" name="customerid" value="124c41">
Кроме того, каждая ссылка должна включать в себя соответствующий фрагмент:
href= "http://Iocalhost/servlet/catalog?customerid=124c41"
Здесь символ ? разделяет URL и строку запроса.
Значение customer id затем извлекается в методе сервлета doPost или doGet с помощью кода, подобного следующему:
String customerid = req.getParameter("customerid")
В технологии, известной под названием перезаписи URL (rewriting URL), к каждому URL-адресу, идентифицирующему данный сеанс, добавляется дополнительная информация. Выбранный для этого формат не должен мешать web- серверу пересылать запрос на правильный сервлет, но сервлет должен иметь возможность обнаружить эту дополнительную информацию. В языке Java перезапись URL осуществляется с помощью точки с запятой (;). Таким образом, в результате URL выглядит так:
href= "http://Tocalhost/servlet/catalog;jsessiornd=124c41"
Для мониторинга сеанса в сервлете ShoppingCart мы используем интерфейс API класса HttpSession. Этот класс позволяет процессору сервлетов детально управлять мониторингом сеанса с минимальным участием программиста.