Поиск книг в интернет магазине Болеро

Поиск DVD в интернет магазине DVD DOM
Внимание! Money-Hunting.Ru - Интересная Online Игра с Денежными Призами.

Содержание

  • Введение
  • Глава 1. Описание данных с помощью XML
  • Глава 2. Каталог товаров на XML
  • Глава 3. Представление XML-каталога в сети
  • Глава 4. Заполнение корзины покупателя
  • Глава 5. Оплата и подтверждение заказа
  • Глава 6. Обслуживание виртуального каталога
  • Глава 7. Поиск своего покупателя с помощью опросов
  • Глава 8. Новости на сайте
  • Глава 9. Привлечение постоянных посетителей
  • Глава 10. Web-приложения на Java
  • Приложение А. Интерфейсы API для сервлетов и JSP-страниц
  • Приложение Б. Словарь терминов
  • Программный код для всех глав книги


  • Глава 1. Описание данных с помощью XML
    • Введение в XML
      • XML и электронная коммерция
      • Области применения XML
        • XML на стороне клиента
        • XML на стороне сервера
        • Использование XML для хранения данных
    • Правила XML
      • Определение правильно оформленного документа XML
      • DTD и допустимость документа XML
        • Объявления элементов
        • Спецификация содержимого
        • Объявления атрибутов
        • Объявления сущностей
      • Непроверенные символьные данные
        • Разделы символьных данных
        • Комментарии в XML
        • Инструкции по обработке
      • Схема XML
    • Создание таблиц стилей с использованием XSL
    • Использование XML в приложениях
      • Модели программирования
        • Программирование на основе DOM
        • Программирование на основе SAX
    • Краткий справочник по правилам XML
      • Требования к правильно оформленному документу XML
      • Элементы
        • Применение
        • Объявление
      • Атрибуты
      • Сущности
        • Применение
        • Объявления

Глава 1.

Описание данных с помощью XML

Если вы программируете на Java, за последние пару лет вам наверняка часто приходилось слышать об XML. Эта глава поможет вам максимально быстро освоиться с технологией XML и с сопутствующей терминологией, так что вы вскоре сможете оценить все преимущества XML. Когда вы начнете разрабатывать приложения с использованием XML, разделы «Краткий справочник по правилам XML» и «Правила XML» данной главы могут пригодиться вам в качестве справочного пособия по XML.

Введение в XML

Расширяемый язык разметки (Extensible Markup Language, XML), созданный в 1996 году Консорциумом W3C (World Wide Web Consortium), является подклассом стандартного языка разметки (Standard Generalized Markup Language, SGML). XML был задуман как гибкий и в то же время формальный метаязык для использования в Интернете.

Метаязык (metalanguage) — это язык, предназначенный для описания других языков. Например, можно сказать, что словарь английского языка в совокупности с английской грамматикой образуют метаязык, описывающий английский язык.

Что касается языка XML, то назначение его — описывать языки разметки. В языке разметки (markup language) для структурирования данных используются теги. Язык гипертекстовой разметки (Hypertext Markup Language, HTML), наиболее распространенный на сегодняшний день язык разметки, исходно был написан на SGML, но мог бы быть и в какой-то степени был написан и на XML.

Язык HTML был задуман для решения специфических задач — разметки документов научной и академической направленности. Если вы занимались какой-либо деятельностью по разработке web-страниц, то хорошо знаете, что в настоящее время HTML «трещит по всем швам», так как на протяжении многих лет делаются попытки дополнить его так, чтобы он соответствовал всем запросам web-программистов. Многие называют XML заменой HTML, но это не совсем точное высказывание.

В то время как HTML содержит фиксированный набор тегов, в XML теги вообще отсутствуют. Вместо этого XML позволяет программисту самому создать такой язык разметки, который в точности соответствует требованиям конкретного приложения. В нашей книге мы проиллюстрируем процесс создания приложения, относящегося к области электронной коммерции. Язык разметки, который мы будем использовать, содержит теги, выглядящие осмысленно именно в этой области (такие, например, как <price> и <quantity>) [Price — цена, quantity — количество. — Примеч. перев. ].

В приложениях XML обычно используются следующие типы данных и вспомогательные функции:

  • сам файл XML, имеющий строго определенную структуру;

  • определение типа документа (Document Type Definition, DTD), где определяется структура файла XML (необязательный элемент);

  • таблицы стилей, содержащие информацию о том, как данные должны быть отформатированы при выводе (необязательный элемент);

  • процессор XML и различные служебные функции для манипулирования данными и переформатирования данных.

XML и электронная коммерция

Для разработчиков web-приложений привычными являются рассуждения о том, что делают с текстом те или иные теги. Мы привыкли к тому, например, что тег <b> выделяет соответствующий текст жирным шрифтом. Тем не менее фактически этот тег не имеет какого-либо собственного значения. Как будет отображен текст, помеченный тегом <b>, полностью зависит от программы, анализирующей данные. В случае HTML такой программой обычно является web-браузер. Поскольку документы HTML создаются обычно в предположении, что их будут читать с помощью web-браузера, многие теги HTML указывают, как следует форматировать данные, но не содержат никакой информации для обычного человека. Назначение XML заключается как раз в том, чтобы отделить данные, содержащиеся в документе, от кода, который задает формат отображения этих данных. Это свойство XML позволяет извлекать данные из документа автоматически, то есть с помощью программных средств.

К примеру, предположим, что вы — торговый посредник и занимаетесь продажей электрических лампочек. На вашем web-сайте приводится самая свежая информация о ценах на товары от различных производителей. Вместо того чтобы вручную проверять web-сайты этих производителей и таким образом узнавать информацию о ценах, вы решаете написать программу, которая автоматически бы считывала цены, добавляла бы 10 % (надбавка для вас) и отображала бы информацию о товарах на вашем web-сайте. Пусть одним из ваших поставщиков является фирма ABC Lightening. В листинге 1.1 приводится часть кода HTML для таблицы, содержащей данные о товарах из web-сайта ABC Lightening [Авторы книги в листингах глав 1 и 2 приводят вымышленные описания товаров, которые не следует воспринимать всерьез. — Примеч. перев. ].

Листинг 1.1. Код HTML для таблицы, содержащей информацию о товарах [Все представленные в книге тексты программ можно найти на сайте издательства по адресу www.piter.com. — Примеч. ред. ]

<table>

<tr>

<th>Название товapa</th>

<th>0писание</th>

<th>Ценa</th>

</tr>

<tr>

<td><b>Фонарик</b>

</td>

<td>Свет, который всегда с вами!</td>

<td>$9.95</td>

</tr>

<tr>

<td><b>Неоновая лампа</b></td>

<td>Ничто не скажет слово "класс" так,
как неоновая лампа!</td>

<td>$14.75</td>

</tr>

</table>

Документ HTML только определяет, как должен быть отформатирован данный текст. Автоматическое извлечение информации из статической страницы HTML даже в лучшем случае окажется весьма непростой задачей. Если вы хотите написать программу, которая бы извлекала данные о цене из приведенного кода HTML, вы можете указать, что цена фонарика присутствует в третьем столбце таблицы в той строке, где в первом столбце стоит слово «фонарик». Но при этом вы рискуете тем, что ваша программа перестанет работать, если изменится дизайн web-сайта ABC Lightening или просто поменяется название данного товара.

Вполне вероятно, что некоторые фирмы-производители динамически создают свои web-сайты на основе информации из баз данных. В таком случае вы можете договориться с web-мастером каждого сайта об обмене информацией прямо из баз данных, но этот процесс требует времени и может происходить по- разному для каждой базы данных, из которой вам требуется получить информацию. Было бы гораздо проще, если сам документ был бы организован так, чтобы из него было легко извлекать содержательную информацию.

В листинге 1.2 приведен пример того, как та же информация может быть представлена с помощью XML [Catalog — каталог, product — товар, name — название, description — описание. — Примеч. перев ].

Листинг 1.2. Документ XML, содержащий информацию о товарах

<?xml version="l.0"  standalone="no"?>

<!DOCTYPE ABC_Lighting: catalog SYSTEM  "catalog.dtd">

<ABC_Ughting: catalog xmlns:ABC_Lighting =

"http://www.abclighting.com">

<ABC_Lighting:product>

<ABC_Lighting:name>Фонарик</ABC_Lighting:name>

<ABC_Lighting:description>Cвeт,  который всегда с вами!

</ABC_Lighting:description>

<ABC_Lighting:price>$9.95</ABC_Lighting:price>

</ABC_Lighting:product>

<ABC_Lighting:product>

<АВС_Lighting:name>Неоновая лампа</ABC_Lighting:name>

<ABC_Lighting:description>Ничто не скажет слово "класс"
так, как неоновая лампа!</ABC_Lighfmg:description>

 <ABC_lighting:price>$14.75</ABC_Lighting:price>

</ABC_Lighting:product>

</ABC_Lighting:catalog>

Первая строка этого документа — объявление XML, которое содержит информацию, предназначенную для анализатора XML. Объявление XML (XML declaration) идентифицирует тип документа и версию XML, которая использовалась при создании документа. Эта строка не является обязательной, но, как правило, именно с нее начинается документ XML. Атрибут standalone = "no" означает, что данный документ снабжен DTD. Следующая строка — это объявление типа документа (document type declaration), которое указывает, какому DTD соответствует этот документ. В данном случае используется определение DTD, называемое catalog.dtd. Обратите внимание на то, что, хотя аббревиатуры совпадают, имеется большая разница между определением типа документа (сокращенно DTD — Document Type Definition) и объявлением типа документа. Объявление типа документа используется, чтобы указать, какому определению типа документа соответствует данный документ XML.

В объявлении типа документа также указывается корневой элемент документа. Корневой элемент (root element) — это элемент, который включает в себя все остальные элементы документа. В данном случае корневым является элемент ABC_Lightening:catalog. Часть имени элемента, расположенная перед двоеточием, идентифицирует пространство имен тега. Пространства имен не являются обязательными, но их можно использовать для того, чтобы гарантировать уникальность тегов. Если фирма ABC_Lightening начнет продавать товары других производителей, то пространства имен помогут избежать возможной путаницы, связанной с тем, что появятся элементы данных, внешние по отношению к этой фирме, с теми же названиями, но иначе структурированные.

Ниже показано, как может выглядеть определение catalog.dtd для гипотетического каталога товаров:

<!ELEMENT ABC_Lightening:catalog (product)>

<!ELEMENT ABC_Lightening:product (name, description?, price+)>

<!ELEMENT ABC_Lightening:name (#PCDATA)>

<!ELEMENT ABC_Lightening:description (#PCDATA)>

<!ELEMENT ABC_Lightening:price (#PCDATA)>

Это DTD показывает, какие элементы могут появиться в каталоге, а также определяет порядок их следования и число появлений. С использованием данных XML и DTD автоматическая идентификация и извлечение полезных данных из документа XML становятся довольно простым делом, в чем вы убедитесь, прочитав эту книгу.

WEB-РЕСУРСЫ XML

Следующие web-ресурсы содержат информацию о последних разработках в области XML, планах на будущее и инструментальных средствах, полезных при работе с XML:

консорциум W3C (www.w3.org);

O'Reilly&Associates, Inc.'s XML.com (www.xml.com) — один из лучших сайтов в Сети, посвященных коммерческому применению XML;

XML Industry Portal, (www.xml.org);

xmlhack (www.xmlhack.com) — новости для web-разработчиков;

Enhydra (www.enhydra.org) — домашняя страница сервера Enhydra, посвященного приложениям Java/XML;

консорциум Unicode (www.unicode.org).

Области применения XML

XML можно применять как на стороне сервера, так и на стороне клиента. Следующие два раздела рассказывают о принципах применения XML в каждой из этих областей. Кроме того, XML можно использовать для хранения данных, о чем говорится в третьем разделе.

XML на стороне клиента

На стороне клиента XML позволяет достичь такого уровня соответствия конкретным условиям представления данных, которого очень трудно или невозможно достичь с использованием HTML. Например, для таких устройств, как PDA (Personal Digital Assistant — «карманный» компьютер, предназначенный для выполнения некоторых специальных функций) или мобильный телефон, требуется, чтобы страницы были отформатированы совсем не так, как для стандартных web-браузеров. Обычно, если даже имелась готовая страница, предназначенная для web-браузера, для подобных устройств приходилось полностью ее переделывать, то есть фактически создавать новую версию этой страницы. Однако благодаря структурированным данным документа XML, в котором содержательные данные отделены от форматирующих указаний, все, что вам надо сделать для приведения страницы в соответствие с каждым конкретным отображающем ее прибором, — это применить к имеющимся данным нужную таблицу стилей.

XML на стороне сервера

В наши дни XML оказывает очень большое влияние на организацию работы сервера. Один из способов применения XML на стороне сервера — передача сообщений (messaging), то есть обмен данными между приложениями или компьютерами. Чтобы приложения и компьютеры могли обмениваться информацией, для них должен быть определен единый формат сообщений. Представить себе то огромное влияние, которое XML может иметь в этой области, невозможно, не зная, хотя бы в общих чертах, истории передачи сообщений. Выбор стандарта для этого остается проблемой с тех времен, когда люди начали общаться между собой, но здесь я имею в виду только последние 30 лет.

Электронная коммерция, согласно определению европейского семинара, посвященного технической поддержке электронной коммерции (European Workshop on Open System's Technical Guide on Electronic Commerce, EWOS TGEC 066), включает в себя такие разнообразные области, как маркетинг, поддержка логистики, проведение деловых операций и взаимодействие с административными органами (например, обмен данными по налогам и таможенным отчислениям). Механизм EDI (Electronic Data Interchange), предназначенный для обмена данными в электронной форме, начинает свою историю с 1970-х годов, когда он был впервые предложен Комитетом по координации передачи данных (Transportation Data Coordinating Committee, TDCC). В таких отраслях деятельности, как финансовая, где сетевые технологии начали применяться уже более тридцати лет назад, EDI служил в качестве стандартного формата обмена сообщениями. Недостаток систем EDI заключается в том, что их установка и поддержка обходятся дорого и, кроме того, они часто требуют выделенных линий.

В 1980-х годах началось бурное распространение и внедрение в фирмах систем электронной почты для рабочих групп. По мере того как производители пытались утвердить свои варианты таких систем в качестве стандарта, все больше фирм переходило на использование в своем бизнесе электронной почты. Такие пакеты, как Microsoft Mail и Lotus cc:Mail, позволяли небольшим компаниям обмениваться электронными сообщениями в пределах своей внутренней сети, но при попытке увеличить ее охват обычно начинались проблемы, управление сетью становилось все более трудной задачей. Также оказалось сложным объединить с внешним миром локальную сеть (Local Area Network, LAN). Результат, как и всегда в отношении компьютерных технологий, заключался в том, что передача сообщений становилась все более децентрализованной. По мере того как передача электронных сообщений отходила от первоначальной централизованной, строго контролируемой системы, требующей выделенных линий связи, в эту технологию вовлекалось все больше пользователей. Децентрализация привела также к огромным проблемам в отношении совместимости различных форматов, к дублированию разработок в области электронной коммуникации и к невозможности организованного ее развития.

Ко времени появления Интернета все уже очень хорошо понимали необходимость стандартизованного и в то же время гибкого способа организации передачи сообщений как между отдельными людьми, так и между компаниями, занимающимися электронной коммерцией. Широко доступный стандартный формат электронных сообщений имел бы огромное влияние на любой вид обмена информацией, как коммерческой, так и некоммерческой.

В первую очередь нужно было договориться о языке, и здесь XML оказался наиболее подходящим кандидатом. Основная причина, по которой XML прекрасно подходит для создания форматов передачи сообщений, — это его простота. XML подчиняется строго определенному стандарту, он не связан с какой-либо операционной системой или производителем, он совместим с большим количеством инструментальных средств и приложений, которые на протяжении многих лет разрабатывались для SGML. Требование строгого соответствия документов XML стандартам, установленным для правильно оформленных (well-formed) документов, гарантирует, что любой анализатор XML будет в состоянии прочесть и осмыслить любой документ XML. Кроме того, гораздо больше людей знакомы с языками разметки, чем с форматами сообщений, необходимыми для построения систем EDI. Благодаря XML формат сообщений может разработать любой, кто способен составить правильно оформленный документ XML.

Другой областью применения XML в web-документах является определение метасодержимого. Метасодержимое, или сведения о содержимом, позволяет сделать работу поисковых машин гораздо эффективнее. Например, пусть вам нужно найти сообщения о последних событиях в городе Остин (Austin) штата Техас. Тогда вы задаете поисковой машине следующие слова:

Остин Техас новости

Поскольку большинство поисковых машин в настоящее время просто индексируют все содержимое сайта, имеется большая вероятность, что в результате такого поиска вам будет предложено множество не нужных вам документов, найденных по случайному совпадению. Если бы страницы, посвященные новостям из Остина, были написаны в виде структурированных документов XML, их поиск был бы гораздо более целенаправленным — вы могли бы указать, например, следующие параметры:

City = Austin,  State = ТХ,  StoryType = News.

Использование XML для хранения данных

XML подходит и для создания баз данных. В документе XML используется древовидная структура хранения данных. Хотя по большому счету хранение данных в виде документов XML не слишком эффективно, у такого способа хранения есть свои преимущества. Как и в отношении передачи сообщений, самым большим преимуществом является простота. Древовидная структура — интуитивно понятный и знакомый способ организации данных. Кроме того, почти любой тип древовидной структуры — от реляционных баз данных до объектно-ориентированных баз данных и иерархических структур — может быть представлен с помощью дерева данных XML. Другое существенное преимущество использования XML для хранения данных заключается в том, что XML поддерживает набор символов Unicode. Следовательно, любой символ любого алфавита мира можно включить в документы XML на «законном основании».

Unicode — это официальный путь реализации универсального набора символов (Universal Character Set, UCS), определенного Международной организацией по стандартизации (International Standards Organization, ISO); иначе говоря, это универсальный стандарт кодировки символов для электронного представления текста и его компьютерной обработки. Для преобразования кодировок символов в фактический набор битов используются форматы преобразования UCS, или сокращенно — UTF (UCS Transformation Formats).

Спецификация XML требует, чтобы процессоры XML поддерживали два формата UTF: UTF-8 и UTF-16. В UTF-16 используются два байта для представления каждого символа. В UTF-8 для символов ASCII используется кодировка ASCII, занимающая один байт, а для символов, не входящих в ASCII, — кодировка переменной длины. Формат UTF-8 полезен, если вы хотите поддерживать совместимость с ASCII. Недостатком этого формата является то, что для представления остальных символов (не входящих в ASCII) в нем может потребоваться от 1 до 3 байтов. Если ваш текст в основном состоит из ASCII-символов, UTF-8 позволит вам сэкономить объем памяти. Если же вы используете другие символы, то этот формат, напротив, потребует излишних затрат. По умолчанию в XML применяют формат UTF-8. Кодировка документа определяется в объявлении XML с помощью специального атрибута кодировки (encoding), как показано в следующем примере:

<?xml version="1.0" standalone='no' encochng="UTF-8"?>

Правила XML

В, настоящее время HTML-браузеры, как правило, делают попытки отобразить любой документ, даже содержащий ошибки или использующий очень старую версию HTML. С другой стороны, процессоры XML должны сообщать о неисправимой ошибке в случае, если они встречают ошибку в разметке. Неисправимая ошибка (fatal error) означает, что приложение не может выполняться далее, и выдает сообщение об ошибке. Такая строгая обработка ошибок иногда называется драконовской (draconian error-handling). Хотя этот способ обработки ошибок для тех, кто создает свои документы на HTML или на SGML, может показаться примитивным, в случае с XML он необходим, так как гарантирует, что любые XML-анализаторы будут воспринимать данный документ XML одинаково.

Документ XML, соответствующий правилам синтаксиса XML, называется правильно оформленным (well-formed). Авторы XML включили требование об этом в спецификацию XML, чтобы предотвратить для XML опасность стать жертвой так называемых «войн браузеров». Результатом таких «войн» между Microsoft и Netscape стало то, что теперь при создании HTML-документа авторам приходится постоянно заботиться о совместимости. Если бы такая история приключилась и с языком XML, он стал бы совершенно бесполезным.

Процессор XML (XML processor) — это программный модуль, который обеспечивает доступ приложений к данным, хранящимся в документах XML. Процессоры XML могут быть как проверяющими, так и не проверяющими допустимость (validating) документа. Проверяющий допустимость документа процессор определяет, существует ли для данного XML-документа DTD, и в случае положительного результата — соответствует ли структура документа правилам, указанным в DTD. Не проверяющий допустимость документа процессор должен удостовериться только в том, что документ соответствует правилам XML.

Определение правильно оформленного документа XML

Весь текст XML-документа можно грубо разделить на две категории, символьные данные и разметку. К разметке (markup) относится все, что начинается с символа < и заканчивается символом > или начинается с символа & и заканчивается парой символов &;. Символьные данные (character data) — это все, что не является разметкой. Символьные данные можно подразделить еще на две категории: проверенные символьные данные (Parsed Character DATA, PCDATA) и непроверенные символьные данные (unparsed character data). Как следует из названия, данные PCDATA — это данные, проверенные анализатором XML.

В листинге 1.3 показан пример правильно оформленного документа XML [Beverage — напиток, manufacturer — производитель, nutntion_facts servmg_size= "1 can' — содержание питательных веществ в одной бутылке, calories — калории, amount unit="g" — количество в граммах, fat — жиры, sodium — натрий, carb — углеводы, protein — белки — Примеч перев ]:

Листинг 1.3. Правильно оформленный документ XML

<?xml version="1.0" standalone="yes"?>

<beverage>

<name>Вода в бутылках</name>

<manufacturer>

<name>Напитки высшего качества</name>

<url href = "http.//www.extragoodbev com"/>

</manufacturer>

<nutrition_facts serving_size="1 can">

<calories>

<amount unit="g">0</amount>

</calones>

<fat>

<amount umt="g">0</amount>

</fat>

<sodium>

<amount unit="g">0</amount>

</sodium>

<carb>

<amount unit="g">0</amount>

</carb>

<protein>

<amount umt="g">0</amount>

</protein>

</nutntion_facts>

</beverage>

В первую очередь в этом листинге следует отметить, что в объявлении XML присутствует атрибут standalone="yes". Это означает, что в данном документе нет определения DTD. Наличие DTD в документах XML не является обязательным. Фактически приложения, в которых задействованы данные XML, часто не включают DTD в целях повышения эффективности в тех случаях, когда структура документа и возможность его многократного использования не являются важными факторами.

Следом за объявлением XML идут элементы. Элемент (element) — это наиболее распространенная форма разметки; он выделяется с помощью угловых скобок (< и >) и описывает тот фрагмент данных, который заключен между скобками > и <. Элемент состоит из открывающего и закрывающего тегов (<beve- rage>...</beverage>, например). Имя элемента называется его общим идентификатором (Generic Identifier, GI) или типом (type). Текст между открывающим и закрывающим тегами называется содержимым (content) элемента. Например, типом следующего элемента является book (книга), а название книги (Java Developer's Guide to XML) — его содержимым:

<book> Java Developer's Guide to XML </book>

Элемент, лишенный содержимого, называется пустым элементом (empty element). Открывающий и закрывающий теги пустого элемента можно объединить в один тег, поместив в его конец косую черту: <br/>. В XML также допускается запись пустого элемента с помощью открывающего и закрывающего тегов, например <br></br>.

Фактически существуют два типа пустых элементов: те, которые заранее определены как пустые и в принципе не могут иметь содержимого, и те, которые оказались пустыми случайно. Чтобы различить эти два типа элементов, рекомендуется использовать пару (открывающий тег, закрывающий тег) для тех элементов, которые не содержат данных, и один тег для тех элементов, которые определены как пустые.

Например, тег HTML <br> не может иметь содержимого, поэтому его нужно записывать как <br/> Если же, к примеру, в вашем документе XML имеется экземпляр элемента, который в данный момент не имеет содержимого, но может получить его в дальнейшем, следует использовать стандартный синтаксис, то есть два тега [Cupboard — шкаф — Примеч перев ]:

<cupboards></cupboards>

У элементов могут быть атрибуты. Атрибут (attribute) — это пара имя-значение, расположенная в открывающем теге элемента. В следующем примере src, width и height являются атрибутами элемента img [Image — изображение, width — ширина, height — высота — Примеч перев ].

<img src = "balloons.gif" width= "100" height = "100" />

СОВЕТ

Если вы хотите, чтобы ваш код HTML был совместим с XML, то обратите внимание, что тег HTML переноса строки <br> способен причинить немало хлопот. Некоторые браузеры не воспринимают тег <br/>, а пару тегов <br></br> воспринимают как два переноса строки. Для решения этой проблемы поместите пробел между символами br и косой чертой: <br />.

В XML значения атрибутов должны быть заключены в одинарные или двойные кавычки. Список правил, которым должен соответствовать правильно оформленный документ XML, расположен в конце этой главы.

DTD и допустимость документа XML

DTD, или определение типа документа, — это способ явным образом определить структуру класса документов XML Например, в DTD для перечня животных может быть задано, что для каждого животного нужно указать его имя, вид и характерный звук, который издает это животное DTD для этого перечня может выглядеть так [Animal-list - список животных, animal — животное, name — имя, type — вид, sound — звук. — Примеч перев]:

<!ELEMENT animal-list  (ammal)*>

<!ELEMENT animal  (name,type,sound)>

<!ELEMENT name  (#PCDATA)>

<!ELEMENT type  (#PCDATA)>

<!ELEMENT sound  (#PCDATA)>

Если бы это определение DTD было стандартным зоологическим определением (фактически это не так), любой зоолог мог бы быть уверен, что его данные сможет прочесть любой другой зоолог и что для составления перечня животных все зоологи используют одни и те же правила Документ XML, который соответ- ствует правилам DTD, то есть какому-то конкретному определению, так же как и общим правилам XML, называется правильно оформленным и допустимым. Ниже показан пример правильно оформленного документа XML, который соответствует приведенному выше определению DTD:

<?xml version="l 0"  standalone="no"?>

<!DOCTYPE animal-list  SYSTEM  "zoology.dtd">

<animal-list>

<animal>

<name>Бесси</name>

<type>Kopoвa</type>

<sound>My-y</sound>

</ammal>

<animal>

<name>Ровер</name>

<type>Собака</type>

<sound>Гав</sound>

</animal>

</animal-list>

ПРИМЕЧАНИЕ

Слова, набранные в предыдущих примерах только прописными буквами, являются ключевыми словами XML. Это делается не просто из стилистических соображений. Язык XML чувствителен к регистру, поэтому процессор XML выдаст сообщение об ошибке, если в ключевом слове окажется хоть одна строчная буква. Таким образом, в XML слова DOCTYPE и Doctype не более похожи между собой, чем слова DOCTYPE и EGGDROP.

Объявления элементов

Основным базовым типом объявления в DTD является объявление элемента, <!ELEMENT>. Формат объявления элемента:

<!ELEMENT имя_элемента правило>

Каждый элемент, который используется в документе XML, должен быть определен в DTD. Существует несколько правил, которым нужно следовать при именовании элементов:

  • имена элементов не должны содержать символа < или >;

  • имя элемента должно начинаться с буквы или символа подчеркивания. После первой буквы в имени элемента может содержаться любое количество букв, цифр, дефисов, точек или символов подчеркивания;

  • имена элементов не могут начинаться с последовательности xml (в любой комбинации верхнего или нижнего регистров),

  • двоеточия запрещены, они применяются только в пространствах имен

Спецификация содержимого

В объявлении элемента вы указываете, что может появиться в качестве содержимого этого элемента Если вам нужно объявить элемент, который не должен содержать никаких данных, вы можете использовать тип EMPTY (например, <!ELMENT img EMPTY>).

Хороший пример пустого элемента — элемент HTML img Чтобы этот элемент не нарушал принципа допустимости документа XML, нужно использовать синтаксис, определенный для пустого элемента XML:

<img src="mycar.jpg"  />

Если вам нужно, чтобы в элементе были только данные PCDATA, используется следующее объявление:

<!ELEMENT mymemoirs #PCDATA>

Вы можете также указать, какие типы элементов могут появиться в элементе, в каком порядке и в каком количестве, как показано в следующем примере [My memoires — мои воспоминания, title — название, author — автор, philosophizing — философствования, sad_story — грустная история, funny_story — смешная история, lesson — наставление, conclusion — вывод — Примеч nepee ]:

<!ELEMENT mymemoires  (

title,  author,  philosophizing, sad_story,

funny_story,  lesson, conclusion)>

В данном примере каждый из приведенных типов элементов должен появиться один раз (и только раз) внутри элемента mymemories в том порядке, в котором они перечислены в объявлении.

ПРИМЕЧАНИЕ

Элементы, которые появляются в элементе mymemoires, называются его дочерними элементами, а сам элемент mymemoires называется родительским. Каждый элемент может быть дочерним по отношению к любому количеству других элементов в документе. Элементы, разделенные более чем одним уровнем в иерархической системе, называются внуками, правнуками и т. д. (или соответственно дедами, прадедами — в другом направлении). Также можно при описании взаимоотношений между элементами использовать термины «предок» и «потомок».

Вы можете создать более гибкие правила, используя операторы повторяемости. Ниже показаны три оператора:

  • ? — элемент должен встретиться один раз или не встретиться ни разу;

  • + — элемент должен встретиться один или более раз;

  • * — элемент может встретиться любое количество раз или не встретиться вовсе.

Ниже снова приведено объявление элемента mymemoirs, переписанное с использованием операторов повторяемости:

<!ELEMENT mymemoires  (title,  author,  philosophizing+,

sad_story*,  funny_story*,

lesson*, conclusion)>

Вы также можете указать, что некоторые элементы могут появиться как альтернатива другим элементам (то есть что между ними нужно делать выбор) с помощью вертикальной черты (|):

<!ELEMENT mymemoires  (title,  author,  philosophizing+,

sad_story*,  funny_story*,

lesson+|  conclusion)>

В этом объявлении элемент mymemoirs может содержать один или более элементов lesson или conclusion, но не оба одновременно. Еще более сложные правила можно создавать с использованием вложенных скобок. В листинге 1.4 показано, как может выглядеть определение mymeroirs.dtd [Paragraph - абзац, letter — письмо. — Примеч. перев. ].

Листинг 1.4. Полная версия определения mymeroirs.dtd

<!ELEMENT mymemoirs  (title,  author,  philosophizing*,

sad_story*,  funny_story*,

(lesson+  |  conclusion)*)>

<!ELEHENT title (#PCDATA)>

<!ELEMENT author (#PCDATA)>

<!ELEMENT philosophizing (paragraph)*>

<!ELEMENT sad_story (paragraph*, letter*,
(lesson | conclusion)*)>

<!ELEMENT funny_story (paragraph*, letter*.

(lesson | conclusion)*)>

<!ELEMENT letter (paragraph)*>

<!ELEMENT lesson (paragraph)*>

<!ELEMENT conclusion  (paragraph)*>

<!ELEMENT paragraph  (#PCDATA)>

Наименее строгим является правило, которое можно выразить словами: «подходит что угодно». Если вы хотите указать, что в данном элементе могут появиться любые проверенные символьные данные или элементы, вы должны использовать ключевое слово ANY (любой), как показано в следующем примере:

<!ELEMENT mymemoirs ANY>

ПРИМЕЧАНИЕ

Такое широкое правило, как элемент типа ANY, не вполне вписывается в строгую структуру XML. Вообще говоря, если вы создаете определение DTD, в котором используется ключевое слово ANY, вы, вероятно, делаете ошибку и вам следует поискать лучший способ определения.

Объявления атрибутов

Атрибуты используются для связывания пар имя-значение с элементами. Они определяются с помощью специальных объявлений атрибутов. Формат объявления атрибута в DTD следующий [Target_element — элемент, к которому относится данный атрибут, type — тип, default_value — значение по умолчанию. — Примеч. перев. ]:

<!ATTLIST target_element name type default_value ?>

Атрибуты используются, чтобы сообщить дополнительную информацию об элементах. Иногда бывает трудно решить, является ли некоторый фрагмент данных элементом или атрибутом. Например, два следующих фрагмента XML можно использовать для достижения одной цели:

<dog name = "Snuggles"></dog>

или

<dog>

<name>Snuggles</name>

</dog>

Хотя выбор остается за вами, имеется несколько общих принципов. Мы расскажем о них в главе 2, а сейчас вам просто следует запомнить, что этот выбор является непростой задачей для всех программирующих на XML.

Ниже приведены некоторые примеры объявлений атрибутов (несколько позже будет объяснено их значение) [Gender (male|female) — пол (мужской|женский), species — вид, «Cams familiaris» — «собака обыкновенная». — Примеч. перев.]:

<!ATTLIST dog name CDATA #REQUIRED>

<!ATTLIST dog gender  (male|female) #IMPLIED>

<!ATTLIST dog species #FIXED "Canis familiaris">

Существует девять различных типов атрибутов, которые подразделяются на три категории: строковые (string), маркерные (tokenized) и перечислимые (enumerated). Строковые атрибуты определяются с помощью ключевого слова CDATA, которое указывается в качестве типа атрибута, как показано в следующем примере:

<!ATTLIST dog name CDATA>

Значением этой строки может быть любая корректная строка символов.

Существует несколько типов маркерных атрибутов. Наиболее важными являются ID и IDREF. Атрибуты типа ID используются для однозначной идентификации элементов. Атрибут ID должен однозначно идентифицировать тот элемент, в котором он содержится. Например, в следующем объявлении задается обязательный атрибут ID, используемый для идентификации товара:

<iATTLIST product id ID #REQUIRED>

Атрибуты ID и IDREF можно использовать почти так же, как теги якоря А в HTML Значением атрибута IDREF должно быть значение атрибута ID какого-либо другого элемента (то есть они задают перекрестную ссылку). Например, в следующем фрагменте DTD объявляется элемент с атрибутом ID и элемент с атрибутом IDREF, который ссылается на первый элемент [Featured_products — ключевые товары, product_reference — ссылка на товар — Примеч перев]:

<!ELEMENT product  (name.description,price)>

<!ATTLIST product id ID #REQUIRED>

<!ELEMENT featured_products  (product_reference)*>

<!ELEMENT product_ref  (#PCDATA)>

<!ATTLIST product_ref link IDREF #IMPLIED>

В файле XML, который использует это DTD, может содержаться фрагмент, подобный следующему:

<product id= "X4343">

<name>rock</name>

</product> <featured_products>

<product_ref link = "X4343">a rock</product_ref>

</featured_products>

В атрибутах перечислимых типов приводится список всех возможных значений этого атрибута. Например, если вы хотите объявить атрибут с именем angle_type для элемента, названного triangle, можно указать возможные значения следующим образом [Triangle — треугольник, angle_type — тип угла, obtuse|acute|nght — тупои|острыи|прямой — Примеч перев]:

<!ATLIST triangle angle_type (obtuse | acute | right)? #REQUIRED>

Существует несколько ключевых слов, которые можно использовать, чтобы указать, должен ли этот атрибут обязательно содержаться в элементе и должен ли он принимать какое-то фиксированное значение. В следующей небольшой таблице приведены эти ключевые слова и указано их значение. Если вы не включите в объявление атрибута ни одно из этих ключевых слов, то по умолчанию будет подразумеваться слово IMPLIED

Ключевое слово | Определение
#REQUIRED | Этот атрибут должен присутствовать в каждом экземпляре элемента
#IMPLED | Такой атрибут может не указываться в конкретных экземплярах элемента. Никакого значения для такого атрибута по умолчанию не предусматривается
#FIXED | Этот атрибут должен присутствовать в каждом экземпляре элемента и иметь указанное значение

Объявления сущностей

Объявления сущностей позволяют использовать ссылки на сущность. Ссылка на сущность (entity) — это последовательность символов, которая автоматически подставляется вместо другой последовательности символов. Обычно ссылки на сущность требуются для того, чтобы обозначить символы, которые иначе могут быть приняты за символы разметки. Если вы имели дело с HTML, то, вероятно, сталкивались со ссылками на сущность. Наиболее распространенный тип сущности — это общая сущность (general entity), то есть сущность, которую можно подставить вместо символов в документах XML Формат объявления общей сущности следующий:<

lt;!ENTITY имя_сущности "заменяемые символы">

Ссылки на сущность имеют вид &имя_сущности; В XML имеется пять встроенных сущностей. Их не обязательно объявлять в DTD, хотя в спецификации XML это сделать рекомендуется, чтобы гарантировать возможность взаимодействия с HTML и SGML. Пять встроенных сущностей перечислены в приведенной ниже таблице:

Ссылка на сущность | Числовой заменитель | Символ
&amp; &lt; &gt; &apos; &quot; | &#38; &#60; &#62; &#39; &#34; | & < > ' "

Эти сущности можно объявить следующим образом:

<!ENTITY lt "&#38,#60,">

<!ENTITY gt "&#62,">

<!ENTITY amp "&#38,#38,">

<!ENTITY apos "&#39,">

<!ENTITY quot "&#34,">

ПРИМЕЧАНИЕ

Символы < и & в объявлениях It и amp дважды экранируются (escaped), чтобы обеспечить выполнение требований о хорошем оформлении документа XML. Другими словами, символы & и < — это два символа, которые сообщают процессору XML, что следующий текст — это фрагмент разметки. Если эти символы не экранировать дважды в объявлениях сущности, то процессор XML будет интерпретировать их как начало нового фрагмента разметки, не дойдя до конца объявления сущности, и сгенерирует ошибку.

Встроенные ссылки на сущности необходимы для создания документов XML, в которых любой из приведенных символов используется сам по себе, а не как фрагмент разметки Ссылки на общие сущности, которые вы определяете самостоятельно, удобны для присваивания имен последовательностям символов, которые вы часто используете. Например, чтобы объявить ссылку на сущность, представляющую символ торговой марки (™), можно включить следующее объявление:

<!ENTITY tm  "&#8482">

Символ торговой марки можно затем вставить в любой документ XML, в котором имеется DTD с этим объявлением. Например:

<product_name>

Super Dnnk&tm;

</product_name>

Хотя сущности можно использовать в определениях других сущностей, при этом следует постоянно учитывать основной принцип: ссылки не должны быть циклическими.

Ниже приведен пример некорректного определения сущностей:

<!ENTITY myentity "please see &myotherentity,  ">

<!ENTITY myotherentity "please see &myentity;  ">

А это — корректное определение:

<!ENTITY tm  "&#8482">

<!ENTITY myentity "I enjoy Super Dnnk&tm;  ">

Объявление параметрических сущностей

Также можно объявлять ссылки на сущности, которые в DTD будут заменяться определениями сущности. Такой тип сущностей называется параметрическим. Ссылка на параметрическую сущность (parameter entity) начинается с символа * и не может присутствовать внутри документа XML — только в DTD, где она определяется. Ниже приведен пример использования параметрической сущности:

<!ENTITY % actors  "  (Joe,  Mary, Todd,  Bill,  Jane)*  ">

<!ELEMENT dialog %actors;>

Объявление внешних сущностей

Внешние сущности (external entities) — это способ включить внешние файлы в документ XML. Они объявляются следующим образом:

<!ENTITY latest_pnces SYSTEM

"http://www.getthepncesofthings.com/today.xml">

После объявления внешней сущности вы можете включить содержимое указанного документа XML в ваш документ, используя ссылку на сущность — в данном случае &latest_prices.

Объявление непроверенных сущностей

Непроверенные сущности (unparsed entities) можно использовать для включения в документ XML данных в формате, отличном от XML. Для определения такой сущности используется ключевое слово NOATA. Например:

<!ENTITY bookcover SYSTEM

"http://www.sybex.com/books/xml/javadevguide.gif" NDATA gif>

Непосредственно за ключевым словом NDATA следует ключевое слово нотации (notation data keyword). Это ключевое слово объявляется с помощью объявления нотации. Объявления нотации (notation declarations) предоставляют дополнительную информацию (например, для идентификации) или, как в данном случае, сведения о формате для непроверенных данных. Ключевое слово нотации определяется с помощью объявления <!NOTATION>. Например:

<!NOTATION gif SYSTEM

"-//CompuServe//NOTATION Graphics Interchange Format 89a//EN">

Непроверенные символьные данные

Проверенные символьные данные не могут содержать разметку. Следовательно, если вы хотите включить в содержимое элемента символы < или &, вам следует использовать для этого управляющую последовательность. Вы можете заменить эти символы их числовыми заменителями (&#60 и &#38 соответственно) или использовать встроенные в XML ссылки на сущности (&#lt и &#атр соответственно). Имеется еще один вариант, который позволит вам обойтись без управляющей последовательности, — использовать раздел символьных данных (CDATA section), чтобы пометить блок текста как непроверенные символьные данные.

Разделы символьных данных

Разделы символьных данных начинаются со строки <![CDATA[ и заканчиваются строкой ]]>. Кроме строки ]]>, содержимое раздела символьных данных не подвергается синтаксическому анализу. Если вы хотите включить пример кода XML в документ XML, вместо того чтобы заменять все символы & и It на &lt и &атр, можете включить весь блок, содержащий эти символы, в раздел символьных данных. Например:

<example>

Это пример правильно оформленного документа XML:

<!CDATA[

<?xml version="1.0" standalone="yes"?>

<beverage>

<name>Super-Drink</name>

<manufacturer>

<name>Extra Good Beverages</name>

<url href = "http://www.extrasuperbev.com"/>

</raanufacturer>

</beverage>

]]>

</example>

Комментарии в XML

Комментарии (comments) в XML устроены и функционируют так же, как в

HTML, что видно из следующего примера:

<!-этот текст является комментарием.-->

Комментарии могут располагаться в любом месте документа, но не внутри разметки. Также комментарии не могут употребляться внутри объявлений. Комментарии не являются частью символьных данных и не могут использоваться анализатором XML.

В HTML комментарии часто используются в качестве контейнера текста, который не является частью документа, но доступен для программ. Например, команды CGI и JavaScript в документах HTML часто помещаются внутрь комментариев. Но анализаторы XML могут полностью игнорировать комментарии, поэтому в XML этим приемом не следует увлекаться, для этого имеются снециальные инструкции по обработке.

ВНИМАНИЕ

Мой опыт подсказывает, что попытка сэкономить время на комментариях обычно приводит к тому же эффекту, что перевод часов на летнее время. Так же как за сэкономленный час светлого времени приходиться расплачиватся, несколько дней приспосабливаясь к новому режиму, так и пренебрежение комментариями ради экономии времени впоследствии принесет вам массу проблем.

Инструкции по обработке

Инструкции по обработке (Processing Instructions, PI) используются для того, чтобы включить в документ информацию, предназначенную для приложений. Подобно комментариям, инструкции по обработке не считаются частью символьных данных документа. Но в отличие от комментариев, инструкции по обработке не игнорируются анализатором XML, а передаются приложениям.

Инструкции по обработке начинаются с символов <? и заканчиваются символами ?>. Первое слово в PI — это имя того приложения, для которого предназначается данная инструкция. Также можно использовать имя нотации, чтобы связать URI (Uniform Resource Identifier, универсальный идентификатор ресурса) с именем приложения. Следом за идентификационной информацией в PI может содержаться любой тип символьных данных. Ниже приведен пример PI:

<?playsound sounds.mp3?>

Эта инструкция инициирует воспроизведение соответствующим приложением файла формата трЗ.

Схема XML

Хотя определения DTD в настоящее время являются стандартом определений типов документов XML, у них есть несколько серьезных ограничений. Определения DTD были унаследованы от языка SGML, где они были исходно разработаны для определения языков разметки, а не для создания схем баз данных. Самое существенное ограничение определения DTD заключается в том, что оно не обеспечивает достаточного контроля над содержимым элементов. Например, с помощью DTD невозможно указать, что показанный ниже элемент является допустимым [Today's date — сегодняшняя дата — Примеч. перев. ]:

<todaysdate>09/01/2000</todaysdate>

Точно так же нельзя указать, что следующий элемент не является допустимым:

<todaysdate>Яйца, тост, кофе</todaysdate>

Кроме того, вам может потребоваться задать более точные ограничения на количество появлений элемента в документе. С помощью DTD этого сделать не удастся.

В результате этих ограничений, а также из-за того, что XML все больше используется для хранения данных, были предложены некоторые альтернативные варианты. В настоящее время среди них лидирует язык определений схем XML (XML Schema Definition Language, XSD).

ПРИМЕЧАНИЕ

На момент написания данной книги XSD находится в стадии разработки. Это означает, что к моменту, когда этот язык получит официальный статус, в нем могут произойти значительные изменения по сравнению с нынешней версией. Со спецификацией XSD можно ознакомиться по адресу www.w3.org/XML/Schema.html.

Назначение схем XML то же, что и DTD: определение классов документов XML. Основная разница между ними заключается в том, что в схемах XML все элементы подразделяются на два типа: простые и сложные.

Элементы, которые содержат другие элементы или атрибуты наряду с символьными данными, относятся к типу сложных (complex), а те элементы, которые содержат только символьные данные, называются простыми (simple) элементами. Атрибуты всегда имеют простой тип. В листинге 1.5 показана схема XML для каталога товаров. В листинге 1.6 приведен документ XML, использующий эту схему [On sale date — дата начала продаж, quantity in stock — количество имеющегося в наличии товара. — Примеч. перев. ].

Листинг 1.5. Схема для каталога (catalog.xsd)

<xsd:schema xmlns:xsd="http://www.w3.org/1999/XMLSchema">

<xsd:element name="Catalog" type="CatalogType"/>

<xsd:complexType name="CatalogType">

<xsd:element name="product" type="ProductType"/>

<xsd:attribute name="onSaleDate" type="xsd:date"/>

<xsd:attribute name="partNum"  type="Sku"/>

</xsd:complexType>

<xsd:complexType name="ProductType">

<xsd:element name="productName" type="xsd:string"/>

<xsd:element name="quantity_in_stock">

<xsd:simpleType base="xsd:positivelnteger">

<xsd:maxExclusive value="500"/>

</xsd:simpleType>

</xsd:element>

<xsd:element name="pnce"  type="xsd'decimal"/>

<xsd:element name="description"  type="xsd:string" minOccurs="0"/>

</xsd:complexType>

<xsd:simpleType name="Sku" base="xsd:string">

<xsd:pat tern value="\[A-Z]{3}-[A-Z]{3}d{3}"/>

</xsd:simpleType>

</xsd:schema>

<catalog>

<product partNum="ABC-PR0336" onSaleDate="12/12/2004">

<productName>BigSoft Xtreminator 3.36</productName>

<quantity_in_stock>20</quantity_in_stock >

<price>195.99</price>

<description>Managing your life has never been
so easy.</description>

</product>

<product partNura="ABC-PR0343" onSaleDate="12/12/2004">

<productName>E-Dev ProntoWorks</productName>

<quanti ty_in_stock>35</quantity_in_stock >

<price>299.99</price>

<description>The premier integrated rapid e-development
suite for busy e-professionals.</description>

</product>

</catalog>

Элементы сложного типа определяются с помощью элемента compl exType. Как было уже сказано, элементы сложного типа содержат другие элементы и атрибуты. Элементы и атрибуты, содержащиеся внутри сложных элементов, определяются с помощью элементов element и attribute соответственно. Например, в листинге 1.5 элемент product определен как элемент сложного типа. Внутри определения типа ProductType определяются пять элементов: productName, quantity_in_stock, price, comment и partNum.

Элементы простого типа не имеют атрибутов и не содержат других элементов. В XSD имеется ряд встроенных простых типов, в том числе string, binary, boolean, double, float и т. д. На основе встроенных простых типов можно сконструировать дополнительные простые типы. В частности, в предыдущем примере определяется простой тип Sku, сконструированный на основании типа string.

Процесс конструирования новых простых типов из уже существующих называется ограничением (restriction). Обратите внимание, что в определении нового типа Sku используется регулярное выражение, задающее шаблон, которому должно соответствовать содержимое любого элемента или атрибута данного типа.

В схемах XSD имеются гораздо более гибкие операторы повторяемости, чем в DTD. Как говорилось выше, в DTD можно указать, что элемент должен встретиться ноль, один, один или более или любое количество раз. В дополнение к этим операторам схема XML позволяет задавать минимальное и максимальное количество повторов одного элемента в документе, его значение или диапазон значений и т. д.

Создание таблиц стилей с использованием XSL

Расширяемый язык таблиц стилей (Extensible Stylesheet Language, XSL), как следует из его названия, — это язык для конструирования таблиц стилей. Таблицы стилей XSL используются для описания внешнего вида документов XML, предназначенных для чтения людьми.

Например, web-дизайнер может создать таблицу стилей для XML-каталога товаров. В этой таблице стилей может быть указано, какие шрифты, размеры шрифтов и границ нужно использовать в документе, что фактически выполняется, когда сам документ объединяется с таблицей стилей с помощью процессора XSL.

Применение таблицы стилей к документу процессор таблиц стилей осуществляет в два этапа. Первый этап — трансформация дерева (tree transformation). Вы можете, например, написать таблицу стилей, которая расположит товары в вашем каталоге в алфавитном порядке или пронумерует их, прежде чем отобразить. Трансформация дерева позволяет также перемещать данные XML и выполнять вычисления с этими данными.

Второй этап — форматирование (formatting). Форматирование фактически представляет собой процесс задания стиля отображения данных, размеров шрифтов, разрывов страниц и т. п.

Спецификация XSL содержит три различных языка, предназначенных для осуществления этих двух задач:

  • XML Path Language (XPath) — язык для создания ссылок на различные части документа XML;

  • XSL Transformations (XSLT) — язык, предназначенный для генерации дерева документа;

  • Extensible Stylesheet Language (XSL) — XSLT плюс описание набора объектов форматирования и свойств форматирования.

Предположим, что у вас имеется документ XML, описывающий вашу музыкальную библиотеку, например такой, как представленный в листинге 1.7.

Листинг 1.7. Пример каталога музыкальной библиотеки (MyMusic.xml)

<?xml version="l.0"?>

<library>

<cd>

<title>Just Singin' Along</title>

<artist>The Happy Guys</artist>

<description>

A lovely collection of songs that the
whole family can sing right along with.

</description>

<song><title>I'm Really Fine</title></song>

<song><title>Can't Stop Grinnin'</title></song>

<song><title>Things Are Swell</title></song>

<purchase_date>2/23/1954</purchase_date>

</cd>

<cd>

<title>
It's Dot Com Enough for Me:
Songs From Silicon Somewhere
</title>

<artist>The Nettizens</artist>

<description>
A collection of the best folk music
from Internet companies.
</description>

<song><title>My B2B Is B-R-0-K-E</title></song>

<song><title>Workin' in a Cubicle</title></song>

<song><title>Killer Content Strategy</title></song>

<song><title>
She Took the Bricks.
I Got the Clicks
</title></song>

<purcnase_date>7/12/2000</purchase_date>

</cd>

</library>

Допустим, вы хотите создать и напечатать список всего, что имеется в вашей библиотеке. Один из способов сделать это заключается в том, чтобы применить к документу MyMusic.xml таблицу стилей, которая трансформирует его в формат HTML. В листинге 1.8 приводится такая таблица стилей.

Листинг 1.8. Таблица стилей, генерирующая документ HTML (CDstyle.xsl)

<?xml version="l.0"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">

<xsl:template match="/">

<TABLE STYLE="border:lpx solid black; width:300px">

<TR STYLE="font-size:10pt; font-family:Verdana;
font weight:bold; text-decoration:underline">

<TD>Title</TD>

<TD>Artist</TD>

</TR>

<xsl:for-each select="library/cd">

<TR STYLE="font-family:Verdana;  font-size:12pt; padding:0px 6px">

<TD><xsl lvalue-of select=" title"/></TD>

<TD><xsl:value-of select="artist"/></TD>

</TR>

</xsl:for-each>

</TABLE>

</xsl:template>

</xsl:stylesheet>

Связать таблицы стилей с документом XML можно с помощью инструкции по обработке. Например:

<?xml-stylesheet href="CDstyle.xsl"  type="text/xsl"?>

Также вы можете использовать каскадные таблицы стилей (Cascading Style Sheets, CSS) для применения определенного формата к данным XML. В этом случае вы должны связать документ с таблицей стилей с помощью инструкции по обработке, подобной следующей:

<?xml-stylesheet href="CDstyle.ess"  type="text/css"?>

В листинге 1.8 показан базовый пример трансформации данных XML с помощью стандартной таблицы стилей. XSLT находит данные, которые соответствуют некоторому образцу, и вставляет их в нужное место шаблона. Поиск соответствия с образцом — очень важная часть XSL. Посмотрим на образец, который применялся во второй части нашего примера:

<xsl:for-each selееt="library/cd">

Эта строка задает цикл по всем экземплярам элемента cd внутри элемента library. Если вы хотите создать разделенный запятыми список всех песен на каждом диске (каждому диску соответствует свой экземпляр элемента cd), вы можете задать еще один цикл внутри приведенного выше цикла, как показано в следующем примере:

...

<xsl:for-each select="library/cd">

<TR STYLE="font-family:Verdana;  font-size:12pt;  padding:0px 6px">

<TD><xsllvalue-of select="title"/></TD>

<TD><xsl:value-of select="artist"/></TD>

<TD>

<xsl:for-each select="song">

"<xsl :value-of select="title"/>"

<xsl:if test="context()[not(end())]">,  </xsl:if>

</xsl: for-each>

</TD>

</TR>

</xsl:for-each>

...

Если получившийся документ XML открыть в браузере, поддерживающем XSL, то он будет выглядеть примерно так, как показано на рис. 1.1.

Рис. 1.1. Результат применения таблицы CDstyle.xsl к файлу MyMusic.xml

Использование XML в приложениях

В данной книге мы будем использовать базу данных XML, предназначенную для хранения каталога товаров, которыми торгует вымышленный электронный магазин. Хотя мы предполагаем, что доступ к данным XML непосредственно из приложений осуществляется достаточно быстро, все же для повышения эффективности можно импортировать XML-базу данных приложения в реляционную базу данных. В настоящее время все поставщики баз данных предлагают средства (или планируют сделать это), позволяющие передавать данные между реляционной базой данных и документами XML, и ряд других вспомогательных инструментальных средств. Данные можно извлекать из любой базы данных и преобразовывать в данные XML, которые используются в приложении, при этом не приходится модифицировать само приложение. Вероятно, в этом и заключается основное преимущество создания приложений, рассчитанных на данные XML: XML — это стандарт, в который могут быть преобразованы данные любого типа, следовательно, приложение гарантированно сможет работать в будущем с данными любого нового типа.

Теперь, когда вы убедились в том, что XML — это правильный выбор для создания каталогов, рассмотрим два различных подхода к написанию программ на Java, предназначенных для работы с XML.

Модели программирования

Язык SGML был ориентирован на документ как на единое целое, поэтому нет ничего удивительного в том, что и в отношении XML сначала использовался подход, связанный с объектной моделью документа (Document Object Model, DOM). Любая обработка документа в соответствии с моделью DOM предполагает, что документ прошел синтаксический анализ и представлен в памяти в виде древовидной структуры, каждая часть которой одинаково доступна. Этот подход символически иллюстрирует рис. 1.2.

Рис. 1.2. Модель программирования DOM

Когда люди начали программировать в соответствии с моделью DOM, быстро выяснилось, что это не очень удобно — приходилось строить объектную модель всего документа, даже если нужно было отобрать всего лишь несколько элементов. Кроме того, этот способ требует больших ресурсов памяти, что может сделать его использование затруднительным, если не сказать невозможным. В результате возникла необходимость в разработке другого способа, который получил название SAX (Simplified API for XML — упрощенный интерфейс прикладного программирования для XML) И DOM, и SAX представляют собой, таким образом, интерфейсы прикладного программирования, которые были реализованы как на Java, так и на других языках.

Как показано на рис. 1.3, анализатор SAX осуществляет один проход по документу XML, сообщая о результатах анализа путем вызова различных методов из кода вашего приложения. В документации SAX есть термин событие (event): событие происходит, когда анализатор идентифицирует элемент в документе XML, поэтому указанные методы называются обработчиками событий (event handlers). Когда анализатор достигает конца документа, в памяти остается только то, что было сохранено вашим приложением.

Как было сказано ранее и как показано на рис. 1.2 и 1.3, использование DTD не является обязательным в XML.

Рис. 1.3. Модель программирования SAX

Для программирования сервлетов и JSP (JavaServer Pages) полезными могут оказаться обе модели, что демонстрируют приведенные ниже примеры. Сначала рассмотрим инструментальные средства Java для «первого уровня» DOM и для версии SAX 1.0. На момент написания книги версия SAX 2.O. и второй уровень DOM находились в разработке, но ко времени, когда книга будет опубликована, вероятно, эти новые версии уже будут доступны.

Программирование на основе DOM

Окончательная версия API для работы с объектной моделью документа находится в пакете org.w3c.dom, одобренном Консорциумом W3C. Этот интерфейс API включает в себя определения интерфейсов и один класс исключений. Основная идея заключается в том, что документ XML преобразуется в документ DOM, состоящий из объектов Java, которые реализуют эти интерфейсы. Любая часть документа становится объектом, а связи между объектами отражают иерархическую структуру документа.

Анализ XML для создания DOM

С точки зрения программиста, нет ничего проще, чем создать документ DOM, поскольку вся работа фактически делается анализатором. Все, что должен сделать программист, — это создать входной поток, выбрать анализатор и отойти в сторону. В листинге 1.9 показан шаблон метода, который считывает данные из файла с помощью утилит из пакета com.sun.xml.parser и возвращает объект com.sun.xml.tree.XmlDocument Класс Xml Document реализует интерфейс Document, как указано в спецификации W3C.

Если вы используете утилиты других пакетов, то конкретные имена могут измениться, но общий принцип останется прежним. В этом конкретном примере используются классы интерфейса прикладных программ Java для анализа XML (Java API for XML Parsing, JAXP), предложенные компанией Sun, которые в настоящее время используются в ядре сервлетов (servlet engine) Tomcat.

За последние годы было создано множество различных анализаторов XML, но только небольшое их число полностью соответствует рекомендациям DOM W3C. Последние тесты на совместимость показали, что наиболее высокий рейтинг в этом отношении имеет анализатор Sun.

Листинг 1.9. Шаблон метода создания объекта XmlDocument

public XmlDocument exampleDOM(String src  )  {

File xmlFile = new File( src )  ;

try {

InputSource input = Resolver create!nputSource( xmlFile );

//  ... флажок  "false" указывает,  что проверка не нужна

XmlDocument doc = XmlDocument.createXmlDocument  (input,  false);

return doc ;

}catch(SAXParseException spe ){

// здесь обрабатываются исключения,  возникшие при

// синтаксическом анализе

}catch( SAXException se ){

// здесь обрабатываются другие исключения SAX

}catch(  IOException ie ){

// здесь обрабатываются исключения ввода-вывода

}

return null ;

}

Имея в памяти объектную модель документа, вы можете манипулировать ею с помощью методов интерфейса DOM из пакета org.w3c.dom, а также с помощью дополнительных инструментальных средств.

Программирование на основе SAX

Основные этапы обработки документа XML на основе SAX можно сформулировать следующим образом.

  1. Создание одного или нескольких пользовательских классов для обработки событий анализатора SAX.

  2. Создание объекта, который обеспечивает прием входного потока символов.

  3. Создание анализатора на основе одного из пакетов инструментальных средств.

  4. Присоединение классов, обрабатывающих события, к анализатору.

  5. Присоединение входного потока к анализатору, начало анализа.

  6. Обработка всех событий в пользовательских классах, которая позволяет записать нужные данные, отследить ошибки и т. д.

Как видно, обработка документов XML в модели SAX подчиняется совсем другой идеологии, чем в модели DOM. Выбор одного из этих двух подходов для конкретного приложения — самый важный выбор из тех, которые вам предстоит сделать. В табл. 1.1 приведены основные критерии, которые следует учитывать в данном вопросе.

Таблица 1.1. Сравнение программирования на основе DOM и SAX

Фактор | DOM | SAX
Требования к ресурсам памяти | Могут оказаться довольно высокими | Пропорциональны количеству элементов, которые требуется сохранить в памяти
Скорость первой обработки | Невысокая, так как анализируются все элементы | Высокая, особенно если нужные элементы легко локализуются
Скорость повторной обработки | Сравнительно высокая, так как все элементы уже находятся в памяти | Сравнительно невысокая, так как каждый поиск подразумевает новый проход анализатора по документу
Допустимость модификации | Очень высокая | Ограничена необходимостью создания нового документа XML для каждого прохода анализатора

Краткий справочник по правилам XML

Этот раздел можно считать кратким справочником по самым основным правилам XML. Полную спецификацию XML можно найти по адресу www.w3c.org.

Требования к правильно оформленному документу XML

  • Каждый элемент должен иметь открывающий и закрывающий теги, за исключением пустых элементов, для которых предусмотрен специальный синтаксис пустого элемента.

  • Имя открывающего тега должно совпадать с именем закрывающего тега. Заметим, что язык XML чувствителен к регистру. Варианты:

    • неправильно: <Name></name>;
    • правильно: <name></name>.

  • Элементы должны быть правильно вложены друг в друга. Варианты:

    • неправильно. <р><b> какой-нибудь текст </р></b>;
    • правильно: <р><b> какой-нибудь текст </b></р>.

  • Имена элементов не должны содержать символа < или > и должны начинаться с буквы или символа подчеркивания

  • Имена элементов не могут начинаться с символов xml (в любой комбинации верхнего и нижнего регистров)

  • В именах элементов не должно содержаться двоеточия, за исключением пространств имен.

  • Атрибуты не должны появляться более одного раза в открывающем теге или в теге пустого элемента.

  • Значения атрибутов должны быть заключены в кавычки.

  • В значениях атрибутов не должны содержаться прямые или непрямые ссылки на внешние сущности.

  • Текст, подставляемый вместо любой сущности, на которую имеется прямая или косвенная ссылка, являющаяся значением некоторого атрибута, не должен содержать символа < (это не относится к сущности &lt;).

Элементы

Ниже мы приводим общие сведения об элементах XML, в том числе о синтаксических правилах, регламентирующих применение элементов и их объявление.

Применение

Примеры:

<tag/>

<tag attribute="value"/>

<tag attribute="value"> какой-нибудь текст </tag>

Объявление

Синтаксис:

<!ELEMENT имя_элемента правило>

Тип элемента | Пример объявления
EMPTY #PCDATA ANY Mixed Children | <! ELEMENT url EMPTY> <! ELEMENT name #PCDATA> <! ELEMENT contacts ANY> <! ELEMENT list (#PCDATA | item)*> <!ELEMENT co-worker (title, name, address)>

Атрибуты

Ниже мы приводим формат объявления атрибутов. Сюда относятся синтаксис объявления атрибутов, их типы и ключевые слова, определяющие, является ли атрибут обязательным, и т. п.

Синтаксис объявления атрибута:

<!ATTLIST target_element name type default_value ?>

Тип

Атрибут

Пример объявления

Строковый

CDATA

<!ATTLIST image url CDATA>
Маркерный ID

IDREF

IDREFS

ENTITY

ENTITIES

NMTOKEN

NMTOKENS

<!ATTLIST id ID #REQUIRED>

Перечислимый

<!ATTLIST 11st type (ordered | bullet) "ordered" ?>

Ключевое члово | Описание
#REQUIRED | Этот атрибут должен присутствовать в каждом экземпляре элемента
#IMPLIED | Такой атрибут может не указываться в конкретных экземплярах элемента. Никакого значения для такого атрибута по умолчанию не предусматривается
#FIXED | Этот атрибут должен присутствовать в каждом экземпляре элемента и иметь указанное значение

Сущности

Ниже мы приводим краткие сведения об использовании и объявлении сущностей. Сюда относятся примеры применения сущностей и синтаксические правила, регламентирующие их объявление.

Применение

Примеры:

Copyright &copy; 2001 Sybex Inc/

while( a %lt; b) // для представления кода Java в HTML

Объявления

Синтаксис:

<!ENTITY имя  "заменяемые символы">

Тип сущности | Пример | Описание
Общая | <! ENTITY publisher "Sybex"> | Может использоваться только в данных XML
Параметрическая | <! ENTITY *cdata "#CDATA"> | Может использоваться только в DTD
Внешняя | <! ENTITY stockquotes SYSTEM "quotes. xml"> | Используется для включения внешних файлов XML
Непроверенная | <! ENTITY picture SYSTEM "picture.jpg" NDATA jpg> | Используется для включения файлов, не являющихся файлами XML


  • Глава 10. Web-приложения на Java
    • Спецификации в интерфейсе API сервлетов Java
      • Развертывание web-приложения
        • Определение web-приложения
        • Структура папок
        • Содержимое папки WEB-INF
        • Дескриптор развертывания web-приложения
        • Параметры конфигурации
        • Архивные файлы web-приложения
    • Следующее поколение
      • Следующее поколение XML
      • Протокол SOAP
    • Проблемы масштабирования
      • Сохранность информации о сеансе
      • J2EE и Enterprise JavaBean
      • Альтернативное решение — Spaces

Глава 10.

Web-приложения на Java

Эта завершающая глава содержит рекомендации по установке web-приложений, написанных на языке Java, в соответствии с последними выпущенными SUN интерфейсами API для сервлетов Java и JSP-страниц.

Спецификации в интерфейсе API сервлетов Java

Ниже перечислены ресурсы, согласованная работа которых требуется, чтобы запустить на web-сервере сервлет Java или JSP-приложение:

  • HTML-страницы;

  • ресурсы мультимедиа;

  • файлы классов сервлетов Java;

  • совместно используемые библиотечные файлы Java;

  • файлы JSP;

  • параметры инициализации сервлетов;

  • параметры безопасности;

  • доступ к базам данных.

В ранних реализациях API для сервлетов Java не было указано, как организовать все эти ресурсы, поэтому производители создавали собственные системы. По мере того как сложность web-приложений возрастала, пользовательские интерфейсы для установки и управления приложениями становились весьма запутанными. Например, старая версия процессора сервлетов JRun, на одно поколение опередившая версию API 2.2, содержала 259 файлов с расширением .properties, разбросанных по системе папок, с трудом поддававшейся пониманию и напоминающей лабиринт.

Компания Sun вынуждена была разобраться с этой проблемой, работая над пользовательскими и программными интерфейсами J2EE (Java 2 Enterprise Edition — редакция Java 2 для корпоративных программных систем на базе Java) и EJB (Enterprise JavaBean — JavaBean для корпоративных систем). Первые выпуски этих технологий требовали создания дескриптора развертывания (deployment descriptor) для каждого интерфейса EJB. Такой механизм был неудобным и трудным для понимания.

Следующая версия EJB была полностью изменена компанией Sun по сравнению с предыдущей. В версии EJB 1.1 за передачу параметров установки при запуске отвечает специальный документ XML. Тот факт, что компания Sun проявила готовность полностью отказаться от существовавшего интерфейса API и предпочла решение, основанное на XML, еще раз подчеркивает значимость языка XML.

Развертывание web-приложения

Спецификация 2.2 сервлетов Java достаточно подробно развивает концепцию web-приложений. Web-приложение — это совокупность сервлетов, классов Java, JSP-страниц, HTML-страниц и других ресурсов, которые могут объединяться в единое целое и выполняться в различных контейнерах. Чтобы это стало возможным, web-серверы не должны, как прежде, применять свои собственные схемы создания папок и инициализации; напротив, они должны выполнять определенные соглашения по установке, утвержденные Sun.

Изменения в API для сервлетов ясно говорят о том, что разработчики Java серьезно задумались о вопросах обеспечения безопасности как в отношении ограничения доступа извне, так и в отношении установки барьеров между различными web-приложениями, а также внутри самих приложений. Например, в более ранних версиях API метод getServletNames из класса ServletConext позволил бы одному сервлету определить имена всех сервлетов, выполняющихся в конкретной папке приложения. Метод getServlet затем мог бы получить ссылку на фактический объект сервлета. Также имелся метод getServlets, который возвращал перечень всех экземпляров сервлетов.

Все эти методы в версии API 2.2 считаются устаревшими и больше не возвращают полезную информацию. Все программисты, разрабатывающие сервле- ты, теперь должны специально заботиться о том, чтобы обеспечить их взаимодействие. Если, например, какой-либо объект требуется всем сервлетам данного приложения, его следует присоединить к объекту ServletContext с помощью метода setAttribute, поскольку именно объект ServletContext совместно используется всеми сервлетами приложения.

Если вы привыкли задействовать статические методы и переменные класса, чтобы обеспечить совместное использование приложениями определенных ресурсов, вы должны учитывать, что в версии API 2.2 у каждого web-приложения на сервере имеется собственный загрузчик классов. Это означает, что невозможно организовать с помощью статических методов и переменных совместное использование web-приложенями каких бы то ни было ресурсов.

Определение web-приложения

На этап определения web-приложения API сервлетов не накладывает никаких ограничений. Производители имеют полную свободу в создании собственных систем для определения контекста приложения на сервере. Сервер Tomcat использует файл server.xml для определения элемента ContextManager, содержащего многочисленные элементы Context. Многие из основных серверных функций определены в элементах Context, и они также задействуются для определения web- приложений. Например, ниже мы приводим элемент Context, который мы использовали для данной книги:

 <Context path="/XMLbook" docBase="webapps/XMLbook" debug="0"
 reload,able="true" > </Context>

Таким образом, относительно исходной папки сервера устанавливается корневая папка в физической структуре файлов, которую web-сервер использует для приложения XMLbook. Папки, входящие в корневую папку, требуются серверу для хранения HTML-страниц, JSP-страниц и других ресурсов, связанных с этим приложением, как будет показано в следующем разделе.

Также вы можете определить параметры, которые будут доступны всем серв- летам или JSP-страницам данного приложения через объект ServletContext. Ниже приводится пример применения элемента Parameter:

 <Context path="/XMLbook" docBase=

"webapps/XMLbook" debug="0"
 reloadable="true" >
 <Parameter name="workdir" value="e \\scnpts\\XMLgifts"  />
</Context>

В Tomcat используется соглашение о том, что корневая папка вашего приложения и ее вложенные папки по умолчанию содержатся во вложенной папке ROOT, как указано в следующем объявлении Context:

 <Context path="" docBase="webapps/ROOT" debug="0"
 reloadable="true"  > </Context>

Папка ROOT содержит файл index.html, который автоматически отображается при вводе URL-адреса, например такого: http://localcost:8080/.

Структура папок

Атрибут doBase определяет базовое расположение файлов приложения относительно установочной папки Tomcat. Предположим, что установочная папка — c:\tomcat, тогда сервер предоставляет по умолчанию папку webapps для установки ваших web-приложений и полный путь к файлам приложения выглядит следующим образом:

 с: \tomcat\webapps\XM Lbook

Поступающие на web-сервер запросы, в которых используются URL-адреса типа hhtp://localhost/XMLbook, будут обслуживаться в физической структуре папок относительно указанного пути. Например, пусть пользователь запрашивает файл, указывая следующий URL-адрес:

http://localhost/XMLbook/catalog/index.html

Тогда web-сервер направит этот запрос в физический файл по адресу:

 c:\tomcat\webapps\XMLbookcatalog\index.html

Естественно, все запросы на таблицы стилей, файлы изображений и другие ресурсы будут выполняться подобным образом Здесь мы подходим к рассмотрению файла класса (class file), который используется апплетами Java и часто является причиной непонимания. Поскольку файл класса должен быть отправлен web-браузеру, как любой другой ресурс, класс апплета, или JAR-файл, должен храниться с простыми HTML-файлами, а не с файлами классов, используемыми сервлетами.

Чтобы отделить файлы классов и библиотеки классов, используемые приложением, от простых HTML-страниц, файлов изображений, файлов классов апп- летов и других ресурсов, в версии API 2.2 для сервлетов требуется, чтобы в папке приложения имелась вложенная папка WEB-IN F. Web-серверу запрещается посылать любые ресурсы из этой папки в ответ на запрос пользователя.

Если приложение было разработано как WAR-файл (более подробно мы поговорим об этих файлах позже), то должна присутствовать папка с именем МЕТА- INF. Смысл здесь в том, что данная папка будет содержать дополнительную информацию о приложении. Как и в отношении папки WEB-INF, web-сервер не имеет права отправлять пользователю любые содержащиеся в этой папке ресурсы.

Содержимое папки WEB-INF

В папке WEB-INF для web-приложения должен содержаться дескриптор развертывания в виде файла с именем web.xml. Этот файл должен быть согласован с опубликованным компанией Sun определением DTD для дескриптора развертывания web-приложения.

Все файлы классов сервлетов Java, уникальные для данного web-приложения, включая классы, используемые JSP-страницами, должны развертываться во вложенной папке classes, если они получены в виде отдельных файлов классов. В этой папке сохраняется обычная иерархия пакетов, так что, например, файл класса для сервлета QanalysnsServ из пакета com.XmlEcomBook.Chap()? будет содержаться в следующей вложенной папке основной папки приложения:

 WEB-INF/classes/com/XmIBook/QanalysisServ.class

Альтернативой хранению отдельных файлов классов во вложенной папке classes является создание библиотеки файлов .jar. Файлы .jar, расположенные в папке WEB-INF/lib, доступны виртуальной машине Java при загрузке классов и других ресурсов для web-приложения. Заметим, что файлы .zip в этой папке будут игнорироваться, поэтому следует использовать файлы с расширением .jar.

Общие классы и наборы инструментальных средств, которые должны быть доступны для всего web-сайта, могут развертываться обычным для Java способом с использованием стандартных настроек путей классов. Не существует способа разрешить совместное использование какого-либо класса только избранным приложениям; доступ к классу разрешен либо только одному приложению, либо всем.

Дескриптор развертывания web-приложения

В API сервлетов имеются спецификации, которые на основе составленного компанией Sun определения DTD идентифицируют сведения, содержащиеся в дескрипторе развертывания приложения. В листинге 10.1 показана часть файла web.xml, который использовался для некоторых сервлетов, описанных в этой книге.

Листинг 10.1. Часть файла web.xml для web-приложения (web.xml)

 <?xml  version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.
//DTD Web Application 2.2//EN"
  "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<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>

В листинге 10.2 показана начальная часть файла web.dtd из версии 2.2 API сервлетов. Полная копия файла, содержащая более 400 строк, имеется на прилагаемом к книге компакт-диске.

Листинг 10.2. Часть DTD для web-приложений, в которой показаны элементы первого уровня (web.dtd)

 <?xml  version="1.0" encoding="ISO-8859-1"?>
<!-- The web-app element is the root of the
deployment descriptor for  a web application -->
<!ELEMENT web-app (icon?, display-name?, description?,
distributable?,  context-param*, servlet*, servlet-mapping*,
session-config?,  mime-mapping*, welcome-file-list?,
error-page*, taglib*,  resource-ref*, security-constraint*,
login-config?, security-role*,  env-entry*, ejb-ref*)>

Параметры конфигурации

Как видно по именам элементов в листинге 10.2, большое количество параметров конфигурации web-приложения можно устанавливать из файла web.xml. Эти параметры удобно разделить на следующие категории:

  • параметры инициализации ServletContext;

  • конфигурация HttpSession;

  • определение и отображение сервлетов и JSP-страниц;

  • отображение типов MIME;

  • стандартные страницы с приветствием;

  • страницы ошибок;

  • параметры безопасности.

Использование параметров каждой из этих категорий подробно описано в официальном руководстве по API сервлетов, поэтому здесь мы не будем рассказывать обо всех параметрах.) Наиболее вероятно, что из всех этих параметров вам потребуются только параметры для определения и отображения сервлетов и JSP-страниц. Например, рассмотрим следующий фрагмент листинга 10.1:

 <servlet><servlet-name>catalog</servlet-name>
 <servlet-class>com.XmlEcomBook.catalog
 .CatalogServ</servlet-class>

<init-param>

<param-name>workch r</param-name>

<param-value>e:\\scMpts\\XMLgTfts</param-val.ue>

</init-param> </servlet>

Этот фрагмент отображает класс сервлета в папку имен и создает один параметр инициализации с именем workdir и значением е: \\scripts\\XMLgifts. Здесь используется символ \\, так как одна косая черта \ играет роль управляющего символа для строк Java.

Архивные файлы web-приложения

Правила организации папок для web-приложений, заданные в API сервлетов, позволяют определить формат архивного файла, в котором будут содержаться все ресурсы, необходимые для работы приложения. Согласно терминологии Sun, такой формат называется WAR, или Web Application Archive (архив web-приложения). Фактически это такой же формат, как хорошо знакомый формат JAR. Идея заключается в том, что созданный поставщиком программного обеспечения WAR-файл, содержащий web-приложение, можно поместить в файловую систему любого подходящего сервера, и он автоматически распакуется, образуя определенную структуру папок приложения.

Эта автоматическая распаковка происходит тогда, когда при запуске сервера обнаруживается появление нового WAR-файла. За распаковку архива отвечает web-сервер.

Следующее поколение

Сейчас, когда пишется эта книга, уже выпущена следующая версия API сервлетов, под номером 2.3. Пока она проходит стадию проверки, но к моменту, когда наша книга будет опубликована, эта версия, вероятно, получит официальный статус. Организация Apache работает над версией 4 сервера Tomcat, в котором будут реализованы и API 2.3 для сервлетов, и API 1.2 для JavaServer Pages. Эта новая версия потребует поддержки пакета JDK 1.2 или более поздней его версии.

К добавленным новым средствам относятся фильтры (filters) и слушатели событий (event listeners). Фильтры — это классы Java, которые могут модифицировать содержание запроса перед тем, как он будет передан сервлету, или содержимое ответа, сгенерированного сервлетом. Например, фильтры могут расшифровать запрос или зашифровать ответ. Другой возможностью является применение XSLT-трансформации к данным XML, сгенерированным сервлетом.

Слушатели событий предназначены для того, чтобы дать программисту больше возможностей для контроля над приложением как над единым целым. Например, слушатель событий может получать извещения каждый раз, когда какой-нибудь сервлет меняет атрибут в объекте Servl etContext.

Следующее поколение спецификации JavaServer Pages предлагает пользователям XML некоторые замечательные нововведения. Этот стандарт определяет, как компилятор JSP будет создавать для каждого документа JSP в точности эквивалентный ему документ XML. Более того, компилятор JSP будет способен принимать входные данные как в виде разметки JSP, так и в виде документа XML. Ожидается, что это изменение будет способствовать развитию современных инструментальных средств создания и поддержки JSP.

В плане обеспечения безопасности также произошли некоторые усовершенствования. Ее реализация в версии API 2.3 сервлетов основана на архитектуре платформы Java 2, поэтому в ней возможно очень тонкое разграничение функций обеспечения безопасности.

В версии API 2.3 сервлетов рекомендуется, но не требуется, поддержка HTTP 1.1. Это означает, что методы сервлетов должны поддерживать запросы PUT, DELETE, OPTIONS и TRACE. Тем не менее предполагается, что эти нововведения в API сервлетов и JSP не нарушат работу приложений, написанных согласно стандартам API 2.2 сервлетов и API 1.1 JSP.

Следующее поколение XML

Версия Tomcat 3.1, которую мы здесь используем, работает с анализатором Sun JAXP 1.0, реализующим только интерфейсы SAX 1.0 и API первого уровня DOM. Версия 1.1 анализатора JAXP будет поддерживать второй уровень DOM и версию 2.0 SAX. Многие изменения всего лишь упрощают настройку характеристик анализатора, но некоторые из них добавляют существенно новые возможности.

В JAXP 1.1 будет включен новый пакет javax.xml .transform для определения интерфейсов, поддерживающих трансформацию данных XML на основе таблиц стилей. В числе других свойств можно выделить создание более обобщенных анализаторов, манипулирование пространствами имен и упрощение настройки многих параметров анализатора.

Если вас интересуют эксперименты с последними новшествами, связанными с поддержкой XML в Java, то в процессе подготовки проекта анализатора Xerces организация Apache продолжает рассматривать новые предложения. Многие из предложенных дополнений находятся в различных стадиях разработки, так что анализатор Xerces является не законченным продуктом, а скорее рабочим проектом. Подробную информацию вы найдете на сайте http:// xml.apache.org/xerces-j/.

Протокол SOAP

Протокол SOAP (Simple Object Access Protocol) — это важный для web-приложений протокол, к которому проявляют огромный интерес программисты, работающие с XML. SOAP определяет стандарты для кодирования различных типов данных и соглашений о представлении вызывающих методов в удаленных объектах. Это «легкий» (light-weight) протокол для обмена сообщениями между объектами в распределенной среде. С использованием XML и передачи простого (plain) текста через стандартный порт HTTP сообщения SOAP могут передаваться через брандмауэры.

SOAP пользуется широкой поддержкой в компьютерной промышленности и считается главной частью оболочки .NET для распределенных вычислений корпорации Microsoft. Поддержка этого стандарта была возложена на консорциум W3C.

Проблемы масштабирования

О каком бы приложении ни шла речь в нашей книге, всегда предполагалось, что имеется только один компьютер, на котором работает один процессор сервлетов Java. Учитывая, что тактовая частота типичного центрального процессора и объем памяти серверов растут почти так же быстро, как и сам Интернет, упомянутое предположение является разумным для серверов, обслуживающих до нескольких тысяч запросов в секунду, в частности еще и потому, что Java хорошо работает в серверных конфигурациях с несколькими центральными процессорами.

Однако в случае головокружительного успеха вашего сайта вам, возможно, придется предусмотреть возможность нехватки ресурсов одного сервера для того, чтобы справиться с возросшей нагрузкой. Помимо увеличения нагрузки есть и еще одна причина, по которой стоит использовать больше одного сервера, — это повышает надежность вашей системы.

Использование двух или более различных машин в сети для одного и того же сайта часто называется объединением в кластеры, или кластеризацией (clustering). В такой системе имеется один препроцессор, который переадресует запросы одному из серверов в зависимости от некоторого критерия.

Сохранность информации о сеансе

Самая большая проблема при разделении нагрузки между несколькими машинами связана с приложениями, которые должны отслеживать состояние пользователя. В приложении «корзина покупателя» мы использовали объект HttpSession, управляемый контейнером сервлетов, чтобы сохранять объект ShoppingCart в промежутках между циклами запрос-ответ. К сожалению, управление объектом HttpSession осуществляется целиком внутри одного объекта ServletContext в одной виртуальной машине Java. Чтобы в условиях кластеризации данные корзины покупателя оставались актуальными в течение всего времени, пока пользователь находится на вашем сайте, в системе должна применяться одна из приведенных ниже технологий.

  • Каждый запрос данного пользователя нужно направлять на одну и ту же машину, для этого в каждом запросе нужно отслеживать специальные данные, например файл cookie, содержащий идентификатор сеанса в строках заголовка, или IP-адрес пользователя. Недостаток данного способа заключается в том, что он неустойчив к сбоям, так как информация о сеансе хранится только на одной машине.

  • Можно направлять запрос каждого пользователя в определенную машину, как и в первом способе, но создавать запасную копию каждого сеанса для того, чтобы, если сбой происходит на одной машине, информация о сеансе могла быть восстановлена.

  • Можно распределять информацию о сеансе по всем системам, входящим в кластер, — тогда будет безразлично, какая система получит запрос.

Другое соображение, говорящее в пользу кластеризации, — балансировка нагрузки, так что все машины примерно поровну задействуют свои ресурсы. Отдельные машины должны иметь возможность сообщать распределителю запросов насколько они заняты.

J2EE и Enterprise JavaBean

Система J2EE (Java 2 Enterprise Edition), включающая библиотеки, наборы инструментальных средств и интерфейсов API, — это редакция среды разработки Java, ориентированная на создание многоуровневых крупномасштабных web-приложений. Как показано на рис. 10.1, в подобных web-приложениях действия сер- влетов Java и JSP-страниц сводятся к созданию пользовательского интерфейса, в то время как «конечными» процессами, связанными с бизнес-логикой и доступом к базам данных, управляет Enterprise JavaBean (EJB) — интерфейс API, основанный на Java.

Рис. 10.1. Модель приложения на основе J2EE

EJB работает в специализированной среде — контейнере EJB, аналогично тому, как сервлеты работают в контейнере сервлетов. Но контейнер EJB может выполнять и многие другие функции для вашего приложения. К этим функциям относятся сохранение данных сеанса и осуществление транзакций в базах данных. Теоретически контейнер EJB отвечает за все вопросы масштабирования.

Альтернативное решение — Spaces

В отличие от жестко структурированной среды J2EE в концепции Spaces web- приложение создается как слабосвязанная система. Эта концепция несколько лет обсуждалась в академических кругах, а недавно было найдено новое ее применение. Проект JINI, разработанный Sun для связи между распределенными приложениями, использует новый интерфейс API JavaSpaces.

Приложения, взаимодействующие через JavaSpaces, обмениваются сообщениями не напрямую, а посредством специального приложения, которое управляет пространством сообщений. Сообщения содержат данные (объекты Java) и идентифицирующие пометки, которые требуются, чтобы сопоставить заданию соответствующее приложение, способное это задание выполнить. Приложение, отправившее сообщение в пространство сообщений, может быть проинформировано о том, что посланный им объект прошел требуемую обработку. Сравнивая эту ситуацию с той, в которой имеется специальный диспетчер, назначающий задания приложениям, мы видим, что взаимодействие, основанное на использовании пространства сообщений, автоматически распределяет нагрузку между различными системами более равномерно.

Например, программа «корзина покупателя» может отправить сообщение, представляющее содержимое корзины, с меткой, которая означает запрос о подсчете стоимости доставки заказа покупателю. Программа подсчета стоимости доставки, ожидающая очередного запроса, получает этот объект, выполняет необходимые вычисления и возвращает заполненный объект в пространство сообщений.

Тем, кто ищет подробную информацию о программировании в соответствии с концепцией пространства сообщений JavaSpace, можно предложить в качестве исходного пункта следующий адрес:

http://java.sun.com/products/javaspaces/


  • Глава 2. Каталог товаров на XML
    • Назначение web-сайта
      • Требования
      • Ограничения
        • Покупка готового пакета — это быстрее и дешевле
        • Сборка приложения из стандартных частей — это лучше и дешевле
        • Разработка сервера web-приложений — это быстрее и лучше
    • Достоинства XML
      • Доступность инструментальных средств
        • SOAP
      • Гибкость инструментальных средств
      • Совместимость инструментальных средств
      • Поддержка Unicode
    • Каталог товаров и DTD
      • Организация данных
      • Написание DTD
      • Доработка DTD
      • Создание первого чернового варианта DTD
      • Уточнение чернового варианта
        • Элементы catalog, productjine и product
        • Элементы description, paragraph и general
        • Элементы price, quantity_in_stock и image
        • Элементы onsale_date, time, clip и title
        • Элемент shipping
      • Финальная версия
    • Принципы разработки DTD
      • Репрезентативный образец данных
      • Избегайте субтрактивного уточнения
      • Упрощайте DTD
      • Элементы или атрибуты?
      • Рассуждайте в терминах реальных процессов
      • Шаблоны XML
        • Краткое описание
        • Задача
        • Контекст
        • Причины
        • Решение
        • Пример
        • Обсуждение
        • Близкие по назначению шаблоны
        • Известные примеры применения
    • Стандартизация DTD

Глава 2.

Каталог товаров на ХМL

Из первой главы вы узнали, что представляет собой XML и как с помощью XML создаются языки разметки. Теперь вы располагаете инструментальными средствами для создания документов XML и определений типов документов (DTD). Но иметь инструменты — это далеко не то же самое, что уметь их использовать. Как вы знаете, изучение любого языка невозможно без опыта его практического применения.

Для того чтобы вы получили представление о том, как происходят создание документа XML и определение типа документа, какие вопросы при этом должен решить программист, в данной главе мы продемонстрируем вам весь процесс создания XML-каталога товаров для гипотетической компании, занимающейся электронной коммерцией. Рассматривается большая часть аспектов этого процесса, в том числе вопросы сбора данных, разработки DTD и фактического программирования каталога.

Назначение web-сайта

Наша гипотетическая компания Xtreme Mega-Large Gifts (XMLGifts) занимается продажей редких музыкальных записей, книг и других предметов. Рынок сбыта очень ограничен, так как магазин расположен в маленьком городе. Однако владельцы компании уверены, что их товары будут пользоваться спросом, если потенциальные покупатели получат информацию об этих товарах. Поэтому компания решила создать свой web-сайт XMLGifts.com.

Как и в любом проекте, первый и, возможно, наиболее существенный его этап — это сбор информации. Поэтому в следующих разделах мы расскажем о требованиях и ограничениях, которые следует учитывать при создании подобного web-сайта.

Требования

Бизнес-требования (business requirements), или требования верхнего уровня (high- level requirements), определяют глобальные цели, которые стоят перед компанией, проектирующей свой web-сайт. После того как решение о создании такого сайта принято, следует ясно и четко изложить эти требования в специальном документе, регламентирующем политику компании.

Пользовательские требования (user requirements) — это те задачи, которые должен решать пользовательский интерфейс сайта. Предположим, для сайта XMLGifts.com бизнес-требования и пользовательские требования сводятся к следующему:

  • посетители сайта должны иметь возможность искать товары по каталогу, добавлять их в корзину покупателя и покупать;

  • следует предусмотреть возможность расширения и модернизации каталога;

  • сайт должен быть исходно рассчитан на некоторое умеренное количество посещений, но следует предусмотреть возможность повышения производительности сайта (по мере роста его популярности), причем это не должно требовать его полной переделки;

  • сайт должен поддерживать партнерские программы, при выполнении которых на других сайтах можно располагать информацию о товарах компании XMLGifts и ссылки на сайт XMLGifts.com;

  • требуется создать сайт достаточно быстро, чтобы успеть к сезону предпраздничных покупок;

  • сайт должен обеспечивать возможность обратной связи с клиентами, которая позволит собирать маркетинговую информацию и отзывы покупателей о товарах;

  • наконец, создание сайта должно уложиться в определенные бюджетные рамки.

Определив эти требования и подробно обсудив их с различными сотрудниками компании, можно сделать новый список с более детальными функциональными требованиями, например:

  • расширения и дополнения сайта, вероятные в будущем, скорее всего, потребуют изменений в дизайне пользовательского интерфейса и модернизации источника данных. Чтобы сделать эти изменения возможно более безболезненными, бизнес-логика сайта должна как можно меньше зависеть от источника данных и от способа их представления;

  • клиент не должен быть привязан к какой-либо конкретной серверной платформе или базе данных, хотя на данном этапе в компании используются различные операционные системы и форматы данных. Как и у любой компании, у XMLGifts имеются долгосрочные планы по объединению различных источников данных и приложений вместе в некоторое комплексное приложение, которое должно работать без сбоев;

  • каталог, созданный для электронного магазина, должен допускать использование в качестве центральной базы данных для всей компании. Например, нужно обеспечить возможность генерации печатной версии каталога из того же источника данных, что и web-сайт;

  • сайт должен предоставлять другим сайтам несложный метод получения данных из каталога;

  • для записи данных о клиентах придется создать достаточно дорогостоящую базу данных. Приложение должно отслеживать определенные предпочтения клиентов, а затем на основе этих сведений и базы данных по товарам генерировать отчеты и ориентировать содержимое сайта на конкретного пользователя

Ограничения

Ограничения, которые приходится учитывать при создании web-сайта, возможно, оказывают более значительное влияние на конечный результат, чем приведенные выше требования. Мы имеем в виду ограниченность в финансовых возможностях и во времени. Эти факторы настолько часто оказываются решающими, что заслуживают отдельного обсуждения.

Фундаментальное правило коммерции «быстрее, лучше, дешевле» в данном случае не работает. Многим разработчикам и консультантам понимание этого факта дается нелегко. Если заказчик упорно настаивает на том, чтобы разработка его проекта удовлетворяла всем трем требованиям, — это часто означает, что проект заранее обречен на неудачу или же консультанту придется делать больше за меньшие деньги Чтобы максимально увеличить шансы на успех предприятия, следует предложить заказчику выбор из трех возможностей, о которых мы рассказываем в следующих трех разделах*

Покупка готового пакета — это быстрее и дешевле

Покупка готового пакета приложений для электронной коммерции, который затем настраивается соответственно конкретным требованиям, иногда может сэкономить время на разработку сайта Однако чаще получается, что вы оказываетесь привязанным к конкретным технологиям, и хотя приложение как-то заработает, вряд ли оно будет точно соответствовать нуждам вашего бизнеса и хорошо масштабироваться.

Если вы планируете разместить на своем сайте достаточно стандартный электронный магазин и вам нужно как можно скорее запустить его в работу, то, вероятно, вам подходит такой вариант В этом случае вам следует внимательно отнестись к своему выбору — вы должны убедиться, что выбранный вами пакет действительно будет работать так, как нужно

Такой метод можно назвать быстрее и дешевле, так как он удовлетворяет этим двум требованиям.

ВНИМАНИЕ

Если вы решите воспользоваться этим методом, но не будете достаточно внимательны, вы рискуете оказаться в ситуации, когда вам придется постоянно модифицировать и дополнять исходное приложение, чтобы приспособить его к своим нуждам. Тем самым ваш проект потребует больших затрат времени и денег, чем многие другие решения.

Сборка приложения из стандартных частей — это лучше и дешевле

Другой способ создания сайта заключается в том, чтобы начать с нуля и сконструировать его именно так, как вам нужно. Хотя этот способ требует больше времени, чем покупка и настройка готового приложения, зато функциональность такого сайта будет в точности отвечать требованиям заказчика

Поскольку все необходимое для создания сайта находится в свободном доступе, гораздо большую часть бюджета заказчика можно потратить на достижение именно той функциональности, которая требуется клиенту. Это лучше и дешевле.

Разработка сервера web-приложений — это быстрее и лучше

Чтобы ускорить создание сайта и сохранить все преимущества индивидуальной разработки, можно воспользоваться сервером web-приложений Такой сервер обычно содержит набор готовых объектов, которые служат для решения многих наиболее распространенных задач разработки web-приложения. В вашем приложении могут использоваться эти объекты, а также другие услуги, предоставляемые сервером. К таким услугам обычно относят балансировку нагрузки, кэширование данных, инструментарий для управления магазином, средства планирования и организации выполнения задач.

Все эти услуги и объекты, конечно, обойдутся недешево. И хотя весьма вероятно, что вы заплатите за какие-то функциональные возможности, которыми никогда не воспользуетесь, это быстрее и лучше.

Достоинства XML

Разработчики часто сталкиваются с такой дилеммой: заказчик не оступается от своих требований сделать сайт быстрее и дешевле, но в то же время ему необходима персональная настройка, которую невозможно обеспечить, взяв за основу готовое приложение. В таком случае искать компромисс между требованиями заказчика и реальными возможностями приходится разработчику

Один из принципов решения такой задачи выглядит следующим образом' не стремитесь сделать более мощное приложение, чем требуется на данный момент. Например, компания XMLGifts предполагает, что в первый год существования загруженность ее сайта посетителями будет невелика. Поэтому, вместо того чтобы заранее пытаться обеспечить функциональность, которая предположительно потребуется впоследствии, можно создать приложение, рассчитанное только на нынешние запросы При необходимости впоследствии это приложение можно будет легко масштабировать.

Одним из результатов этой стратегии может оказаться то, что запросы и ожидания заказчика придется несколько урезать, но зато будут удовлетворены основные требования: разработка сайта уложится в отпущенные сроки и не потребует дополнительных расходов. Если вы навсегда запомните, что невозможно достичь всех трех целей сразу (быстрее, лучше, дешевле), и сумеете находить компромисс в любой ситуации, ваши проекты будут иметь высокие шансы на успех Если вы правильно построите web-приложение, то его расширение и дополнение в будущем не доставят проблем.

Обдумав и взвесив все «за» и «против», компания XMLGifts приняла решение программировать бизнес-логику на Java и использовать XML для представления данных. Впрочем, об этом можно было догадаться, посмотрев на обложку нашей книги.

ПРИМЕЧАНИЕ

В реальной жизни, в отличие от этой книги, Java и XML не всегда являются лучшими решениями. Перед тем как выбрать технологии, которые вы будете использовать в своем приложении, тщательно проанализируйте приоритеты, требования и имеющиеся в вашем распоряжении ресурсы.

Некоторые причины такого выбора приведены ниже:

  • доступность инструментальных средств;

  • гибкость инструментальных средств;

  • совместимость инструментальных средств;

  • поддержка Unicode.

Приведенные здесь причины более подробно рассматриваются в следующих разделах.

Доступность инструментальных средств

И XML, и Java обязаны своей популярностью и количеством пользователей тому, что они были основаны на открытых и находящихся в свободном доступе стандартах. Можно сказать, что эти технологии естественным образом дополняют друг друга.

Существует большое количество недорогих и даже бесплатных инструментальных средств для разработки Java-приложений, которые работают с данными XML. К ним относятся анализаторы XML, редакторы XML, средства для проверки допустимости документов XML и их преобразования, а также промежуточное программное обеспечение.

До недавнего времени недостающим звеном между Java и XML был стандарт обмена данными между web-приложениями. Но появление протокола SOAP (Simple Object Access Protocol — простой протокол доступа к объектам) восполнило этот недостаток. Пользуясь поддержкой таких гигантов компьютерной индустрии, как Microsoft и IBM, SOAP дает разработчикам прекрасные возможности для создания приложений.

SOAP

SOAP — это протокол, предназначенный для удаленного вызова процедур, основанный на стандартах XML и HTTP. В декабре 1999 года протокол SOAP был предложен компаниями DevelopMentor Inc., Microsoft и UserLand Software Inc. на рассмотрение Консорциуму W3C как Интернет-проект.

Методы SOAP вызываются с помощью HTTP-запросов POST. В заголовке HTTP SoapMethodName указывается имя метода, который требуется вызвать. В следующем примере в заголовке указаны пространство имен вызываемого метода (sybex- com) и имя метода (getPrice):

POST /xmlstore.jsp HTTP/1.1

Host: www.sybex.com

SOAPMethodName: urn:sybex - com:SybexStore#getPrice

Content-Type: text/xml

Content-Length: nnnn

Содержательная часть сообщения SOAP пишется на XML:

<?xml version='1.0'?>

<SOAP:Envelope

xmlns:SOAP='urn:schemas-xmlsoap-org:soap.vl'>

<SOAP:Body>

<nsl:getPrice

xmlns:nsl='  urn:sybex-com:SybexStore  '>

<ISBN>x-xxxx-xxxx-x</ISBN>

</nsl:getPrice>

</SOAP:Body>

</SOAP:Envelope>

Этот запрос просто передает на сервер все данные, необходимые для вызова указанного метода. Обратите внимание, что пространство имен, указанное в первом элементе внутри SOAP:Body, должно совпадать с пространством имен в заголовке SoapMethodName.

Если дальше все идет нормально (запрошенный метод реально существует и удаленный сервер принимает запросы SOAP), то будут предприняты определенные действия, соответствующие запросу. Когда эти действия будут выполнены, полученный HTTP-ответ, содержащий данные XML, будет послан обратно клиенту.

Пока что SOAP не является официальной спецификацией Консорциума W3C, но если вы хотите начать использовать этот протокол прямо сейчас, у вас имеется такая возможность — IBM и DevelopMentor создали библиотеки Java, которые реализуют технологию SOAP. Вы можете воспользоваться сайтом организации Apache (http://xml.apache.org/soap/index.htm), откуда можно загрузить все IBM-версии SOAP, a Perl- и Java-версии компании DevelopMentor можно загрузить по адресу www.develop.com/SOAP.

Гибкость инструментальных средств

Хотя данные для нашего электронного магазина хранятся в виде документов XML, для повышения производительности сайта они могли бы храниться в двоичных реляционных или объектных базах данных, а при поступлении запросов от приложений нужные данные просто преобразовывались бы в XML.

Для выполнения этой задачи существуют различные промежуточные приложения. Наиболее совершенные серверы XML обеспечивают такие возможности, как кэширование, репликация и балансировка нагрузки. Серверы XML обычно представляют собой приложения, написанные на Java и способные воспринять любые данные, для которых у вас имеется драйвер JDBC (Java Database Connectivity — средство доступа Java-приложений к базам данных).

И XML, и Java являются переносимыми стандартами (portable standards). Это означает, что они не зависят от платформы, на которой будет работать приложение. Если приложение написано на Java, то для его работы годится любая виртуальная машина Java. Приложение, которое использует данные XML, может легко взаимодействовать с любым типом источников данных. Если вам потребуется изменить источник данных приложения XML, вносить изменения в само приложение не потребуется.

Совместимость инструментальных средств

Если при разработке сайта вы использовали Java и XML, вам будет легко создавать взаимосвязи с другими web-сайтами и электронными магазинами. При организации любого взаимодействия или передачи данных между web-сайтами приходится решать некоторую проблему, связанную с тем, что вы, с одной стороны, не хотите предоставлять кому бы то ни было доступ к вашей базе данных, а с другой стороны, должны передать другой стороне данные в доступной для понимания форме.

Многие так называемые партнерские программы в настоящее время требуют, чтобы приложение-потребитель (партнерский сайт) получило от поставщика код HTML. Недостатком такого подхода является то, что формат полученной информации предопределен. С другой стороны, с помощью XML потребитель может форматировать данные так, как ему нужно. Дополнительным преимуществом XML при обмене информацией между сайтами является то, что документ XML, в отличие от HTML, может использоваться потребителем не только для отображения информации.

Поддержка Unicode

Допустим, что компания XMLGifts решила, помимо всего прочего, распечатать свой каталог на нескольких языках. Встроенная в Java и XML поддержка символов Unicode упрощает эту задачу.

Поскольку web-разработчики все больше и больше стремятся к тому, чтобы пользовательский интерфейс и содержимое web-страниц не были ограничены в языковом отношении, поддержка Unicode становится все более важной характеристикой любой Интернет-технологии. Поддержка Unicode была добавлена и в другие популярные языки web-программирования, такие как Perl и Tel. Однако в отличие от них язык Java был исходно рассчитан на поддержку Unicode, поэтому обработка символов различных алфавитов нисколько не усложняет структуру приложения, как это может произойти на других платформах.

Каталог товаров и DTD

Когда все требования к web-сайту сформулированы и принято решение использовать XML, следующим шагом в создании XML-каталога товаров является изучение имеющихся данных и преобразование их в подходящий формат.

В начале работы над проектом каталог данных сайта XMLGifts.com хранился в виде электронной таблицы. Часть этой таблицы показана в табл. 2.1.

Наш опыт показывает, что такой способ хранения данных характерен для многих физических (в отличие от виртуальных) магазинов, которые только начинают разрабатывать web-сайты. Реальная таблица может иметь гораздо больше столбцов, чем показано в нашем примере, но общий принцип заключается в том, что для каждого товара в таблице отводится отдельная строка, а для каждой возможной характеристики товара имеется отдельный столбец. Подобный тип организации каталога (все данные в одной таблице) обычно является результатом того, что количество видов товаров, с которыми имеет дело данная фирма, растет быстрее, чем техническая квалификация сотрудников. Излишне говорить, что эта ситуация требует кардинального пересмотра. Следующим этапом является выбор оптимального способа организации данных.

Таблица 2.1. Образец каталога XMLGifts.com

Шифр | Название товара | Описание | Категория | Цена | Количество на складе | Автор
bk0022 | Справочник по растениям | Все, что вы когда-либо хотели узнать о растениях | Книги | $12.99 | 4 | Вильям Смит
cd0024 | Просто подпевай | Прекрасная коллекция песен, которые можно петь всей семьей | Компакт- диски | $10.00 | 100 |
WZ0027 | Ударный интерфейс | Этот интерфейс позволяет вам общаться со своей любимой вычислительной машиной, ударяя по ее корпусу | Приборы и устройства | $109.99 | 7 |

Организация данных

Таблицы представляют собой, вероятно, наиболее распространенный способ организации данных. Хранение данных в строках и столбцах обеспечивает гибкость при создании представлений данных и определения взаимосвязей между ними. Тем не менее использование единственной таблицы — это очень неэффективный способ организации данных.

Если бы в нашем примере для создания каталога электронного магазина использовалась реляционная база данных, первым шагом по улучшению организации было бы создание отдельной таблицы для каждого вида товаров. Это помогло бы избежать очевидных потерь, связанных с тем, что в единой таблице приходится отводить целый столбец, в котором, возможно, будет заполнена только одна ячейка (например, если в столбце указывается автор книги, для всех остальных видов товаров, помимо книг, этот столбец будет пустым). Далее, если для какой-то книги указывается не автор, а редактор, то ради одной такой книги придется добавлять целый столбец. Если у какой-либо другой книги два (или более) автора, то снова придется либо создавать очередной столбец, либо создать отдельную таблицу для авторов.

На рис. 2.1 показан один из способов организации взаимоотношений между таблицами.

Рис. 2.1. Схема отношений между таблицами

На рис. 2.2 показан более подробно фрагмент приведенной выше схемы, в которую добавлены некоторые поля из таблицы Авторы.

Рис. 2.2. Отношения между таблицами и полями

На рис 2.3 мы добавили некоторые фактические значения и организовали данные в виде древовидной схемы.

Рис. 2.3. Отношения между таблицами, полями и данными

Что напоминают три приведенные схемы? Эти схемы фактически показывают фрагмент реляционной базы данных в виде иерархической структуры. Иерархические структуры данных (hierarchical data structures), подобные тем, что создаются с помощью XML, очень удобны для организации данных, поскольку в них имеется единая «точка отсчета». Например, в схеме, приведенной на рис. 2.3, такой точкой отсчета для всей структуры является Серия товаров «книги». В документе XML такая точка отсчета, относительно которой строится вся организация данных, называется корневым элементом (root element). Напомним, что в любом документе XML должен присутствовать корневой элемент.

В то время как в реляционных базах данных для организации отношений используются ключевые слова, в языках разметки отношения элементов указываются путем вложения. Например, автор книги может быть указан путем вложения элемента Author внутрь элемента Book.

Отношения и семантику, которые было бы очень сложно показать с помощью реляционной базы данных, можно легко, интуитивно понятным способом показать, используя разметку. Ниже мы приводим фрагмент правильно оформленного документа XML, который содержит ту же информацию, что и приведенная на рис. 2.3:

<Products>

<Book>

<Тitle>Справочник по растениям</Title>

<Descnption>
Bce, что вы когда-либо хотели узнать
о растениях
</Descnption>

<Рrice>$12 99</price>

<Author>

<Name>Вильям Смит</Name>

<Biography>
Мистер Смит родился в Западной Филадельфии,
а в настоящее время проживает в Бел Эйр, Калифорния.
</Biography>

</Author>

</Book>

</Products>

Как показано в предыдущем примере, реляционную базу данных часто удается преобразовать в XML. Для этого нужно объявить корневой элемент с тем же именем, что и у базы данных, затем создать элементы с именами, совпадающими с названиями строк в таблице, и отобразить столбцы таблицы в их дочерние элементы.

Преобразование реляционной базы данных в документ XML не обязательно делать вручную. Для этой цели существует множество инструментальных средств, так же как и для решения обратной задачи (преобразование документа XML в таблицу).

Также этот процесс можно автоматизировать с помощью запросов SQL. Основной метод преобразования табличных данных в иерархическую структуру заключается в использовании внешнего соединения SQL. Внешнее соединение (outer join) объединяет две таблицы в одну, причем полностью сохраняются данные лишь одной из них. Структура получившейся в результате таблицы повторяет структуру одной из исходных. Предположим, например, что у нас имеются две таблицы, Books и Authors. Для создания иерархической структуры этих реляционных данных можно использовать что-либо подобное следующей инструкции SQL:

SELECT *

FROM Books LEFT JOIN Authors ON  [Authors]  [Book_ID]=[Books].[ID];

Эта инструкция SQL объединяет таблицы Books и Authors, сохраняя все строки из таблицы Books, даже если в таблице Authors нет соответствующей записи. Поскольку в таком случае главной таблицей является Books, то те строки таблицы Authors, которые не ассоциированы с какой-либо записью из таблицы Books, выбраны не будут. В получившейся таблице данные будут показаны с точки зрения Books (табл. 2.2).

Таблица 2.2. Представление Books

Название книги | Имя автора
По ком звонит колокол Электронный магазин на Java и XML Преступление и наказание | Эрнест Хемингуэй Вильям Брогден, Крис Минник Федор Достоевский

Чтобы преобразовать эту таблицу в данные XML, каждый столбец нужно сделать дочерним элементом по отношению к элементу Book, как показано в листинге 2.1 [Book — книга, title — название, author — автор. — Примеч. перев. ].

Листинг 2.1. Документ XML, получившийся в результате преобразования реляционной базы данных (BookView.xml) [Все представленные в книге тексты программ можно найти на сайте .издательства по адресу www.piter.com, — Примеч. ред. ]

<BookView>

<Book>

<Title>По ком звонит колокол</Title>

<Author>

<Name>Эрнест Хемингуэй</Nаmе>

</Author>

</Book>

<Book>

<Title>Электронный магазин на Java и XML</Title>

<Author>

<Name>Вильям Брогден</Name>

</Author>

<Author>

<Name>Крис Mинник</Name>

</Author>

</Book>

<Book>

<Title>Преступление и наказание</Title>

<Author>

<Name>Федор Достоевский</Name>

</Author>

</Book>

</BookView>

Написание DTD

Независимо от того как вы преобразуете данные в XML, в процессе написания определения типа документа вы непременно получите представление о потенциальных проблемах, связанных с организацией данных. В DTD должно быть указано, каким образом элементы каталога связаны друг с другом, также DTD должно обеспечивать возможность последующего добавления в каталог новых данных, не нарушая требований допустимости документа XML.

Один из способов создания DTD — начать с правильно оформленного документа XML и выводить DTD из него. Для этого нужно просто последовательно «пройтись» по документу и создать объявления для каждого элемента разметки, который вы встретите. Этот процесс в той или иной степени может быть автоматизирован. Для начала этот метод неплох, но полученное таким образом описание DTD не может быть столь же логичным и столь же полезным, как DTD, созданное с нуля и основанное на подробном исследовании.

Другой способ разработки DTD заключается в том, чтобы писать DTD до того, как написан сам документ XML. Это более формальный метод, который требует тщательного планирования. Если вы начинаете с нуля, не имея каких- -либо данных, которые вам нужно преобразовывать, можно использовать этот метод.

Чаще, однако, при создании DTD используется комбинация этих двух методов. Если, к примеру, нам требуется создать DTD для служебных записок, можно начать с типичной записки и разметить ее как документ XML [Memo — служебная записка, from — от кого, to — кому. — Примеч. перев. ]:

<memo>

<from>Крис Минник</from>

<to>Сотрудникам</to>

<body>Сегодня выходной.  Поезжайте на озеро отдыхать</body>

</memo>

Для этого простого документа DTD может выглядеть следующим образом:

<!ELEMENT memo (from,to,body)>

<!ELEMENT from (#PCDATA)>

<!ELEMENT to (#PCDATA)>

<!ELEMENT body (#PCDATA)>

Доработка DTD

После внимательного изучения этого определения DTD его недостатки становятся очевидными, и мы начинаем понимать, что требуется внести ряд исправлений. Первый недостаток заключается в том, что у нас нет способа однозначно идентифицировать записку. Во-вторых, следовало бы более подробно указать, что может содержаться в элементах from и to. В-третьих, возможны ситуации, когда записки посылаются не от одного, а от нескольких лиц или нескольким лицам. Обдумав, какие виды записок реально могут пересылаться между служащими фирмы, мы переделали DTD (листинг 2.2 [Name — название, department — отдел, important part — наиболее существенная часть. — Примеч. перев. ]).

Листинг 2.2. Исправленное определение DTD для записок (memo.dtd)

<!ELEMENT memo (from,  to,  cc?, body)>

 <!ATTLIST memo

 id ID «REQUIRED

 date CDATA «REQUIRED

 subject CDATA #IMPLIED>

 <!ELEMENT from  (name.department)*>

 <!ELEMENT to (name,department)*>

 <!ELEMENT cc  (name,department)*>

 <!ELEMENT name  (#PCDATA)>

 <!ELEMENT department  (#PCDATA)>

 <!ELEMENT body  (#PCDATA,important_part?)>

 <!ELEMENT important_part  (#PCDATA)>

Используя это определение DTD, любой сотрудник фирмы XMLGifts может точно ответить на вопрос, что такое служебная записка В листинге 2 3 показана правильно оформленная и допустимая служебная записка

Листинг 2.3. Правильно оформленная и допустимая служебная записка (memoexamplel.xml)

<?xml version="l.0" standalone="no"?>

<!DOCTYPE memo SYSTEM "memo.dtd">

<memo id = "cm0001"

date = "8/2/2000"

subject = "ваша задача на сегодня">

<from>

<name>Крис Минник</name>

</from>

<to>

<department>Bceм coтpyдникaм</department>

</to>

<body>

Поздравляю с окончанием проекта XMLGnfts.com
Предлагаю <important_part>сделать выходной и поехать на
озеpo</important_part>

</body>

</memo>

Этот метод — изучение реального образца данных и последующее исправление DTD с учетом возможных потребностей в будущем — мы и будем использовать при создании каталога товаров для сайта XMLGifts.com

В листинге 2 4 приводится первый черновой вариант документа XML, описывающего несколько товаров из будущего каталога для сайта XMLGifts.com Этот документ написан без DTD Он является правильно оформленным документом XML, но, поскольку никакому определению DTD он не соответствует, его нельзя назвать допустимым и самодокументируемым [Caption — подпись (к рисунку) productlme — серия товаров — Примеч перев ].

Листинг 2.4. Первая черновая попытка описания товаров

<?xml version="1.0"?>

<catalog>

<product_line name="Books">

<product id="bk0022">

<name>Guide to Plants</name>

<description>Everything you've ever wanted to
know about plants. </descnption>

<price>$12 99</price>

<quantity_in_stock>4</quantity_in_stock>

<image width="234" height="400"

src="images/covers/plants.gif ">

<caption>

This is the cover from the first edition

</caption>

</image>

<onsale_date>12/23/1999</onsale_date>

</product>

<product id="bk0035">

<name>Writing Fake Catalogs</name>

<description>Chns Hinnick's latest book
explains, in agonizing detail, the process
of thinking up fake products for a demonstration
catalog </description>

<pnce>$59.95</pnce>

<quantity_in_stock>30</quantity_in_stock>

<onsale_date>09/01/2000</onsale_date>

</product>

</product_line>

<product__line name="CDs'>

<product id="cd0024">

<name>Just Singin' Along</name>

<description>A lovely collection of songs that the
whole family can sing right along with </description>

<price>$10 00</price>

<quantity_in_stock>100</quantity_in_stock>

<onsale_date>2/23/2000</onsale_date>

</product>

<product id="cd0025">

<name>It's Dot Com Enough for Me Songs from Silicon
Somewhere</name>

<description>A collection of the best folk music from
Internet companies.

</descnption>

<clip format="mp3" length="4.32" size="4.0 Mb"

src="track2.mp3">

<title>Track 2: My B2B Is B-R-0-K-E</title>

</clip>

<onsale_date>4/12/2000</onsale_date>

</product>

</product_line>

<product_line name="widgets">

<product id="wi0026">

<name>ElectroThermal Oxidizor</name>

<description>This amazing gizmo uses electricity to
produce heat that can be used for oxidization
purposes.</description>

<price>$24.95</price>

<quantity_in_stoek>10</quantity_in_stock>

<image width="200" height="200" src="/images/toaster.gif">

</image>

<onsale_date>6/2/2000</onsale_date>

</product>

<product id="wz0027">

<name>Percusive Interface Unit</name>

<description>Communicate with your favorite electric
calculating machine through tapping!</description>

<price>$109.99</price>

<quantity_in_stock>7</quantity_in_stock>

<onsale_date>6/23/200K/onsale_date>

</product>

</product_line>

</catalog>

Создание первого чернового варианта DID

Для создания первого чернового варианта DTD, чтобы определить класс данных, которому принадлежит приведенный выше документ XML, мы использовали редактор XML CLIP!, созданный компанией Techno2000USA, Inc. Этот редактор имеет очень удобное свойство — он позволяет создавать DTD на основе правильно оформленного документа XML. В листинге 2.5 приведено полученное таким образом определение DTD. Для автоматизации процедуры создания первого чернового варианта DTD пригодны многие редакторы XML. Например, редактор XML Spy (доступный по адресу www.xmlspy.com) может сгенерировать DTD (а также некоторые другие типы схем XML) на основе любого правильно оформленного документа XML.

Листинг 2.5. Первый черновой вариант DID

<!ELEMENT catalog (product_line)* >

<!ELEMENT product_line (product)* >

<!ATTLIST product_line

name CDATA #IMPLIED>

<!ELEMENT product(name]description|price|

quanti ty_in_stock|image|onsale_date|clip)* >

<!ATTLIST product

id CDATA #IMPLIED>

<!ELEMENT name (#PCDATA)* >

<!ELEMENT description (#PCDATA)* >

<!ELEMENT price (#PCDATA)* >

<!ELEMENT quantity_in_stock (#PCDATA)* >

<!ELEMENT image (caption)* >

<!ATTLIST image

width CDATA #IMPLIEO>

<!ATTLIST image

height CDATA #IMPLIED>

<!ATTLIST image

src CDATA #IMPLIED>

<!ELEMENT caption (#PCDATA)* >

<!ELEMENT onsale_date (#PCDATA)* >

<!ELEMENT clip (title)* >

<!ATTLIST clip

format CDATA #IMPLIED>

<!ATTLIST clip

length CDATA #IMPLIED>

<!ATTLIST clip

size CDATA #IMPLIED>

<!ATTLIST clip

src CDATA #IHPLIED>

<!ELEMENT title (#PCDATA)* >

Уточнение чернового варианта

Приведенное выше определение DTD правильно описывает документ XML, но все же является весьма расплывчатым. Чтобы определение DTD точнее определяло реальный каталог товаров, его нужно несколько доработать. Следующие несколько разделов будут посвящены последовательному анализу DTD и внесению необходимых исправлений.

Элементы catalog, productline и product

В первой строке содержится объявление корневого элемента catalog:

<!ELEMENT catalog (product_line)*>

Это объявление достаточно очевидное: в нем указывается, что catalog может состоять из любого количества элементов productJ i ne.

Элемент product_line тоже очень просто устроен, но в отличие от элемента catal og у него имеется атрибут name:

<!ELEMENT product_line (product)* >

<!ATTLIST product_line

name CDATA #IMPLIED>

В объявлении атрибута name элемента productjine указано, что этот атрибут не является обязательным (#IMPLIED). Но информация о принадлежности товара (элемент product) к некоторой серии товаров (элементу productjine) не имеет смысла, если мы не знаем названия этой серии. Поэтому соответствующий атрибут, name, мы сделаем обязательным. Внести это изменение несложно:

<!ATTLIST productjline

name CDATA #REQUIRED>

Следующее объявление — наиболее содержательная часть этого определения DTD [Photo — фотография, clip — клип. — Примеч. перев. ]:

<!ELEMENT product(name|description|price|
quantity_in_stock|photo| onsale_date|clip)* >

В таком виде это объявление подразумевает, что элемент product может содержать любое (в том числе нулевое) количество любых перечисленных здесь элементов. Это не соответствует фактической организации каталога на сайте XMLGifts.com. Также здесь не учитываются сведения об авторах книг (для элементов Books) и исполнителях (для элементов CDs). Такие промашки часто случаются, если используется недостаточно большой фрагмент исходных данных, для которых вы составляете DTD. Полностью исправленное объявление элемента product выглядит следующим образом:

<!ELEMENT product  name,author* , artist*,

description,price,quantity_in_stock,image*,

onsale_date?,clip*>

ПРИМЕЧАНИЕ

Обратите внимание, что в исправленной версии объявления элемента product указано, что элементы name, description, quantity_m_stock могут встречаться в одном элементе product только один раз. Заметим также, что они обязательно должны присутствовать в этом элементе. Далее обратите внимание, что элемент photo (фотография) был переименован в image (изображение) для большей общности.

Хотя получившееся определение DTD не идеально и, возможно, не учитывает все особенности каждого товара, все же это неплохое «первое приближение». Поскольку оно было сконструировано для конкретного типа документа, а не как DTD общего назначения, мы можем позволить себе пересматривать DTD по мере изменения каталога. Всегда предпочтительнее (и проще) начинать с более обобщенного определения DTD и добавлять к нему новые элементы по мере надобности. Этот метод называется аддитивным уточнением (additive refinement). Противоположный метод уточнения DTD, который сводится к удалению лишних объявлений элементов, называется субтрактивным уточнением (subtractive refinement).

После определения элемента product в DTD определяется атрибут id этого элемента:

<!ATTLIST product

id CDATA #IMPLIED>

Как и с атрибутом name элемента product_line, в каталоге у каждого товара должен быть однозначный идентификатор, поэтому следует сделать этот атрибут обязательным. Кроме того, чтобы гарантировать уникальность значения каждого атрибута id, то есть идентификатора каждого товара, тип атрибута должен быть ID. Исправленное объявление атрибута id выглядит следующим образом:

<!ATTLIST product

id ID #REQUIRED>

Кроме определения товара по его уникальному идентификатору также мы хотели бы иметь возможность отыскать этот товар по ключевым словам. Для этого мы добавим в элемент product еще один атрибут, keywords [Keyword — ключевое слово (характеризующее товар) — Примеч перев.]:

<!ATTLIST product

keywords CDATA #IMPLIED>

В нашем представлении значение атрибута keywords — это список с разделенными запятыми элементами, который можно использовать для поиска и группировки.

Элементы description, paragraph и general

Следующая часть DTD определяет элементы, дочерние по отношению к product. Дочерний элемент name в настоящий момент может содержать любую разновидность проверенных символьных данных, что вполне соответствует нашим требованиям. Элемент description, определенный таким же образом, содержит информацию, исходно предназначенную для прочтения людьми, но также в нем могут содержаться некоторые данные, которые мы хотели бы сделать доступными для программ, то есть которые можно было бы извлекать автоматически.

Чтобы сделать содержимое элемента description удобным для восприятия и для применения к нему в дальнейшем различных стилей, нужно предусмотреть возможность разделения элемента description на части, которые будут содержать элементы для отображения текста жирным шрифтом, курсивом и т. д. Для этого в первую очередь нужно, чтобы элемент description мог содержать элементы paragraph, и определить paragraph, как это сделано ниже [Paragraph — абзац (имеется в виду просто некая часть текста), bold — полужирный (шрифт), italics — курсив, quote — цитата, link — ссылка — Примеч перев ]:

<!ELEMENT description (paragraph*)>

<!ELEMENT paragraph (#PCDATA | bold |
italics|quote|link)*>

<!ELEMENT bold #PCDATA>

<!ELEMENT italics #PCDATA>

<! ELEMENT quote #PCDATA>

<!ATTLIST quote

attrib CDATA #IMPLIED>

<!ELEMENT link (#PCDATA)>

<!ATTLIST link

href CDATA «REQUIRED

alt CDATA #IMPLIED>

Существует большая вероятность того, что такой же список элементов разметки, который используется в элементе paragraph, потребуется в каком-то другом месте DTD (например, в элементе title или footnote, то есть в заголовке или в сноске). Чтобы упростить процесс создания DTD, мы ввели параметрическую сущность, представляющую собой список элементов разметки текста:

<!ENTITY %runmng_text "(#PCDATA | bold |
italics | quote | link)* ">

СОВЕТ

Напомним, что сущность — просто подстановочный текст, который пишется вместо заменяемого им значения, и что общие и параметрические сущности должны быть объявлены прежде, чем будут использованы. Поскольку параметрические сущности могут существовать только в DTD, то обычно их объявления сгруппированы в начале DTD. В нашем случае это тем более удобно, что при необходимости добавления какого-либо элемента разметки в элемент paragraph эту сущность будет легко найти.

Когда объявление параметрической сущности добавлено в начало DTD, объявление элемента paragraph можно переписать следующим образом:

<!ELEMENET paragraph %running_text;>

Если параметрическая сущность применяется правильно, это повышает удобочитаемость определения DTD и способствует его многократному использованию. Но будьте осторожны — так же, как следует избегать излишнего усложнения элементов, вам нужно сопротивляться искушению всюду вводить параметрические сущности. Если их окажется слишком много или они будут неправильно использоваться, пострадает удобочитаемость DTD.

Вторая цель переработки элемента description — обеспечить возможность автоматического извлечения из этих описаний содержательной информации. Для этого мы должны иметь возможность использовать другие элементы внутри элемента description. Предположим, что в нашем каталоге имеется следующий элемент description:

<description>

<paragraph>Эта яркая новая группа из Бельгии,
звучание которой можно определить как смесь
Iggy Pop и Spice Girls,стремительно завоевывает

популярность во всем мире.</paragraph>

</description>

Было бы замечательно, если бы наша схема позволила приложению использовать различные фрагменты этого описания для ссылок на близкие по назначению товары. Проблема же заключается в том, что в описание могут быть включены тысячи различных типов данных. Мы могли бы попытаться создать подробный список возможных фрагментов данных, которые могут встретиться в описании. Но это привело бы к созданию огромного по размерам объявления элемента, в котором было бы невозможно разобраться.

Хорошим способом обойти эту проблему является использование элемента general с атрибутом type. Идентифицирующую информацию можно сделать значением этого атрибута, что дает максимальную гибкость автору документа и тому, кто конструирует разметку элемента description, как видно из приведенного ниже листинга:

<!ELEMENT general #PCDATA>

<!ATTLIST general

type CDATA #REQUIRED>

Используя элементы general и paragraph, мы можем теперь разметить описание (то есть элемент description) в нашем примере таким образом, чтобы информацию из него могли извлечь и человек, и программа:

<description>

<paragraph>Эта яркая новая группа из

<general type="country">Бельгии</general>,
звучание которой можно определить как смесь

<general type="artist">Iggy Pop</general>и

<general type = "artist">Spice Girl</general>
стремительно завоевывает популярность во всем
мире.</paragraph>

</description>

Теперь приложение легко справится с задачей преобразования элементов general в пары имя-значение.

В результате объявление элемента description и его дочерних элементов будет выглядеть следующим образом:

<!ELEMENT description  (paragraph|general)* >

<!ELEMENT paragraph %running_text;>

<!ELEMENT bold #PCDATA>

<!ELEMENT italics #PCDATA>

<!ELEMENT quote #PCDATA>

<!ATTLIST quote

attrib CDATA #IMPLIED>

<!ELEMENT link  (#PCDATA)>

<!ATTLIST link

href CDATA #REQUIRED

alt CDATA #IMPLIED>

<!ELEMENT general #PCDATA>

<!ATTLIST general

type CDATA #REQUIRED>

Элементы price, quantity_in_stock и image

Следующий элемент, объявленный в DTD, — элемент price. Было бы замечательно, если бы сведения о ценах действительно имели такой простой формат, как указано в DTD:

<!ELEMENT price(#PCDATA)*>

Но реально ситуация с ценами сложнее и цена товара не выражается какой- то одной фиксированной суммой. В реальном мире для различных товаров могут быть предусмотрены различные скидки, а иногда на какие-то товары или группы товаров объявляется распродажа. Чтобы учесть скидки, в элемент price следует добавить атрибут с именем di scount:

<!ELEMENT price #PCDATA>

<!ATTLIST price

discount CDATA #IMPLIED>

База данных каталога не занимается определением того, как именно тот или иной клиент получает скидку (как член определенной привилегированной группы клиентов или по распродаже). Эти факторы контролируются приложением и базой данных о клиентах. База данных о клиентах содержит информацию как раз о том, какие клиенты или группы клиентов имеют право на скидки.

Все, что должно содержаться в элементе price, — это сведения о стоимости данного товара. Поскольку в стандартном определении DTD отсутствует возможность указать тип данных элемента, то за соответствие реальным ценам тех данных, которые содержатся в элементе price, отвечает приложение.

Следующий элемент DTD, quantity_in_stock, не требует больших изменений:

<!ELEMENT quantity_in_stock (#PCDATA>*>

Если убрать излишний символ оператора повторяемости (*), то элемент получится как раз таким, как надо:

<!ELEMENT quantity_in_stock (#PCDATA)>

Элемент image на самом деле не будет содержать изображений. В документы XML, которые, в сущности, являются простыми текстовыми файлами, не так-то легко вставить двоичные данные Вместо этого предлагается гораздо более простое (и компактное) решение — в элементе image и его дочерних элементах располагать только адреса, по которым можно отыскать файлы с изображениями товаров, и другую информацию об изображениях, например.

<!ELEMENT image  (caption)>

width CDATA #IMPLIED>

<!ATTLIST image
height CDATA #IMPLIED>

<!ATTLIST image

src CDATA #IMPLIED>

<!ELEMENT caption  (#PCDATA)*>

Некоторые товары из каталога XMLGifts.com не сопровождаются изображениями В тех случаях, когда изображение товара отсутствует, во многих электронных магазинах появляется определенная картинка, которая извещает посетителей магазина о невозможности предоставить изображение товара Эту замену можно было бы описать в DTD, введя некоторое значение атрибута scr, используемое по умолчанию (то есть когда не указано никакого другого значения) Но, рассмотрев эту возможность, компания XMLGifts решила просто внести соответствующие дополнения в само приложение, а не в DTD Причиной этого является то, что указанная картинка, используемая по умолчанию, на самом деле не содержит никаких сведений о товаре. Она требуется только для того, чтобы при отсутствии изображения товара не пришлось изменять единый для всех товаров дизайн страницы. Поскольку эта картинка связана скорее с представлением данных, чем с описанием товаров, ее не следует включать в DTD.

Хотя наличие изображений не является обязательным требованием для всех товаров, если элемент image все же присутствует, у него обязательно должен быть атрибут scr (источник) и необязательно — caption (подпись).

<!ELEMENT image (caption?)>

<!ATTLIST image

width CDATA #IMPLIED>

<!ATTLIST image

height CDATA #IMPLIED>

<!ATTLIST image

src CDATA #REQUIRED>

<!ELEMENT caption (#PCDATA)>

Существует еще одна деталь, которую необходимо уточнить при описании элемента image. Это способ указания формата изображения Чтобы гарантировать, что форматы изображения товаров окажутся приемлемыми для большинства web-браузеров, мы ограничим количество возможных форматов тремя — GIF, PNG и JPG Ограничения на формат можно записать с помощью следующего нового атрибута

<!ATTLIST image

format (gif|png|jpg) #REQUIRED>

Теперь у элемента image имеется один дочерний элемент (caption) и четыре атрибута, из которых два (scr и format) являются обязательными.

Заголовок изображения может содержать только символьные данные Здесь появляется прекрасная возможность — снова использовать элемент paragraph, который мы создали для элемента description

<!ELEMENT caption  (paragraph*)>

Хотя элемент image и его дочерние элементы фактически не содержат изображений, они предоставляют всю информацию, которая необходима для использования изображений товаров.

<!ELEMENT image (caption?)>

<!ATTLIST image format (gif|png|jpg) #REQUIRED

width CDATA #IMPLIED

height CDATA #IMPLIED

src CDATA #REQUIRED>

<!ELEMENT caption (paragraph*) >

Элементы onsale_date, time, clip и title

Следующий объявленный в DTD элемент — onsa!e_date. Исходным назначением этого элемента было обеспечение возможности внесения товаров в каталог заранее, еще до фактического начала их продажи В будущем возможно и другое применение этого элемента. Например, мы можем предоставить пользователю возможность просматривать новые товары или учитывать значение этого элемента (дату начала продаж) при составлении отчетов для внутреннего использования

Как отмечалось в главе 1, не существует способа указать в DTD, что значением некоего элемента должна быть дата Тем не менее можно создать несколько дочерних элементов onsale_date, чтобы более точно указать, данные какого типа ожидаются, а также чтобы формат этих данных был достаточно гибким и мог включать, например, день недели, число месяца или даже часы, минуты и секунды [Day of week — день недели, month — месяц, day of month — день месяца year — год, hour — час, minute — минута, seconds — секунды — Примеч перев]

<!ELEMENT onsale_date

(day_of_week?.month?,day_of_month?,year?,
(hour,minute, seconds?)?)>

<!ELEMENT day_of_week (#PCDATA)>

<!ELEMENT month (#PCDATA)>

<!ELEMENT day_of_month (#PCDATA)>

<!ELEMENT year (#PCDATA)>

<!ELEMENT hour (#PCDATA)>

<!ELEMENT minute (#PCDATA)>

<!ELEMENT seconds (#PCDATA)>

Здесь уже структура элемента становится несколько сложнее. Фактически в приведенном объявлении говорится, что каждый из дочерних элементов onsale_date может присутствовать один раз или отсутствовать Элемент time не является обязательным, но, если он присутствует, то и часы, и минуты (элементы hour и minute) должны быть указаны. Здесь мы можем несколько усовершенствовать DTD, введя параметрическую сущность date_time [Date — дата, time — время — Примеч перев ], которую можно будет повторно использовать в других элементах для указания даты и времени:

<!ENTITY % date_time"(day_of_week?,month?,
day_of_month?,year?,(hour.minute, seconds?)?)">

<!ELEMENT onsale_date %date_time;>

Чтобы включить в наше приложение отрывки из музыкальных записей, дающие покупателю представление о том или ином компакт-диске, в документе XML и в соответствующем определении DTD используется элемент clip (то есть музыкальный клип). Этот элемент функционирует так же, как элемент image, — он содержит ссылку на файлы мультимедиа, которые хранятся отдельно в базе данных XlL [Size — размер, format — формат — Примеч перев ]:

<!ELEMENT clip (title)* >

<!ATTLIST clip

format CDATA #IMPLIED>

<!ATTLIST clip

length CDATA #IMPLIED>

<!ATTLIST clip

size CDATA #IMPLIED>

<!ATTLIST clip

src CDATA #IMPLIED>

<!ELEMENT title (#PCDATA)* >

Возможно, вы обратили внимание на то, что название (title) определено как дочерний элемент, в то время как format, length, size и src определены как атрибуты. Хотя и title можно было бы определить как атрибут, но основная причина, по которой мы не сделали этого, заключается в том, что атрибуты format, length, size и src в первую очередь предназначены для приложения, в то время как title — для людей. Также учитывались соображения логической согласованности: в элементе image подпись (caption) также была определена как дочерний элемент.

Как и в предыдущих объявлениях элементов этого определения DTD, можно внести несколько изменений в элемент clip и его атрибуты и дочерние элементы, чтобы добиться более точного соответствия с реальным документом XML. В первую очередь следует изменить правило для самого элемента clip. Хотя вполне возможно, что для одного товара может быть несколько клипов (элементов clip), но трудно представить ситуацию, в которой для одного клипа потребуется несколько названий (то есть несколько элементов title для одного элемента clip). Мы можем переписать объявление для clip заново, чтобы учесть это соображение:

<!ELEMENT clip (title)>

Также может возникнуть ситуация, когда для какого-то клипа потребуются пояснения. Для этого в элемент clip можно включить элемент description, содержащий описание данного клипа:

<!ELEMENT clip (title, description?)>

Как и в случае с атрибутом format элемента image, значение атрибута format элемента clip должно быть ограничено несколькими возможными форматами мультимедиа:

<!ATTLIST clip format (mp3|mpeg|mov|rm) #REQUIRED>

Атрибут scr элемента clip также должен быть объявлен как обязательный атрибут. Что касается атрибутов scr и length, их можно оставить почти в том же виде, в котором они определены на данный момент. Мы думаем, что нам поначалу не придется слишком много работать с этими атрибутами; мы просто укажем их значения рядом со ссылкой на файл мультимедиа. На самом деле мы все равно можем сделать лишь немногое в отношении задания единиц или ограничений для этих полей. За проверку правильности этих атрибутов отвечает само приложение.

Элемент shipping

Наконец, нам нужен элемент, описывающий требования по доставке товаров покупателю. Хотя исходно каталог был рассчитан на товары, которые доставляются покупателю посылкой, но на случай расширения каталога нужно предусмотреть и другие возможности, например пересылку определенных видов товаров по электронной почте или пересылку каких-то особых товаров, для которых имеются специфические требования [Shipping info — информация о доставке — Примеч перев. ].

<!ELEMENT shipping_info >

<!ATTLIST shipping_info

type CDATA #REQUIRED>

<!ATTLIST shnpping_infо

value CDATA #IMPLIED>

Вместо того чтобы указывать какую-то одну характеристику отправляемого товара (например, вес), мы выбрали более общий термин — величина (атрибут value). Этот атрибут можно легко приспособить к любому типу товаров и способу их доставки; например, если мы будем продавать электронные версии книг, загружаемые через Интернет, то данный атрибут будет идентифицировать размер книги в мегабайтах.

Финальная версия

После того как мы внесли все указанные выше исправления в первую черновую версию, мы можем скомпоновать окончательную версию определения catalog.dtd и создать пример каталога catalog.xml. Он довольно сильно отличается от первой версии и (как мы надеемся) гораздо лучше соответствует нашим требованиям. Листинг 2.6 содержит последнюю версию нашего определения DTD.

Листинг 2.6. Исправленный файл catalog .dtd

<!ENTITY % running_text "(#PCDATA | bold |
italics | quote | link | general)*">

<!ENTITY % date_time "(day_of_week?, month?,
day_of_month?, year?,(hour, minute, seconds?)?)">

<!ELEMENT catalog (product_line*) >

<!ELEMENT product_line (product*)>

<!ATTLIST product_line name CDATA #IMPLIED>

<!ELEMENT product (name,author*,artist*,description,
price,quantity_in_stock,image*,
onsale_date?,clip*,shipping_info*)>

<!ATTLIST product id ID #REQUIRED>

<!ATTLIST product keywords CDATA #IMPLIED>

<!ELEMENT name (#PCDATA)>

<!ELEMENT author (name)>

<!ELEMENT artist (name)>

<!ELEMENT description (paragraph|general)* >

<!ELEMENT paragraph %running_text;>

<!ELEMENT bold (#PCDATA)>

<!ELEMENT italics (#PCDATA)>

<!ELEMENT quote (#PCDATA)>

<!ATTLIST quote attrib CDATA #IMPLIED>

<!ELEMENT link (#PCDATA)>

<!ATTLIST link href CDATA #REQUIRED alt CDATA #IMPLIED>

<!ELEMENT general (#PCDATA)>

<!ATTLIST general type CDATA #REQUIRED>

<!ELEMENT price (#PCDATA)>

<!ATTLIST price discount CDATA #IMPLIED>

<!ELEMENT quantity_in_stock (#PCDATA)>

<!ELEMENT image (caption?)>

<!ATTLIST image format (gif|png|jpg)
#REQUIRED width CDATA
#IMPLIED height CDATA
#IMPLIED src CDATA
#REQUIRED>

<!ELEMENT caption (paragraph)* >

<!ELEMENT onsale_date %date_time;>

<!ELEMENT day_of_week (#PCDATA)>

<!ELEMENT month (#PCDATA)>

<!ELEMENT day_of_month (#PCDATA)>

<!ELEMENT year (#PCDATA)>

<!ELEMENT hour (#PCDATA)>

<!ELEMENT minute (#PCDATA)>

<!ELEMENT seconds (#PCDATA)>

<!ELEMENT clip (title,description?)>

<!ATTLIST clip format (mp3|mpeg|mov|rm)
#REQUIRED length CDATA
#IMPLIED size CDATA
#IMPLIED src CDATA
#REQUIRED>

<!ELEMENT title (#PCDATA)>

В листинге 2.7 содержится правильно оформленный и допустимый документ XML, в котором используется DTD из файла catalog.dtd. Это только фрагмент исходного текста для полного каталога, который вы найдете на прилагаемом к данной книге компакт-диске.

Листинг 2.7. Пример каталога из файла catalog.xml

<?xml version="1.0" standalone="no"?>

<!DOCTYPE catalog SYSTEM "catalog.dtd">

<!-- modified 10/12/2000 -->

<catalog>

<product_line name="Books">

<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/plantsv1.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_info type="UPS" value="1.0" />

</product>

 <product id="bk0023" keywords="gardening,
plants">

<name>Guide To Plants, Volume 2</name>

<description>

<paragraph>Everything else 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/plantsv2.gif">

<caption>

<paragraph>This is the cover from the first
edition.</paragraph>

</caption>

</image>

<onsale_date>

<month>4</month>

<day_of_month>8</day_of_month>

<year>2000</year>

</onsale_date>

<shipping_info type="UPS" value="1.0" />

</product> <product id="bk0024"
keywords="how-to, technology">

<name>The Genius's Guide to the 3rd Millenium</name>

<author>

<name>Christoph Minwich</name>

</author>

<description>

<paragraph>Learn to convert your replicator
into a transporter..and other neat tricks.</paragraph>

</description>

<price>$59.95</price>

<quantity_in_stock>0</quantity_in_stock>

<image format="gif" width="234" height="400"
src="images/covers/millenium.gif">

</image>

<onsale_date>

<month>1</month>

<day_of_month>1</day_of_month>

<year>2001</year>

</onsale_date>

<shipping_info type="UPS" value="2.0" />

</product>

<product id="bk0025" keywords="how-to, art">

<name>Dryer Lint Art</name>

<description>

<paragraph>A new book about the new folk art
that's catching on like wildfire.</paragraph>

</description>

<price>$5.95</price>

<quantity_in_stock>34</quantity_in_stock>

<image format="gif" width="200" height="200"
src="images/covers/dryerart.gif">

</image>

<onsale_date>

<month>11</month>

<day_of_month>3</day_of_month>

<year>1971</year>

</onsale_date>

<shipping_info type="UPS" value="1.5" />

</product>

</product_line>

<!-- end Book Product Line -->

<!-- being CDs Product Line -->

</product_line>

<product_line name="CDs">

<product id="cd0023" keywords="music,easy
listening,pets,dogs">

<name>Music for Dogs</name>

<description>

<paragraph>Keep your pets calm while you're
away from the house! Each of the 15 tracks
on this CD has been scientifically shown to
relax pets of all kinds.</paragraph>

</description>

<price>$14.99</price>

<quantity_in_stock>50</quantity_in_stock>

<image format="gif" width="200" height="200"
src="http://www.musicfordogs.com/images/cover.gif">
 </image>

<onsale_date>

<month>3</month>

<day_of_month>2</day_of_month>

<year>1990</year>

</onsale_date>

<clip format="mp3" length="2:12" size="1.6 Mb"
src="http://www.musicfordogs.com/sounds/track1.mp3">

<title>Track 1: Fetching the Stick</title>

<description>

<paragraph>An exciting and playful melody.</paragraph>

</description>

</clip>

<shipping_info type="UPS" value="0.5" />

</product>

<product id="cd0024" keywords="music,easy listening">

<name>Just Singin' Along</name>

<description>

<paragraph>A lovely collection of songs that the
whole family can sing right along with.</paragraph>

</description>

<price>$10.00</price>

<quantity_in_stock>100</quantity_in_stock>

<onsale_date>

<month>2</month>

<day_of_month>23</day_of_month>

<year>2000</year>

</onsale_date>

<shipping_info type="UPS" value="0.5" />

</product>

<product id="cd0025" keywords="music,folk">

<name>It's Dot Com Enough For Me: Songs
From Silicon Somewhere</name>

<description>

<paragraph>A collection of the best folk
music from Internet companies.</paragraph>

</description>

<price>$12.99</price>

<quantity_in_stock>4</quantity_in_stock>

<onsale_date>

<month>4</month>

<day_of_month>12</day_of_month>

<year>2000</year>

</onsale_date>

<clip format="mp3" length="4.32"
size="4.0 Mb" src="track2.mp3">

<title>Track 2: My B2B is B-R-O-K-E</title>

</clip>

<shipping_info type="UPS" value="0.5" />

</product>

</product_line>

<!-- end CDs product line -->

<!-- begin Widgets product line -->

<product_line name="widgets">

<product id="wg0026" keywords="appliance,electronics">

<name>ElectroThermal Oxidizer</name>

<description>

<paragraph>This amazing gizmo uses electricity
 to produce heat that can be used for oxidization
purposes.</paragraph>

</description>

<price>$24.95</price>

<quantity_in_stock>10</quantity_in_stock>

<image format="jpg" width="208" height="178"
src="/images/toaster.jpg"> </image>

<onsale_date>

<month>6</month>

<day_of_month>2</day_of_month>

<year>2000</year>

 </onsale_date>

<shipping_info type="UPS" value="12.0" />

</product>

<product id="wg0027" keywords="computing,
electronics, input device">

<name>Percusive Interface Unit</name>

<description>

<paragraph>Communicate with your favorite
electric calculating machine--through
tapping!</paragraph>

</description>

<price>$109.99</price>

<quantity_in_stock>7</quantity_in_stock>

<onsale_date>

<month>6</month>

<day_of_month>23</day_of_month>

 <year>2001</year>

</onsale_date>

<shipping_info type="UPS" value="12.0" />

</product>

<product id="wg0028" keywords="outdoors">

 <name>Umbrella</name>

<description>

<paragraph>Imagine going out into the rain
and NOT getting wet! The amazing umbrella
makes it possible.</paragraph>

</description>

<price>$10.99</price>

<quantity_in_stock>7</quantity_in_stock>

<onsale_date>

<month>7</month>

<day_of_month>2</day_of_month>

<year>2000</year>

</onsale_date>

<shipping_info type="UPS" value="10.0" />

</product>

<!-- end widgets product line -->

<!-- end catalog -->

</catalog>

Принципы разработки DTD

Если вы взяли на себя труд внимательно проследить весь процесс создания DTD для каталога товаров, описанный нами в этой главе, возможно, вы отчаялись и решили никогда больше не связываться с XML и DTD. Но мы не советуем вам сдаваться, так как на самом деле существует несколько удобных обобщений, которые используются при создании DTD. В этом разделе мы обсудим наиболее полезные из них.

В первой главе мы говорили о двух различных языках для определения классов документов XML: язык определения типов документов, который включен в спецификацию XML 1.0, и схемы XML. В настоящее время обсуждается возможность принятия в качестве стандарта определения типа документа других методов, в том числе XML-Data и DCD (Document Content Description — описание содержимого документа).

ПРИМЕЧАНИЕ

Хотя в нашей книге при определении типа документа мы используем спецификацию XML 1.0, многие концепции, упоминаемые в этом разделе, не характерны для определения типа документа, построенного с помощью этой конкретной разновидности языка.

Определенная разновидность языка разметки электронного текста существовала уже несколько десятилетий назад, и за прошедшие годы у людей сформировался определенный опыт в области разметки документов. Создатели XML учли те проблемы, с которыми сталкивались авторы, использовавшие SGML, и намеренно освободили XML от некоторых из них. Достичь этого помогло более простое, чем SGML, устройство языка XML. В то же время создатели XML смогли обеспечить совместимость этого языка с SGML.

За последние несколько лет, тем не менее, авторы, пишущие на XML, столкнулись со многими затруднениями как свойственными языку SGML, так и специфическими для XML. Ниже мы приводим несколько советов, которые помогут вам избежать некоторых проблем, возникающих при разработке DTD.

Репрезентативный образец данных

При создании DTD общего назначения главная задача заключается в том, чтобы проанализировать достаточно большую подборку данных. При этом вы, с одной стороны, не должны упустить из виду никаких существенных деталей, но, с другой стороны, не следует и вдаваться в избыточные подробности, раздувая DTD до огромных размеров.

Следует помнить, что нельзя объять необъятное. Если стремиться к этому, вы увязнете в бесконечных спорах и обсуждениях. История показывает, что, когда люди пытаются договориться о стандартах в какой бы то ни было области, будь то языки разметки, программирование или строительство, — всегда существует вероятность углубиться в длительные дискуссии, оставляя нерешенным главный вопрос. Поэтому следует принять волевое решение и остановиться на пусть несовершенном и, возможно, слишком общем определении DTD, которое впоследствии можно будет при необходимости доработать и дополнить, вместо того чтобы продолжать бесконечные споры.

Избегайте субтрактивного уточнения

Авторам, пишущим на HTML, хорошо известно, что субтрактивное уточнение гораздо сложнее, чем аддитивное. Создатели браузеров за годы существования HTML сделали множество дополнений (или расширений) этого языка. В свое время многие из этих дополнений играли важную роль, отвечая определенным потребностям, но теперь они только тормозят развитие языка и должны быть удалены как из спецификации HTML, так и из употребления.

Самым известным примером таких дополнений HTML является элемент font (шрифт). Когда этот элемент был введен, не существовало никакого жизнеспособного альтернативного способа указать, как именно должен форматироваться текст. В настоящее время имеются каскадные таблицы стилей (Cascading Style Sheets, CSS) и, более того, расширяемый язык таблиц стилей (Extensible Stylesheet Language, XSL), которые предназначены как раз для работы с форматом представления документов. Но до сих пор многие (если не большинство) web-дизайнеры используют элемент font для форматирования текста. Причина в том, что web-дизайнеры хорошо знакомы с этим элементом и знают в точности его действие на текст документа. Каскадные таблицы стилей являются гораздо более гибким, но и более сложным инструментом; кроме того, реализация CSS в каждом из браузеров сопровождается различными сбоями и неполадками, пока что недостаточно хорошо изученными.

Последние несколько лет Консорциум W3C пытается исключить элемент font из HTML. Но так как этот элемент присутствует в очень многих web-страницах и очень многие web-дизайнеры весьма активно его используют, видимо, пройдет еще немало времени, прежде чем элемент font прекратит свое существование. Было бы замечательно, если бы всегда удавалось избегать субтрактивного уточнения, но реально это не всегда получается. Тем не менее можно постараться минимизировать количество элементов, которые впоследствии, возможно, придется удалять из документа.

Первый шаг, который следует предпринять, если вы хотите минимизировать потребность в субтрактивном уточнении, — это осознание причин, по которым такое уточнение вообще становится необходимым. Основной причиной удаления объявлений из DTD является то, что эти объявления задают типы данных или правила, не соответствующие или вступающие в противоречие с теми реалиями данной сферы деятельности, которые призваны отображать. Например, представьте себе, что у вас имеется DTD для разметки документов, описывающих счета за предоставленные услуги. Допустим, в вашей фирме оплата производится по фиксированной шкале, за каждый вид работ назначена определенная сумма. Но определение DTD могло сохраниться с тех времен, когда присутствовала и другая форма оплаты — почасовая. Соответствующие объявления могли бы выглядеть следующим образом [Invoice — счет, service — вид работ, duration — затраченное время, rate — оплата за час работы, fee — стоимость работ, totaldue — общая сумма к оплате. — Примеч. перев. ]:

<!ELEMENT invoice (service*,total_due)>

<!ELEMENT service (description,(duration,rate)?,fee?)>

<!ELEMENT description (#PCDATA)>

<!ELEMENT duration (hours.minutes)>

<!ELEMENT rate (#PCDATA)>

<!ELEMENT fee (#PCDATA)>

<!ELEMENT total_due(#PCDATA)>

Поскольку в вашей фирме больше не используется почасовая оплата, эти объявления становятся ненужными и только вносят путаницу. Например, если ваша фирма за какую-то работу берет с клиента 300 долларов и эта работа выполняется за 3 часа, то существуют по меньшей мере два способа записать эту информацию:

<!-первый способ записи-->

<invoice> <service>

<description>Установка бензинового
нacoca</description>

<duration>

<hours>3</hours>

<minutes>0</minutes>

</duration>

<rate>$100</rate>

<fee>$300</fee>

</service>

</invoice>

<!-второй способ записи-->

<invoice>

<service>

<description>Установка бензинового
нacoca</description>

<fee>$300</fee>

</service>

</invoice>

Согласно существующему определению DTD, оба эти способа корректны, но только второй отражает реальную ситуацию. Чтобы исключить возможность некорректного использования этого определения, следует удалить элементы rate и duration, как это сделано во втором способе записи.

Упрощайте DTD

Не делайте определение DTD настолько сложным, чтобы его нельзя было легко и просто прочитать. Чем легче для чтения и понимания будет ваше DTD, тем оно окажется полезнее. Если вы разрабатываете DTD для очень специализированной области применения, оно неизбежно будет сложнее, чем DTD для обычного каталога товаров. В листинге 2.8 представлены два определения DTD, причем второе выполняет те же функции, хотя гораздо проще первого [Phone call — телефонный звонок, valid phone digits — допустимые телефонные символы, from — от, to — кому, content — содержимое, phone number — телефонный номер, digit — цифра (символ), dash — тире, caller — вызывающий абонент, answerer — отвечающий абонент — Примеч. перев. ].

Листинг 2.8. Два возможных DTD для описания телефонного звонка.

<!—DTD для элемента phone_call-->

<!ENTITY % valid jihone_ch gits  '"
(1|2|A|B|C|3|D|E|F|4|G|H|I|5|J|K|L|6|M|N|
0|7|P|R|S|8|T|U|V|9|W|X|Y|0)">

<'ELEMENT phone_call  (from*.to*,content)>

<!ELEMENT from  (phone_number)>

<!ELEMENT phone_number  ((dig!t?,digit'.digit?,dash?)?,

digit, digit, digit, dash, digit, digit,digit,digit)>

<!ELEMENT digit EMPTY> <!ATTLIST digit number
%valid_phone_dlgits;  #REQUIRED>

<!ELEMENT dash  EMPTY>

<!ATTLIST dash

value CDATA #FIXED  "-">

OELEMENT to  (phone_number)> <iELEMENT content
(caller|answerer)*> <'ELEMENT caller  (#PCDATA)>
<!ELEMENT answerer  (#PCDATA)>

<!—упрощенное определение DTD для элемента phone_call-->

<!ELEMENT phone_call  (from*,to*,content)>

<!ELEMENT from EMPTY> <!ATTLIST from
phone_number CDATA #REQUIRED>

<!ELEMENT to EMPTY>

<!ATTL1ST to phone_number CDATA #REQUIRED>

<!ELEMENT content (caller|answerer)*>

<!ELEMENT
caller (#PCDATA)>

<!ELEMENT answerer (#PCDATA)>

В большинстве случаев достаточно использовать второе, упрощенное определение DTD, так как первое является слишком сложным. Сколько потребуется времени, чтобы разобраться в нем и понять, что это всего лишь определение обычного телефонного номера? В данном случае возможная область применения DTD, несомненно, преувеличена. Нужно ли нам на самом деле выделять каждую цифру телефонного номера? Может быть, данный пример несколько утрирован, но он иллюстрирует основную идею' не злоупотребляйте подробностями при создании DTD или документов XML. Здесь существует некий предел, переход за который может оказаться губительным.

Элементы или атрибуты?

Вероятно, при разработке DTD наиболее часто задается и наиболее горячо обсуждается вопрос о том, в каких случаях следует использовать элементы, а в каких — атрибуты. Например, если у вас имеется книга и ее название, как вы определите, какой из следующих способов представления данных правилен:

  • первый способ:

<Book>

<Titlе>Справочник по растениям</Тitle>

</Book>

  • второй способ:

<Book Title="Справочник по растениям"> </Book>

Суть заключается в том, что по большому счету это дело личного выбора. Как и в большинстве ситуаций, в этом вопросе имеются прямо противоположные точки зрения. Некоторые утверждают, что использование атрибутов упрощает программный доступ к данным в документах XML. Побочным эффектом предпочтения атрибутов является то, что получившийся документ становится сложнее для человеческого восприятия Другие считают, что данные следует организовывать исключительно в виде элементов. Большинство же придерживается золотой середины, применяя как атрибуты, так и элементы для записи данных.

Существует, однако, несколько правил, которые были выработаны уважаемыми авторами, долгое время пишущими на XML и SGML. Этими правилами можно руководствоваться в вопросе о том, как следует оформить конкретный фрагмент данных — в виде элемента или в виде атрибута. Чтобы пользоваться этими правилами, нужен некоторый опыт. Также следует помнить, что из любых правил существуют исключения.

  • Атрибуты следует использовать для записи информации о содержимом (метаданные), в то время как элементы следует использовать для записи фактической информации. Например, цвет обложки книги может быть отнесен к метаданным и записан как атрибут. Абзац из этой книги следует рассматривать как фактическое содержимое.

  • Для записи данных перечислимого типа (например, номера товара в списке или его уникального идентификатора) следует использовать атрибуты.

  • При записи данных, предназначенных для программного прочтения, используйте атрибуты. Для записи данных, которые будут читать люди, используйте элементы.

  • Если важен порядок следования значений, используйте элементы. (Причина заключается в том, что невозможно задать порядок следования атрибутов, но порядок следования элементов можно смоделировать нужным образом.)

Рассуждайте в терминах реальных процессов

При разработке DTD лучше рассуждать в терминах реальных бизнесс-процес- сов, а не в терминах конкретного приложения. Если вы не делаете никаких предположений о том, как и кем будет обрабатываться документ, вы составите гораздо более гибкое определение DTD. Например, хотя с большой вероятностью можно предположить, что DTD для нашего электронного магазина будет гораздо чаще обрабатываться приложением, чем изучаться людьми, это обстоятельство при разработке DTD не учитывается. Если бы мы составляли определение DTD, которое заведомо использовалось бы только программами, мы, к примеру, могли бы сэкономить значительный объем памяти за счет более коротких (но менее понятных!) имен элементов и за счет более широкого использования параметрических сущностей. Но такой подход искажает саму суть языка XML, предназначенного как раз для того, чтобы чтение документа было легким как для машин, так и для людей. Жертвуя пространством на диске и скоростью работы приложения, вы получаете гораздо более значимый выигрыш — гибкий и удобный в использовании документ XML с корректной разметкой данных.

Шаблоны XML

Шаблоны XML — это готовые решения распространенных проблем, возникающих в определенных ситуациях. В последнее время шаблоны все больше привлекают внимание как способ совершенствования процесса разработки приложений и обмена полезной информацией как внутри отдельных компаний, так и в сфере разработки программного обеспечения в целом.

Несмотря на большое количество появившихся в последнее время книг и статей о шаблонах, лежащая в их основе идея не представляет собой ничего нового. Это та же идея, на которой основаны книги по нетрадиционной медицине, диетическому питанию и оздоровительным процедурам, — сходные задачи имеют сходные решения. В шаблоне просто формулируется контекст задачи, сама задача и способ ее решения. Шаблоны обычно составляются некоторым стандартным образом, что облегчает их чтение и понимание.

Если при конструировании DTD вы будете использовать исключительно готовые шаблоны, вы, вероятнее всего, не сделаете грубой ошибки, но одновременно вряд ли добьетесь оптимального решения. Тем не менее очень часто оказывается, что кто-то другой уже сталкивался с такой же задачей, которую пытаетесь решить вы, и, заглянув в архив шаблонов, вы найдете искомое решение и вам не придется изобретать велосипед.

Для описания шаблонов имеется определенный стандарт, состоящий из нескольких разделов (краткое описание шаблона, контекст, пример и т. д.). Ниже описывается шаблон универсального элемента (generic element) для web-документов. Этот шаблон и многие другие можно найти на сайте www.xmlpatterns.com.

Краткое описание

Для обеспечения максимальной гибкости был сконструирован универсальный элемент. Область применения этого элемента не фиксируется жестко, поэтому авторы могут использовать его по своему усмотрению.

Задача

Основная задача, стоявшая перед создателями шаблона, — обеспечить гибкость конструкции, так как не известно в точности, каким образом авторы документов будут использовать универсальный элемент.

Контекст

Этот элемент можно использовать в документах общего назначения, в которых важным требованием является гибкость.

Причины

Более гибкие типы документов могут оказаться более удобными. Универсальный элемент облегчает обработку документа.

Решение

Шаблон универсального элемента создает новый элемент, не имеющий специфического назначения.

Пример

Ниже приводится пример кода шаблона:

<Paragraph>

Повесть Алберта Камю <General
type="book">Посторонний</General>
служит примером выражения идей
экзистенциализма в литературе

</Paragraph>

Обсуждение

Недостаток гибкости может послужить причиной использования авторами документа неподходящего элемента для разметки. Это может привести к тому, что документы будут трудны для восприятия и при их обработке возникнут ошибки.

При обработке конструкций, отвечающих требованиям гибкости, могут возникнуть некоторые проблемы. Добавление атрибута role к универсальному элементу дает обрабатывающим программам некоторые ключи к тому, как следует обрабатывать этот элемент.

Близкие по назначению шаблоны

Вместе с универсальным элементом часто используется шаблон Role Attribute (атрибут role).

Известные примеры применения

Элементы div и span в XHTML являются распространенными примерами применения данного шаблона.

Стандартизация DTD

В настоящее время во многих приложениях, которые используют XML, DTD отсутствует. Это в основном относится к тем приложениям, которые связаны с передачей сообщений, а не с хранением данных. Даже если вы не создаете формального определения DTD, вам все равно приходится думать о том, как лучше разметить данные в вашем приложении. Выбор способа разметки может оказаться достаточно сложным и длительным процессом.

Многие из тех, кто использует XML для создания приложений, надеются, что в будущем им не придется так много беспокоиться по поводу разработки DTD. Когда будет принято соглашение о языке схем, лучше других приспособленном для описания данных, будет гораздо проще стандартизировать и многократно использовать определения типов документов.

Хотя пользовательское определение DTD, подобное тому, которое мы разработали в этой главе для сайта XMLGifts.com, может быть очень мощным инструментом, достоинства XML проявятся в полную силу лишь тогда, когда большее количество организаций договорятся о стандартных схемах.

В приведенном ниже списке перечислены некоторые схемы XML для электронной коммерции, рекламируемые в настоящее время различными производителями и организациями.

  • Commerce One, поставщик программного обеспечения для электронной коммерции, предлагает «общую бизнес-библиотеку» — Common Business Library (CBL) — открытые спецификации XML для обмена документами между разными промышленными секторами. Такими документами могут быть заказы на приобретение, описания продуктов или схемы поставок.

  • Организация UN/CEFACT (The United Nations Centre for the Facilitation of Procedures and Practices for Administration, Commerce and Transport — Центр по упрощению процедур и практики в управлении, торговле и на транспорте) при ООН реализует проект EbXML (XML for electronic bismess — XML для электронного бизнеса), направленный на создание «единого глобального электронного рынка», который поддерживается Организацией OASIS (Organization for the Advancement of Structured Information Standards). Согласно определению, приведенному на web-сайте www.ebxml.org, EbXML — это открытый глобальный основанный на XML стандарт для электронной коммерции.

  • Согласно IBM, язык BRML (Business Rules Markup Language — язык разметки для деловых операций) — это «промежуточный язык для службы Agent Communication, основанный на программах Courteous/Ordinary Logic». BRML используется совместно с CommonRules IBM — библиотекой Java, которая обеспечивает функциональность для деловых операций.

  • Язык traML (Trading Partner Agreement Markup Language — язык разметки для соглашений между торговыми партнерами) предложен компанией IBM как язык для организации электронных контактов между торговыми партнерами.

  • В настоящее время разработана спецификация ОТР (Online Trading Protocol — открытый торговый протокол), которая представляет собой структуру для осуществления деловых транзакций на основе XML, независимую от систем оплаты. Протокол ОТР был разработан несколькими банками и компаниями, занимающимися проведением коммерческих расчетов через Интернет, в том числе SET, Mondex, CyberCash, DigiCash и GeldKarte.

Гибкость и возможность многократного использования данных являются ключевыми принципами при развитии удобных в управлении приложений XML. Тщательная разработка DTD или, если возможно, соглашение о едином стандарте DTD в данной области должны стать первым шагом при разработке любого серьезного приложения XML. В следующих главах нашей книги при обсуждении вопросов манипулирования данными XML с помощью языка Java всегда будет использоваться определение DTD (хотя время от времени его придется модифицировать) для обеспечения структурированности и целостности данных.


  • Глава 3. Представление XML-каталога в сети
    • Технологии представления
      • Взаимодействие по протоколу HTTP
        • Запрос браузера
        • Ответ web-сервера
      • API для сервлетов Java
        • Классы и интерфейсы для сервлетов Java
        • Обработка запросов сервлетами
        • Простой пример сервлета
        • Инициализация сервлета
        • Генерирование ответа сервлетом
        • Роль JavaBeans
      • API для JSP-страниц
        • Языктегов JSP
        • Пользовательские библиотеки тегов
        • Обработка запросов JSP
        • Встроенные переменные в JSP-страницах
    • Организация каталога
      • API для объектной модели документа
      • Создание объектной модели документа для каталога товаров
        • Интерфейс Node
        • Интерфейс NodeList
        • Интерфейс Document
        • Индексация товаров
    • Информация для представления каталога в сети
      • Организация поиска по ключевым словам
      • Форматирование описаний товаров
        • Гибкость стилей
        • Гибкость содержимого

Глава 3.

Представление XML- каталога в сети

В этой главе мы приводим обзор интерфейсов API (Application Programming Interface — интерфейс прикладных программ) для сервлетов Java, для JSP-страниц и для манипулирования элементами XML. Перечисленные инструментальные средства весьма существенны для создания динамических web-страниц на основе данных XML. Затем, используя эти интерфейсы API, мы рассмотрим различные подходы к представлению в сети каталога, созданного нами в главе 2.

Технологии представления

При создании коммерческого сайта в Интернете программисты, пишущие на языке Java, имеют очень широкий выбор различных вариантов. Все эти варианты, однако, ограничены возможностями web-протоколов. Наиболее существенным ограничением является следующий аспект взаимодействия web-браузера клиента и web-сервера: каждый запрос пользователя инициирует один ответ сервера. Такое взаимодействие называется неустойчивым (stateless), так как в протоколе отсутствует требование, чтобы на сервере сохранялась какая-либо информация о транзакции после того, как ответ сервера отослан клиенту.

Взаимодействие по протоколу 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

Ответ 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 для сервлетов 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- сервера в течение длительного времени, ответ на запрос, обращенный к сервлету, формируется очень быстро. Этот способ работает гораздо быстрее, чем технологии, в которых приходится порождать новый процесс и загружать приложение каждый раз, когда возникает запрос.

Классы и интерфейсы для сервлетов 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 определены только два исключения. ServletException — это исключение общего назначения, используемое в классах сервлетов, в то время как исключение UnavaliableException возникает в случаях, когда сервлет должен сообщить, что он временно или постоянно недоступен. Эти классы не наследуют класса RuntimeException, поэтому, если некий метод объявляет, что он вызвал исключение ServletException, вызывающий метод должен перехватить это исключение.

Пакет 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

Обработка запросов сервлетами

В обычных коммерческих приложениях запросы пользователей обрабатываются в следующей последовательности.

  1. Данные, передаваемые в запросе, используются для создания объекта HttpServletRequest, который содержит информацию из заголовка запроса и всю остальную дополнительную информацию. Также создается объект HttpServletResponse в ходе подготовки к созданию ответа на запрос.

  2. Вызывается метод service сервлета со ссылками на указанные два объекта. На основании типа запроса принимается решение, какой из методов обработки запроса следует вызвать в данном случае. Специализированные сер- влеты обычно не отменяют метод service, но могут заменить методы doGet и/или doPost.

  3. Метод doGet или doPost исследует запрос и определяет, какие действия должно осуществить приложение. Во всех приложениях, за исключением самых простых, сервлеты обычно используют другие объекты для выполнения запросов к базам данных или для вычислений.

Простой пример сервлета

Обычное приложение с сервлетами включает в себя класс, который расширяет класс 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.

Инициализация сервлета

Когда контейнер сервлета загружает код и создает экземпляр класса сервлета, 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 обычно устанавливает связь с базами данных и открывает файлы регистрации.

Генерирование ответа сервлетом

Все ресурсы, необходимые для контроля за созданием ответа, содержатся в интерфейсах 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, то есть поток простых двоичных данных, когда преобразование не происходит.

Роль JavaBeans

Огромный успех языка Java обусловлен его простой архитектурой, основанной на компонентах JavaBeans. Хотя сначала планировалось использовать их как компоненты графического интерфейса, вскоре оказалось, что они весьма полезны и в неграфических приложениях. JavaBeans — это просто класс Java, который удовлетворяет следующим критериям:

  • он должен быть открытым и реализовывать Seri al i zabl e;

  • класс должен иметь конструктор без параметра;

  • доступ к любым переменным, используемым другими классами, в классе JavaBeans осуществляется через методы setXxx и getXxx.

Создавая классы и называя методы в соответствии с этими простыми условиями, можно частично автоматизировать конструирование различных приложений, используя готовые компоненты, особенно в технологии JavaServer Pages. Вы, вероятно, слышали название Enterprise JavaBeans в контексте разговоров о серверах web-приложений. Технология Enterprise JavaBeans значительно отличается от JavaBeans и является гораздо более сложной.

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.

Язык тегов 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-браузер клиента

Пользовательские библиотеки тегов

Удобным свойством интерфейса API для JavaServer Pages является возможность определять пользовательские библиотеки тегов. Это очень мощное средство, позволяющее задействовать специализированные инструментальные средства так же легко, как стандартные теги.

Пользовательские теги задействуют интерфейсы и классы пакета javax.se- rlet. jsp.tagext. Пользовательские библиотеки тегов намного упрощают работу по созданию JSP-страниц.

Обработка запросов JSP

Ниже описана последовательность событий, происходящих при обработке запроса, обращенного к JSP.

  1. Запрос, обращенный к JSP, направляется web-сервером к процессору JSP (JSP engine).

  2. Процессор JSP отыскивает соответствующий сервлет, основываясь на имени страницы. Если сервлет существует, то запрос передается методу сервлета _jspService с помощью объектов HttpServl etRequest и HttpServl etResponse, как это происходит с обычным сервлетом.

  3. Если исходная страница изменилась или еще не была скомпилирована, то компилятор обрабатывает исходный код и создает эквивалентный исходный код Java для сервлета, реализующего интерфейс HttpJspPage.

  4. Затем код компилируется и выполняется новый сервлет. Сервлет может оставаться в памяти, что позволяет очень быстро отвечать на следующий запрос.

Объекты request и response — это в точности те же самые объекты, которые используются в обычных сервлетах; разница заключается только в том, что метод service создается процессором JSP.

Встроенные переменные в 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 или одного из его подклассов | Содержит только те страницы, которые обозначены в директиве страницы как ошибочные

Организация каталога

Каталог товаров, созданный нами в виде документа XML, организован последовательно. Однако мы хотели бы, чтобы пользователи имели возможность более гибкого доступа к элементам каталога, то есть чтобы им не приходилось просматривать страницу за страницей в поисках нужного товара. В этом разделе мы рассмотрим технологии Java, позволяющие организовать такой гибкий доступ к элементам каталога.

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.

Создание объектной модели документа для каталога товаров

Как мы говорили в главе 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.

Интерфейс 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, если данная позиция списка пуста.

Интерфейс Document

Объект Java, который инкапсулирует весь документ XML, реализует расширение Document интерфейса Node. Большая часть методов этого интерфейса связана с созданием или модифицированием DOM в памяти. Тот метод, который мы будем использовать (getDocumentElement), просто возвращает ссылку на корневой элемент данного документа:

 Element rootE = catDoc.getDocumentElement();

Например, чтобы получить список узлов (объект NodeLi st) всех элементов product в документе catDoc, следует использовать следующие методы:

 Element rootE = catDoc.getDocumentElement();
 NodeList nl = rootE.getElementsByTagName("product");

Индексация товаров

Пользуясь приведенным выше кратким обзором интерфейсов 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 следующие.

  1. Берется объектная модель документа (каталога), представленная в виде списка узлов (NodeLi st), каждый из которых соответствует некоторому товару.

  2. К списку узлов применяются правила отбора. Возможные правила отбора включают идентификатор товара (id), ключевое слово или название серии товаров (productjtine).

  3. При необходимости массив элементов сортируется.

  4. Для каждого элемента (товара) генерируется и добавляется к создаваемой странице код HTML, задающий формат представления данных об этом товаре.

Организация поиска по ключевым словам

Поскольку мы уже проделали некоторую работу по кодированию ключевых слов для каждого товара в каталоге, мы, конечно, хотели бы, чтобы пользователю было удобно работать с этими ключевыми словами. Напомним, что, как сказано в предыдущем разделе, методы 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>

Рис. З.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-дизайнеру страницы выбирать содержимое любой части документа XML (каталога товаров), не меняя при этом классы Java.

Гибкость стилей

Самым очевидным инструментом для достижения гибкости в применении различных стилей к документу являются каскадные таблицы стилей. С помощью 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>

Гибкость содержимого

Чтобы добиться гибкости внутреннего содержимого страницы, мы собираемся использовать форматирующий класс с именем 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)

Рис. 3.2. Отображение каталога товаров

В следующей главе мы расскажем, как можно расширить функциональность классов CatalogBean и ProductFormatter для создания представлений, необходимых при работе с корзиной покупателя.


  • Глава 4. Заполнение корзины покупателя
    • Проблема корзины покупателя
      • Использование объектов класса HttpSession
      • API для класса HttpSession
        • Интерфейс HttpSessionBindingListener
    • Корзина покупателя на языке Java
      • Классы Cartltem и ShoppingCart
    • Класс CatalogServ
      • Инициализация сервлета
      • Методы doGet и doPost
      • Метод doPageEnd
      • Метод doPageMid
      • Отображение полной информации о товаре
      • Использование класса cartListener
    • Класс CatalogBean
      • Использование объектов Cartltem и ShoppingCart
    • Класс ProductFormatter
      • Методы, вызываемые методом doFullItem
      • Метод doListOutput
      • Метод addText

Глава 4.

Заполнение корзины покупателя

Вы наверняка хотели бы, чтобы посетители вашего web-сайта могли легко находить интересующие их товары, добавлять их в корзину и производить оплату, в соответствии со своим интуитивным представлением об этих действиях как о неразрывно связанных этапах покупки товара. К сожалению, основные используемые в настоящее время HTTP- и web-протоколы не позволяют обеспечить эту неразрывность. В настоящей главе исследуется закулисная работа, необходимая для того, чтобы пользователи могли без затруднений приобретать товары в вашем электронном магазине.

Проблема корзины покупателя

Для посетителей виртуального магазина интуитивно понятна аналогия «корзины покупателя» (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. Этот класс позволяет процессору сервлетов детально управлять мониторингом сеанса с минимальным участием программиста.

Использование объектов класса 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.

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). Программисту следует очень внимательно относиться к выбору объектов, которые он собирается хранить как объекты сеанса. Помните, что вы не можете предсказать, когда пользователь вернется к данному сеансу. Поэтому не рекомендуется хранение таких объектов, как, например, объекты соединения с базами данных, которые требуют значительных системных ресурсов.

Интерфейс HttpSessionBindingListener

Как вспомогательное средство для управления системными ресурсами, которые могут быть задействованы в сеансах, и как средство отладки в API сервлетов предусмотрены интерфейс HttpSessionBindingListener и класс HttpSessionBinding- Event. В этом интерфейсе определены два метода.

  • void valueBoundCHttpSessionBindingEvent event). Когда объект, реализующий интерфейс HttpSessionBindingListener, присоединяется к сеансу HttpSession, вызывается данный метод. Параметр этого метода event передает двоякую информацию — имя, которое было использовано для присоединения объекта к сеансу, и идентификатор сеанса (объект типа String).

  • void valueUnbound(HttpSessionBindingEvent event). Этот метод вызывается, когда объект должен быть удален из сеанса. Обычно это происходит, когда в результате простоя выполняется метод invalidate класса HttpSession и сеанс становится недействительным.

В сервлете catalogServ мы демонстрируем использование этого интерфейса для выполнения простых операций по отладке.

Корзина покупателя на языке Java

В этой главе мы создадим сервлет, функции которого ограничены отображением каталога и оформлением заказа на товары. В реальном коммерческом сайте эти функции составили бы лишь небольшую часть всех возможностей, предложенных пользователю. В нашем примере новый объект класса HttpSession создается, когда сервлет CatalogServ в первый раз получает запрос от данного пользователя. В реальном виртуальном магазине сеанс может быть создан в другой части сайта.

Возможной «точкой входа» посетителя может быть просмотр всего каталога или списка товаров какой-либо одной серии или просмотр результатов поиска по ключевому слову. В любом случае, мы не создаем объект ShoppingCart до тех пор, пока пользователь не проявит достаточного интереса к товару, чтобы просмотреть его полное описание. В этот момент мы создаем объект Cartltem для просматриваемого товара непосредственно из его описания в документе XML и добавляем объект Cartltem в ShoppingCart, то есть в корзину.

Пока пользователь просматривает информацию о различных товарах, мы создаем объекты Cartltem для каждого выбранного товара и удаляем из корзины те товары, которые он раздумал покупать. На любой странице пользователь имеет возможность вернуться к полному каталогу, найти товар по ключевому слову, просмотреть содержимое корзины или перейти к оплате товара. Теперь рассмотрим детали реализации описанной функциональности.

Классы 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();

 }

 }

Класс CatalogServ

Класс, который мы написали для отображения элементов каталога и манипулирования корзиной покупателя (классом ShoppingCart), называется CatalogServ. Здесь мы используем усовершенствованные версии классов для представления каталога сети, описанных в главе 3, и добавляем функции для организации корзины покупателя и мониторинга сеанса. Возможности сервлета CatalogServ перечислены ниже.

  • Отображение компактных списков товаров каталога с использованием критерия отбора, в частности:
    • полный каталог;
    • товары одной серии;
    • товары, связанные с определенным ключевым словом (результат поиска по ключевому слову).
  • Отображение детальной информации о товаре, дополненное следующими функциями:
    • добавление данного товара в корзину;
    • изменение количества заказанных экземпляров.
  • Отображение списка выбранных на текущий момент товаров.

Для простоты будем считать, что заказ товара (добавление его в корзину и изменение количества заказанных экземпляров) может происходить только на странице с подробной информацией о данном товаре.

Кроме того, наш сервлет не будет выполнять никаких других функций, свойственных обычному коммерческому сайту. Как показано на рис. 4.1, наш сервлет просто размещает текст «Your site navigation could go here» [Здесь может быть расположен интерфейс для навигации по вашему сайту. — Примеч. перев. ], в то время как в этом месте обычного коммерческого сайта, как правило, находятся логотипы и навигационный интерфейс.

Рис. 4.1. Отображение полного каталога с помощью класса CatalogServ

Перед тем как углубляться в изучение исходного кода CatalogServ, рассмотрим некоторые другие представления, которые он генерирует. На рис. 4.2 показана страница с раскрывающимся списком ключевых слов. Это тот же самый список, который представлен на рис. 3.1 в главе 3, но сервлет CatalogServ добавляет в нижнюю часть страницы дополнительные ссылки:

  • Full Catalog (Весь каталог);

  • Books (Книги);

  • CDs (Компакт-диски):

  • Widgets (Приборы и устройства);

  • Search (Поиск).

Рис. 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)

Инициализация сервлета

Как видно из листинга 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».

Методы 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 очень прост, но в реальном коммерческом сайте этот метод можно использовать для отображения средств навигации по сайту и выполнения других функций.

Метод 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> &nbsp; " );

 out.print("<a href=\"" + checkoutalias +

 "?action=initial\" >Checkout Now</a> &nbsp; \r\n");

 }

 out.println( a1 + "showcatalog&select=all\" >
Full Catalog</a> &nbsp; ");

 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> &nbsp; ");

 }

 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>");

 }

Рис. 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>");

 }

Отображение полной информации о товаре

Метод doShowProduct класса Catal ogServ отвечает за отображение полной информации об одном конкретном товаре. Существует несколько вариантов формата этого отображения в зависимости от наличия в X ML-каталoгe ссылки на изображение этого товара и от того, находится ли уже данный товар в корзине покупателя.

На рис. 4.4 приведено типичное представление, сгенерированное для товара, которого еще нет в корзине покупателя и для которого имеется изображение. Кнопка, расположенная в правом нижнем углу, предназначена для добавления в корзину этого товара. При щелчке на этой кнопке генерируется запрос к сервле- ту, параметр action принимает значение setcart, а параметр itemct — значение 1, в результате в корзине оказывается один экземпляр данного товара.

Рис. 4.4. Отображение полной информации о товаре вместе с его изображением

Рис. 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");

 }

Использование класса 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 ");

 }

 }

 }

Класс 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 ; }

Использование объектов 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");

 }

 }

Рис. 4.6. Отображение содержимого корзины покупателя

Сложное форматирование при представлении полной информации о товаре (как показано на рис. 4 4 и 4 5) контролируется методом doFuTIItem, код которого приведен в листинге 4 15 Исходно мы строим таблицу с четырьмя или тремя ячейками в зависимости от наличия или отсутствия изображения товара В этой таблице содержатся сведения о товаре, его изображение (если оно есть), название и цена, а также количество заказанных экземпляров (если покупатель решил приобрести этот товар) Создается объект ProductFormatter, задающий стиль отображения всей этой информации о товаре, то есть стиль всех фрагментов текста, которые содержатся в документе XML, описывающем данный товар

Обычно объект Cartltem, соответствующий идентификатору товара, в этот момент уже добавлен к корзине Но если это не так, то создается новый объект Cartltem

Наличие или отсутствие ссылки на изображение товара в XML-каталоге влияет на форматирование верхней строки таблицы Если изображение отсутствует, то название товара вместе с его описанием занимает всю верхнюю строку

Листинг 4.15. Метод doFullItem (CatalogBean.java)

// we are in a <table>.. </table>

 public void doFullItem( String id, PrintWriter out,

 ShoppingCart cart, String alias ){

 pf = new ProductFormatter( "full" );

 pf.setResourcePath( resourcePath );

 Element pE = cat.getProductElByID( id );

 // out.print( cart.toString() ); // debugging

 if( pE == null ){

 out.print("Bad Product ID " + id ); return ;

 }

 CartItem ci = cart.getProdById( id );

 if( ci == null ){

 ci = new CartItem( pE );

 System.out.println("Create CartItem " + ci.toString() );

 }
 String imgS = pf.doImageTag( pE );

 if( imgS != null ){

 out.print("<tr><td align=\"center\">");

 out.print(pf.doImageTag( pE ));

 out.print("</td><td align =\"left\" >");

 }

 else{ // no image, spread description

 out.print("<tr><td align=\"center\" colspan=\"2\" >");

 }

 out.print(pf.doProdName( pE ));

 out.print(pf.doAuthorArtist( pE ));

 out.print(pf.doDescription( pE ));

 out.print("</td></tr>\r\n");

 out.print("<tr><td align=\"center\" >");

 out.print(pf.doPrice( pE ));

 // lower right cell contains form

 out.print("</td><td align=\"center\" >
<form method=\"POST\" action=\"");

 out.print( alias ); out.print( "\" >\r\n" );

 out.print("<input type=\"HIDDEN\" name=\"id\" value=\"");

 out.print( id ); out.print("\" >");

 out.print("<input type=\"HIDDEN\" name=\"action\""

 + " 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() + "\" > &nbsp; ");

 out.print("<input type=\"SUBMIT\" value=\"");

 out.print("Change\" >");

 }

 out.print("</form></td></tr>\r\n");

 }

Класс 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 );

 }

 }

Методы, вызываемые методом 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();

 }

Метод 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

Метод 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();

 }

 }


  • Глава 5. Оплата и подтверждение заказа
    • Процесс оплаты
    • Безопасность
    • Доверие клиента
    • Сбор информации о заказе
      • Класс Customerlnfo
      • Класс Creditlnfo
      • Класс Fullfilment
      • Класс Authorization
      • Класс Order
      • Класс TestPaymentAuthorizer
      • Класс ShippingCalculator
      • Класс Emailer
      • Страница Customerlnfo
      • JSP-страница Shippinglnfo
      • JSP-страница Creditlnfo
      • JSP-страница Confirmlnfo
      • Сервлет SubmitOrder
      • JSP-страница Approved
      • JSP-страница Declined
    • Обновление информации о доставке
      • JSP-страница OrderDateSelector
      • JSP-страница SelectOrder
      • JSP-страница ShowOrder
      • JSP-страница UpdateFullfilment

Глава 5.

Оплата и подтверждение заказа

В предыдущей главе мы показали, как в виртуальном магазине реализуется корзина покупателя. Теперь нам необходимо предоставить покупателю возможность фактически приобрести выбранные товары. Эта как раз та область, где посетители электронных магазинов проявляют наибольшее беспокойство, так что наша задача — гарантировать, чтобы этот процесс проходил гладко. Многие продажи в Интернете тормозятся на этом этапе взаимодействия с пользователем, в основном из-за нежелания пользователя передавать через Интернет информацию, связанную с его кредитной картой.

Процесс оплаты

Для коммерческих web-сайтов, занимающихся розничной продажей, прием платежей через кредитные карты является общепринятой практикой. Вообще говоря, оплата по кредитной карте товаров, приобретенных как непосредственно в магазине, так и через систему «товары почтой», появилась на много лет раньше, .чем WWW. На розничном уровне электронной торговли сложилась определенная последовательность действий, необходимая для оплаты через кредитные карты, и каждый электронный магазин работает с кредитными картами на основе этой последовательности. Прежде чем исследовать подробности ее реализации, рассмотрим, из каких этапов она складывается.

В процессе оплаты товара кредитной картой участвует несколько действующих лиц: сам клиент, магазин, банк клиента, поставщик услуг по обработке и поставщик коммерческих услуг. Роли клиента и магазина в процессе оплаты товара более или менее ясны. Банк клиента — это финансовое учреждение, которое выдало ему кредитную карту. В следующих двух абзацах описываются роли двух остальных действующих лиц.

Поставщик коммерческих услуг магазина часто является банком, но может быть и другим финансовым институтом; например, в этом качестве может выступать сама компания, выпускающая кредитные карты. Чтобы работать с кредитными картами, магазин должен заключить с банком соглашение. Требования и условия у каждого банка свои, но, как правило, взимаются по крайней мере три вида платежей. Во-первых, имеется так называемая учетная ставка (discount rate), размер которой обычно колеблется от 2,5 до 5 %. Это процент, отчисляемый от каждого платежа, который проходит через коммерческого поставщика. Во-вторых, при прохождении каждого платежа отчисляется некоторая небольшая фиксированная сумма, обычно в пределах от 30 до 50 центов (transaction fee). Наконец, коммерческий банк обычно взимает за свои услуги некоторую ежемесячную оплату. Магазин и банк должны договориться обо всех этих платежах, прежде чем магазин начнет принимать к оплате кредитные карты.

Наконец, пятым действующим лицом в этом процессе является поставщик услуг по обработке. Другое название этого поставщика — расчетная палата (clearinghouse). В его обязанности входит прием запросов от магазина, проверка достоверности полученных от клиентов сведений, проверка наличия на счетах покупателей достаточных средств и, наконец, перевод денег между банками.

Фактически процесс оплаты кредитной картой состоит из двух этапов. Сначала магазин посылает поставщику услуг по обработке запрос о проверке сведений, поступивших от клиента. В этом запросе указываются сумма покупки, номер кредитной карты и адрес клиента. Поставщик проверяет существование указанного счета в банке, наличие на счету клиента достаточной суммы и, возможно, адрес владельца кредитной карты. Если все в порядке, то поставщик высылает магазину код подтверждения. На этом этапе не происходит никакой передачи денег, но на счету клиента блокируется сумма, равная сумме заказа, сделанного им в электронном магазине.

Следующий этап процесса обработки (этап, когда деньги фактически снимаются со счета клиента) начинается после того, как магазин закончил всю деятельность по выполнению заказа. Поставщику услуг по обработке посылается второе сообщение. В реальном магазине (в отличие от виртуального) этот этап начинается после того, как покупатель вышел из магазина со своей покупкой. Обычно в таком магазине все чеки после завершения рабочего дня (или смены) собираются и отсылаются вместе. Но чек, в принципе, может быть послан и отдельно. В электронном магазине снятие денег со счета клиента происходит не ранее чем ему фактически отправлен товар. Это предотвращает вероятность слишком раннего снятия денег со счета клиента, например в случае, если товар на складе временно отсутствует.

Помимо подтверждения правильности сведений о кредитной карте и фактического снятия денег со счета клиента поставщик услуг по обработке осуществляет и другие функции, например расторжение сделки. Например, если после завершения первого этапа оказывается, что по какой-либо причине заказ не может быть выполнен (например, товар отсутствует на складе или клиент раздумал его покупать), осуществляется расторжение сделки, то есть снимается блокировка с соответствующей суммы денег на счету клиента.

Еще один вид услуг, выполняемых этим поставщиком, — возвращение денег клиенту. Если клиент по какой-либо причине возвращает поставщику товар, оплаченный по кредитной карте, то деньги за возвращенный товар снова заносятся на счет клиента.

Для небольших web-магазинов, возможно, не имеет смысла пытаться полностью автоматизировать описанный процесс. Если количество заказов невелико, то может оказаться более экономичным выполнять все описанные действия «вручную». При этом проверка кредитных карт клиентов, вероятно, займет несколько более длительное время, но в целом такое решение может оказаться предпочтительным для небольших электронных магазинов, которые только начинают свою деятельность.

Существует множество компаний, предлагающих услуги по организации всего описанного процесса электронным магазинам, которые хотели бы автоматизировать обработку электронных платежей. Эти компании являются либо поставщиками услуг по обработке, либо посредниками между такими поставщиками и электронными магазинами. Возможности и условия этих компаний сильно различаются, так что перед тем, как сделать выбор, вам предстоит изучить множество предлагаемых вариантов. Можно порекомендовать такие компании, как CyberSource (www.cybersource.com), Verifone (www.venfone.com), Authonze.net (www.authonze.net) и Clear-Commerce (www.clearcommerce.com). Большая часть этих компаний предлагают не только услуги по обработке, но и установку соответствующего программного обеспечения на web-сайт поставщика коммерческих услуг, что упрощает взаимодействие с ним.

Безопасность

Безопасность — очень важный фактор для любого коммерческого web-сайта. Чтобы гарантировать безопасность, нужно придерживаться определенных принципов. Следует определить все риски и разработать план снижения этих рисков. Постоянный учет разработанных принципов позволяет обеспечивать безопасность даже в быстроменяющихся условиях. Следует обратить внимание, в частности, на следующие факторы:

  • конфиденциальность информации (information confidentially) — защита передаваемой или хранящейся на сайте информации;

  • аутентификация (authentication) — проверка регистрационной информации пользователя;

  • авторизация (authorization) — ограничение прав клиента при взаимодействии с системой;

  • целостность данных (data integrity) — гарантия того, что принятые по сети данные не изменены при пересылке случайно или преднамеренно;

  • отсутствие сбоев (non-repudiation) — обеспечение защищенности источника данных и целостности данных,

  • доступность (availability) — гарантия того, что система доступна всегда, когда это требуется.

Подробное изучение всех этих факторов выходит за рамки нашей книги, но существуют некоторые специфические вопросы, связанные с обработкой кредитных карт, которые мы рассмотрим в этой главе. При работе с персональной информацией клиента и особенно с номером его кредитной карты безопасность является предельно важным фактором.

Первой линией защиты является шифрование важной информации, которая пересылается между браузером пользователя и сервером электронного магазина. Обычно для этого используется протокол HTTPS (Secure Hypertext Transfer Protocol — безопасный протокол передачи гипертекста), в котором все данные, пересылаемые между браузером и сервером, шифруются с помощью технологии SSL (Secure Sockets Layer — слой защищенных сокетов). Если данные не зашифрованы, злоумышленники могут с легкостью прочесть информацию, которая пересылается между браузером и сервером.

Далее, все источники данных, в которых хранится важная информация, также должны быть зашифрованы. Если номера кредитных карт хранятся в базе данных, то эта база данных должна быть зашифрована во избежание несанкционированного доступа. Если данные хранятся в файле XML, то номера кредитных карт или сам файл должен быть зашифрован. Кроме того, данные должны храниться на компьютере, к которому невозможен доступ через Интернет.

На всех компьютерах следует использовать брандмауэры. Правильно сконфигурированные средства защиты могут значительно снизить риск несанкционированного доступа к информации. Отсутствие брандмауэра позволяет злоумышленникам осуществить все виды нападений на ваш сайт, так что эти средства защиты необходимы для любого коммерческого web-сайта.

Тщательно протестируйте вашу систему. Ошибки в системе часто оказываются теми лазейками, из-за которых не срабатывают средства защиты. Этого не произойдет, если система работает без сбоев. Поэтому тестирование системы имеет очень важное значение. Установка средств защиты зависит от программного обеспечения, используемого при реализации системы. Для установки протокола HTTPS необходимо, чтобы web-сервер был правильно сконфигурирован. Для шифрования базы данных также необходимо, чтобы она была правильно сконфигурирована. Мы не будем углубляться в детали установки средств обеспечения безопасности, так как они весьма разнообразны, но важность этого фактора трудно переоценить.

Доверие клиента

Если клиент не испытывает доверия к web-сайту, у него не возникнет желания передавать информацию, связанную с его кредитной картой. Между чтением информации на страницах электронного магазина и решением фактически приобрести товар — огромная разница. Можно посоветовать несколько способов повышения доверия клиента к web-сайту.

Качество работы сайта влияет на впечатления клиента от вашего электронного магазина. Если клиент сталкивается с неработающими ссылками или другими очевидными системными ошибками, вряд ли он будет относиться к такому сайту с доверием. Когда речь идет только о поиске информации, то подобные ошибки могут восприниматься достаточно легко, но когда дело доходит до сообщения важной персональной информации, надежная работа сайта становится критически важной.

Доступное изложение принципов работы с персональной информацией также очень важно для формирования доверительного отношения клиента. Если клиент не имеет представления о том, что произойдет с той информацией, которую он должен переслать на ваш сайт, он вряд ли захочет это сделать. Поэтому на сайте следует отвести специальную страницу, где четко объясняется, зачем нужна эта информация и как она будет обрабатываться; кроме того, необходимо, чтобы имелась четко обозначенная ссылка на эту страницу.

Неплохой идеей является предоставление клиенту возможности ознакомиться с теми мерами, которые предприняты для обеспечения безопасности при пересылке информации. Если вы объясните, что вся персональная информация зашифровывается перед отправлением и хранится в безопасном месте, клиент будет больше доверять вашему сайту.

Также на отношение клиента влияет общее впечатление от вашего сайта. Чем лучше выглядит ваш сайт, тем больше доверия он вызовет у посетителя. Оформление сайта, удобство навигации и другие факторы, влияющие на впечатление клиента, также будут влиять и на степень его доверия к сайту. Грамматические ошибки в тексте, неряшливое оформление и неудобство навигации неизбежно оттолкнут потенциальных покупателей.

Сбор информации о заказе

Когда покупатель отобрал товары, которые он собирается приобрести, следующим этапом является подсчет стоимости покупки и выполнение заказа. Необходимо получить определенную информацию о покупателе, в том числе его имя, адрес и номер телефона или адрес электронной почты (или установить какой- нибудь другой способ связи с ним). Также покупатель может выбрать один из способов доставки товара. Наконец, самое главное — покупатель должен сообщить номер своей кредитной карты, которой он будет оплачивать покупку.

Мы начнем с изучения классов, которые будут использоваться в JSP-страни- це как компоненты JavaBean. Customerlnfo, Creditlnfo, Fulfillment, Authorization и Order — все это классы, которые содержат информацию, собранную в процессе оформления заказа.

Следующие три класса, которые мы изучим, — TestPaymentAuthorizer, Ship- pi ngCal cul ator и Eraai 1 er — также используются в процессе оформления заказа. Затем мы рассмотрим HTML-страницу, сервлет и несколько JSP-страниц, с помощью которых осуществляется взаимодействие с пользователем при сборе необходимой информации.

Класс Customerlnfo

Класс Customerlnfo предназначен для сбора некоторой стандартной информации о покупателе. Имя покупателя и его адрес необходимы для того, чтобы ему можно было доставить заказ, а адрес электронной почты и номер телефона — для того, чтобы можно было связаться с клиентом и сообщить ему о каких-то изменениях или задать какие-либо вопросы. Объект Creditlnfo мы обсудим несколько позже. Класс Customerlnfo состоит только из методов getXxx и setXxx, как показано в листинге 5.1.

Листинг 5.1. Класс Customerlnfo (Customerlnfo.java)

package com.XmlEcomBook.Chap05;

 public class CustomerInfo {

 private String lastName;

 private String firstName;

 private String address1;

 private String address2;

 private String city;

 private String state;

 private String zip;

 private String email;

 private String phoneNumber;

 private CreditInfo creditInfo;

 public CustomerInfo() {

 }

 public String getLastName() {

 return lastName;

 }

 public void setLastName( String newLastName ) {

 lastName = newLastName;

 }

 public String getFirstName() {

 return firstName;

 }

 public void setFirstName( String newFirstName ) {

 firstName = newFirstName;

 }

 public void setAddress1( String newAddress1 ) {

 address1 = newAddress1;

 }

 public String getAddress1() {

 return address1;

 }

 public void setAddress2( String newAddress2 ) {

 address2 = newAddress2;

 }

 public String getAddress2() {

 return address2;

 }

 public void setCity( String newCity ) {

 city = newCity;

 }

 public String getCity() {

 return city;

 }

 public void setState( String newState ) {

 state = newState;

 }

 public String getState() {

 return state;

 }

 public void setZip( String newZip ) {

 zip = newZip;

 }

 public String getZip() {

 return zip;

 }

 public void setEmail( String newEmail ) {

 email = newEmail;

 }

 public String getEmail() {

 return email;

 }

 public void setPhoneNumber( String newPhoneNumber ) {

 phoneNumber = newPhoneNumber;

 }

 public String getPhoneNumber() {

 return phoneNumber;

 }

 public CreditInfo getCreditInfo() {

 return creditInfo;

 }

 public void setCreditInfo( CreditInfo newCreditInfo ) {

 creditInfo = newCreditInfo;

 }

 }

Класс Creditlnfo

В классе Creditlnfo содержится информация о кредитной карте клиента. Сюда входит тип кредитной карты, например Visa или MasterCard, ее номер и дата истечения срока действия. Все эти сведения необходимо сообщить при обращении к поставщику услуг по обработке.

Листинг 5.2. Класс Crediditlnfo (Creditlnfo.java)

package com.XmlEcomBook.Chap05;

 public class CreditInfo extends Object {

 private String creditCardType;

 private String creditCardNumber;

 private String expirationDate;

 public CreditInfo() {

 }

 public String getCreditCardType() {

 return creditCardType;

 }

 public void setCreditCardType( String newCreditCardType ) {

 creditCardType = newCreditCardType;

 }

 public String getCreditCardNumber() {

 return creditCardNumber;

 }

 public void setCreditCardNumber( String newCreditCardNumber)

{

 creditCardNumber = newCreditCardNumber;

 }

 public String getExpirationDate() {

 return expirationDate;

 }

 public void setExpirationDate( String newExpirationDate ) {

 expirationDate = newExpirationDate;

 }

 }

Класс Fullfilment

В процесс выполнения заказа входит все, что должно быть сделано с товаром после оформления заказа. Обычно для этого нужно найти товар на складе, запаковать его и отправить клиенту. Мы рассмотрим несколько упрощенный вариант выполнения заказа; в нашем примере будет учитываться только сам процесс доставки.

В классе Fullfilment имеются поля для хранения информации о фирме, которая осуществляет доставку, и о типе услуг. Таким образом, мы можем доставлять товары с помощью различных почтовых фирм — например, Federal Express, UPS или почтовой службы US, а также выбирать различные типы услуг — срочную доставку на следующий день, через один или два дня или доставку в обычном режиме. Также нужно знать, сколько клиент должен заплатить за доставку. Эта сумма добавляется к стоимости товаров, что в результате дает полную стоимость заказа. Когда заказ наконец отправлен, ему должен быть присвоен некоторый идентификационный номер, а также следует записать дату отправки заказа — эти сведения потребуются, если от клиента поступят какие-либо вопросы, связанные с доставкой.

В классе Fullfilmerit (листинг 5.3) имеются методы setXxx и getXxx для каждого из полей. Имеется также один специальный метод, setShipperAndClass. Он позволяет задавать в одной строке и название почтовой фирмы, и тип услуг (для данного заказа) [Эту совокупность далее мы будем называть способом доставки. — Примеч. перев.]. Как вы увидите в разделе «JSP-страница Shippinglnfo», это упрощает обработку введенных пользователем данных (о предпочтительном способе доставки). В этом методе используется объект StringTokenizer, который разделяет строку на две части, затем первую часть заносит в поле shipper (почтовая фирма), а вторую — в поле class (тип услуг). Это сделано для того, чтобы упростить ввод указанных данных. Ниже мы рассмотрим страницу Shippinglnfo.jsp и увидим, как используется данный метод.

Листинг 5.3. Класс Fulfilment (Fullfilment.java)

package com.XmlEcomBook.Chap05;

 import java.util.Date;

 import java.util.StringTokenizer;

 public class Fulfillment {

 String shipper;

//UPS, Fedex, USPS, etc.

 String shippingClass;

//Overnight, 2 Day, regular, frieght, etc.

 double costToCustomer;

//How much the customer is charged for shipping

 String trackingNumber = "NO_TRACKING_NUMBER";

 String dateSent = "NOT_SENT_YET";

 public Fulfillment() {

 }

 public void setShipper( String newShipper ) {

 shipper = newShipper;

 }

 public String getShipper() {

 return shipper;

 }

 public void setShipperAndClass( String shipperAndClass ) {

 StringTokenizer st = new StringTokenizer( shipperAndClass );

 if( st.hasMoreTokens() ) {

 shipper = st.nextToken();

 if( st.hasMoreTokens() ) {

 shippingClass = st.nextToken();

 }

 }

 }

 public void setShippingClass( String newClass ) {

 shippingClass = newClass;

 }

 public String getShippingClass() {

 return shippingClass;

 }

 public void setTrackingNumber( String newNumber ) {

 trackingNumber = newNumber;

 }

 public String getTrackingNumber() {

 return trackingNumber;

 }

 public void setDateSent( String newDate )
 {
 dateSent = newDate;

 }

 public String getDateSent() {

 return dateSent;

 }

 public double getCostToCustomer() {

 return costToCustomer;

 }

 public void setCostToCustomer( double newCost ) {

 costToCustomer = newCost;

 }

 }

Класс Authorization

Класс Authorization (листинг 5.4) используется для хранения данных, которые возвратил поставщик услуг по обработке. В нем имеются три поля: во-первых, булева переменная, указывающая, было ли получено подтверждение указанных клиентом сведений о кредитной карте. Затем имеется поле, в котором указываются причины отказа (если подтверждение не получено), и, наконец, поле, содержащее код подтверждения (если оно получено). Для всех этих полей имеются методы getXxx и setXxx, с помощью которых можно извлекать и модифицировать значения полей.

Листинг 5.4. Класс Authorization (Authorization.java)

package com.XmlEcomBook.Chap05;

 public class Authorization {

 private boolean approved = false;

 private String reason = "Unknown";

//reason for a denial

 private String authorizationCode;

// auth code from patment service

 public boolean isApproved() {

 return approved;

 }

 public void setApproved( boolean newApproved ) {

 approved = newApproved;

 }

 public String getReason() {

 return reason;

 }

 public void setReason( String newReason ) {

 reason = newReason;

 }

 public String getAuthorizationCode() {

 return authorizationCode;

 }

 public void setAuthorizationCode( String newAuthCode ) {

 authorizationCode = newAuthCode;

 }

 }

Класс Order

Класс Order служит в основном в качестве контейнера для всевозможных сведений, которые мы собираем при оформлении заказа. Сюда входит информация о клиенте, о доставке, номер кредитной карты клиента, перечень заказанных товаров и данные, присланные поставщиком услуг по обработке (в частности, код подтверждения). Нам также понадобится уникальный идентификатор этого заказа, чтобы на него можно было впоследствии ссылаться. Кроме того, нужно указать дату заказа. Все эти сведения вносятся в соответствующие поля класса Order. Конструктор класса задает идентификатор и дату заказа. Метод, используемый для генерации уникального идентификатора, мы исследуем несколько позже. В листинге 5.5 показаны методы getXxx и setXxx для полей класса. Для полей id и date методы setXxx отсутствуют, так как их значения задаются конструктором класса и затем не меняются.

Листинг 5.5. Поля, конструктор и методы setXxx и getXxx для класса Order (Order.java)

package com.XmlEcomBook.Chap05;

 import com.XmlEcomBook.catalog.CartItem;

 import java.util.*;

 import java.io.*;

 import com.XmlEcomBook.util.Debug;

 public class Order {

 private int id; //unique id for this order

 private Date date; //date of order

 private Vector items = new Vector();

 private CustomerInfo customerInfo;

 private Authorization authorization; //payment authorization

 private Fulfillment fulfillment;

 public Order() {

 id = getUniqueId();

 date = new Date();

 }

 public int getId() {

 return id;

 }

 public Date getDate() {

 return date;

 }

 public void setItems( Vector newItems ) {

 if( newItems != null )

 items = newItems;

 }

 public Vector getItems() {

 return items;

 }

 public void setCustomerInfo( CustomerInfo newCustomer ) {

 if( newCustomer != null )

 customerInfo = newCustomer;

 }

 public CustomerInfo getCustomerInfo() {

 return customerInfo;

 }

 public void setAuthorization( Authorization newAuth ) {

 authorization = newAuth;

 }

 public Authorization getAuthorization() {

 return authorization;

 }

 public void setFulfillment( Fulfillment newFulfillment ) {

 fulfillment = newFulfillment;

 }

 public Fulfillment getFulfillment() {

 return fulfillment;

 }

В листинге 5.6 показаны некоторые методы, оперирующие данными, которые содержатся в классе Order. Первый метод, getTotalltemPrice, реализует цикл по всем заказанным товарам и для каждого товара умножает его цену на количество заказанных экземпляров. Затем подсчитывается и возвращается общая сумма. Метод getOrderTotal добавляет к общей сумме стоимость доставки, что и составляет общую стоимость заказа. Наконец, имеется метод getTotalltemWeight, который аналогичен методу getTotalltemPrice, но только в нем подсчитывается не стоимость, а общий вес заказа. Метод getPrice-осуществляет вспомогательные функции — удаляет дополнительные символы (знак $ и запятые) из строки с указанием цены, полученной из объекта Cartltem, а затем преобразует полученное число к типу doubl e.

Листинг 5.6. Методы для подсчета характеристик заказа как единого целого (Order.java)

public double getTotalItemPrice() {

 double total = 0;

 Enumeration enum = items.elements();

 while( enum.hasMoreElements() ) {

 CartItem item = (CartItem)enum.nextElement();

 total += getPrice( item ) * item.getNumberOrdered();

 }

 return total;

 }

 public double getTotalItemWeight() {

 double total = 0;

 Enumeration enum = items.elements();

 while( enum.hasMoreElements() ) {

 CartItem cartItem = (CartItem)enum.nextElement();

 double d = Double.parseDouble(cartItem.getShippingValue());

 total += d * cartItem.getNumberOrdered();

 }

 return total;

 }

 private double getPrice( CartItem item ) {

 String s = item.getPrice();

 //remove dollar sign

 s = s.replace( '$', ' ' );

 //remove commas

 int i;

 while( (i = s.indexOf( ',' )) > 0 ) {

 s = s.substring( 0, i ) + s.substring( i + 1 );

 }

 return Double.parseDouble( s );

 }

Имеется также метод для записи заказа в формате XML. Информация о заказе может пригодиться в будущем. Поэтому мы создали определение DTD, которое является непосредственным отображением класса Order и всех классов, которые в нем используются. В листинге 5.7 представлен файл order.dtd, в котором каждому из полей класса Order (item, customer-Info, authorization и fullfilment) соответствует элемент, дочерний по отношению к элементу order. Поля id и date класса Order представлены атрибутами элемента order. Таким же образом сформированы и остальные элементы DTD: каждому полю класса Order соответствует элемент или атрибут в DTD [Order — заказ, item — товар, first name — имя, last name — фамилия, address — адрес, city — город, state — штат или страна, zip — почтовый индекс, phone — номер телефона, card number — номер кредитной карты, card type — тип кредитной карты, expiration date — дата окончания срока действия, reason — причина (отклонения кредитной карты), authorization code — код подтверждения, cost — стоимость, tracking number — номер для отслеживания заказа, date sent — дата отправки. — Примеч. перев. ].

Листинг 5.7. DTD для описания структуры заказа (order.dtd)

<!ELEMENT order (item*, customer_info, authorization,
fulfillment )>

 <!ATTLIST order id ID #REQUIRED

 date CDATA #REQUIRED>

 <!ELEMENT item (#PCDATA)>

 <!ATTLIST item id NMTOKEN #REQUIRED

 quantity NMTOKEN #REQUIRED

 price CDATA #REQUIRED>

 <!ELEMENT customer_info (first_name, last_name,
address1, address2,

 city, state, zip, email, phone, credit_info )>

 <!ELEMENT first_name (#PCDATA)>

 <!ELEMENT last_name (#PCDATA)>

 <!ELEMENT address1 (#PCDATA)>

 <!ELEMENT address2 (#PCDATA)>

 <!ELEMENT city (#PCDATA)>

 <!ELEMENT state (#PCDATA)>

 <!ELEMENT zip (#PCDATA)>

 <!ELEMENT email (#PCDATA)>

 <!ELEMENT phone (#PCDATA)>

 <!ELEMENT credit_info (card_numer, card_type, expiration )>

 <!ELEMENT card_number (#PCDATA)>

 <!ELEMENT card_type (#PCDATA)>

 <!ELEMENT expiration_date (#PCDATA)>

 <!ELEMENT authorization (reason?, auth_code?)>

 <!ATTLIST authorization approved CDATA #IMPLIED>

 <!ELEMENT reason (#PCDATA)>

 <!ELEMENT authorization_code (#PCDATA)>

 <!ELEMENT fulfillment (backorder_date, shipper, shipping_class,

 cost, tracking_number, date_sent)>

 <!ELEMENT shipper (#PCDATA)>

 <!ELEMENT shipping_class (#PCDATA)>

 <!ELEMENT cost (#PCDATA)>

 <!ELEMENT tracking_number (#PCDATA)>

 <!ELEMENT date_sent (#PCDATA)>

В листинге 5.8 приведен метод, который создает документ XML, соответствующий этому определению DTD. Это примитивный, слишком прямолинейный метод, в котором просто перебираются все поля класса Order и для каждого поля выписываются элементы и атрибуты XML. Такой подход не всегда является оптимальным, так как изменения в классах, входящих в класс Order, приведут к необходимости изменения самого метода. Таким образом, теряются преимущества инкапсуляции, которую обеспечивает объектно-ориентированный подход к программированию. Было бы лучше рассматривать эти классы в единстве. Более удачный метод создания документа XML мы рассмотрим в главе 6.

Листинг 5.8. Метод writeXML (Order.java)

public void writeXML( Writer writer ) {

 try {

 writer.write( "<?xml version='1.0' ?>" );

 writer.write( "<!DOCTYPE order SYSTEM '.."

 + File.separator + "order.dtd'>" );

 writer.write( "<order id='" + id + "' " );

 writer.write( "date='" + date + "'>" );

 Enumeration enum = items.elements();

 while( enum.hasMoreElements() ) {

 CartItem item = (CartItem)enum.nextElement();

 writer.write( "<item id='" + item.getId() + "' " );

 writer.write( "quantity='" + item.getNumberOrdered() + "' ");

 writer.write( "price='" + item.getPrice() + "'>" );

 writer.write( item.getName() + "</item>" );

 }

 writer.write( "<customer_info>\n<first_name>" +

 customerInfo.getFirstName() + "</first_name>" +

 "\n<last_name>" + customerInfo.getLastName() + "</last_name>" +

 "\n<address1>" + customerInfo.getAddress1() + "</address1>" +

 "\n<address2>" + customerInfo.getAddress2() + "</address2>" +

 "\n<city>" + customerInfo.getCity() + "</city>" +

 "\n<state>" + customerInfo.getState() + "</state>" +

 "\n<zip>" + customerInfo.getZip() + "</zip>" +

 "\n<email>" + customerInfo.getEmail() + "</email>" +

 "\n<phone>" + customerInfo.getPhoneNumber() + "</phone>" );

 CreditInfo credit = customerInfo.getCreditInfo();

 writer.write( "\n<credit_info>\n<card_number>" +

 credit.getCreditCardNumber() + "</card_number>" +

 "\n<card_type>" + credit.getCreditCardType() + "</card_type>" +

 "\n<expiration_date>" + credit.getExpirationDate() +

 "</expiration_date>\n</credit_info>\n</customer_info>" );

 writer.write( "\n<authorization approved='" +

 authorization.isApproved() + "'>" +"\n<reason>" +

 authorization.getReason() + "</reason>\n<authorization_code>" +

 authorization.getAuthorizationCode() + "</authorization_code>" +

 "</authorization>" );

 writer.write( "\n<fulfillment>\n" +

 "\n<shipper>" + fulfillment.getShipper() + "</shipper>" +

 "\n<class>" + fulfillment.getShippingClass() + "</class>" +

 "\n<cost>" + fulfillment.getCostToCustomer() + "</cost>" +

 "\n<tracking_number>" + fulfillment.getTrackingNumber() +

 "</tracking_number>\n<date_sent>" +

 fulfillment.getDateSent() + "</date_sent>\n</fulfillment>" );

 writer.write( "\n</order>" );

 }

 catch( IOException e ) {

 }

 }

Последний метод в классе Order, названный getllniqueld, приведен в листинге 5.9. Он генерирует уникальный идентификатор заказа. В нашем случае мы используем счетчик, который увеличивается на единицу каждый раз, когда нам требуется новый идентификатор. Этот счетчик должен храниться постоянно, чтобы мы могли быть уверенны в уникальности присваиваемых идентификаторов даже после полной перезагрузки системы. Хранение счетчика в памяти — в данном случае не выход из положения, так как при каждой перезагрузке системы счетчик начинает повторять уже присвоенные ранее идентификаторы. В качестве постоянного места хранения счетчика мы используем специальный файл.

Листинг 5.9. Метод getUniqueld (Order.java)

synchronized private int getUniqueId() {

 int id;

 try {

 ObjectInputStream in = new ObjectInputStream( new

 FileInputStream( "orderID.txt" ) );

 id = in.readInt();

 }

 catch( IOException e ) {

 id = 1000;

 }

 try {

 ObjectOutputStream out = new ObjectOutputStream( new

 FileOutputStream( "orderID.txt" ) );

 out.writeInt( id + 1 );

 out.close();

 }

 catch( IOException e ) {

 }

 return id;

 }

 }

Класс TestPaymentAuthorizer

Следующие классы, которые мы будем рассматривать, используются в процессе оформления заказа. Первый из них занимается проверкой данных о кредитной

карте клиента. В этом примере мы создали очень простой класс, который возвращает код подтверждения в зависимости от последней цифры номера кредитной карты, содержащегося в классе Order. В настоящем электронном магазине вместо этого класса должен быть подставлен другой, который передает данные о кредитной карте поставщику услуг по обработке. Способ передачи этих данных в большой степени зависит от конкретного поставщика.

Класс TestPaymentAuthori zer представлен в листинге 5.10. Первый метод в этом классе, getAuthorization, в качестве параметра получает объект Order, из которого извлекается информация о кредитной карте для проверки. В нашем примере мы просто берем последнюю цифру номера и, если это 1, карта с таким номером отклоняется, то есть считается не прошедшей проверку. Этот абстрактный, произвольный критерий мы используем только для того, чтобы протестировать работу метода и продемонстрировать оба типа возвращаемых значений.

Метод capture нужен для того, чтобы завершить процесс снятия денег со счета клиента, осуществляемый поставщиком услуг по обработке. В нашем случае этот метод ничего не делает, но, опять-таки, в настоящем магазине он использовался бы для сообщения поставщику о том, что следует завершить процесс оплаты. Если бы мы хотели автоматизировать процесс возвращения денег в случае, когда клиент возвращает товар, или процесс разблокировки счета клиента, если заказанного товара не оказалось в наличии, то соответствующие методы следовало бы тоже поместить в класс TestPaymentAuthori zer.

Листинг 5.10. Класс TestPaymentAuthorizer (TestPaymentAuthorizer.java)

public class TestPaymentAuthorizer {

 static public Authorization getAuthorization( Order order ) {

 Authorization authorization = new Authorization();

 try {

 CustomerInfo custInfo = order.getCustomerInfo();

 CreditInfo creditInfo = custInfo.getCreditInfo();

 String num = creditInfo.getCreditCardNumber();

 if( num != null ) {

 if( num.endsWith( "1" ) ) {

 authorization.setApproved( false );

 authorization.setReason("Insufficent Funds");

 }

 else

 {

 authorization.setApproved( true );

 authorization.setReason( "Approved" );

 authorization.setAuthorizationCode( "Test" );

 }

 }

 }

 catch( Exception e ) {}

 return authorization;

 }

 static public void capture( String authorizationCode, double
amount ) {

 }

 }

Класс ShippingCalculator

Теперь нам нужен какой-нибудь способ подсчета стоимости доставки товара заказчику. Эта сумма зависит от нескольких факторов, главными из которых являются выбранная клиентом почтовая фирма и тип услуг. На стоимость также могут влиять вес посылки и расстояние до пункта назначения. Мы при вычислении общей стоимости будем учитывать только три фактора: почтовую фирму, тип услуг и вес посылки. Объекту ShippingCalculator в качестве параметра передается объект Order, в котором содержится вся необходимая информация о заказе: вес посылки и адрес пункта назначения.

Первый метод, getTypes, возвращает массив, в котором перечислены все типы услуг, предоставляемые каждой почтовой фирмой, сотрудничающей с магазином. Второй метод, getPrice, возвращает цену конкретного типа доставки конкретной фирмой. Входным параметром этого метода должна быть одна из строк массива, возвращенного методом getTypes.

Метод getPrice (листинг 5.11) можно расширить, чтобы учитывать при вычислении стоимости доставки большее количество факторов, чем перечисленные выше (вес посылки и способ доставки). Кроме того, было бы неплохо организовать считывание информации в этот класс из некоторого источника данных, например файла XML или базы данных, с последующим вычислением стоимости. Это позволило бы динамически менять указанные цифры в соответствии с реальными колебаниями цен, не меняя самого кода.

Листинг 5.11. Начало кода класса ShippingCalculator и его конструктора (ShippingCalculator.java)

public class ShippingCalculator {

 Order order;

 public ShippingCalculator( Order setOrder ) {

 order = setOrder;

 }

 public String[] getTypes() {

 String[] names = { "FedEx Overnight",

 "FedEx 2-Day",

 "UPS Overnight",

 "UPS 3-Day",

 "USPS 2-Day",

 "USPS Regular" };

 return names;

 }

 public String getPrice( String name ) {

 double weight = order.getTotalItemWeight();

 if( name.equals( "FedEx Overnight" ) )

 if( weight > 3.0 )

 return "$10.99";

 else

 return "$7.99";

 if( name.equals( "FedEx 2-Day" ) )

 if( weight > 3.0 )

 return "$5.99";

 else

 return "$3.49";

 if( name.equals( "UPS Overnight" ) )

 if( weight > 2.0 )

 return "$8.99";

 else

 return "$6.99";

 if( name.equals( "UPS 3-Day" ) )

 if( weight > 2.5 )

 return "$5.99";

 else

 return "$4.99";

 if( name.equals( "USPS 2-Day" ) )

 if( weight > 2.5 )

 return "$4.99";

 else

 return "$3.99";

 if( name.equals( "USPS Regular" ) )

 return "$2.99";

 return "0.00";

 }

 }

Класс Emailer

Класс Emailer используется для отправки электронных сообщений клиенту. Эти сообщения могут содержать подтверждение заказа или информацию о доставке. В этом классе имеются две статические переменные, необходимые для его конфигурирования. Первая переменная — имя сервера SMTP (Simple Mail Transfer Protocol — простой протокол электронной почты), который используется данным магазином для отправки почты. Вторая переменная — электронный адрес, который будет указан в письмах клиенту в качестве обратного, то есть в поле From (От). В нашем примере мы используем формат, в котором указывается как название фирмы XMLGifts, так и ее электронный адрес (orders® xmlgifts.com). Прежде чем использовать код, приведенный на прилагаемом к нашей книге компакт-диске, вы, разумеется, должны заменить эти данные теми, которые фактически фигурируют в вашей системе. Имя сервера SMTP, используемого для пересылки электронной почты, можно найти в параметрах конфигурации вашей почтовой программы. В листинге 5.12 показано начало кода класса Emailer.

Листинг 5.12. Начало кода класса Emailer (Emailer.java)

package com.XmlEcomBook.Chap05;

 import java.util.*;

 import java.io.*;

 import javax.mail.*;

 import javax.mail.internet.*;

 public class Emailer {

 static final String host = "SMTP-HOST-NAME";

 static final String from = "XMLGifts<orders@xmlgifts.com>";

Первый метод в этом классе используется для сообщения клиенту об отправке ему посылки с заказом. Хотя это подтверждение не является абсолютно необходимым, оно имеет большое значение, так как клиент, получив такое сообщение, будет уверен, что его заказ действительно выполняется, а также найдет ответы на возможные вопросы о доставке заказа. В этом методе после получения информации о пользователе из объекта класса Order мы вызываем служебный метод под названием <jetMessage, передавая ему в качестве параметра электронный адрес клиента. Метод getMessage создает объект Message с помощью интерфейса API JavaMail, о чем будет подробнее сказано далее в этом разделе. В объекте Message указывается тема сообщения и вносится текст, после чего письмо отправляется. Отправка письма осуществляется с помощью метода send объекта Transport из интерфейса API JavaMail. Этот метод создает простое сообщение (листинг 5.13), в котором указывается идентификатор заказа, чтобы клиент мог в дальнейшем на него ссылаться. Метод можно легко расширить, чтобы включить более детальную информацию о заказе.

Листинг 5.13. Метод sendConfiramtion (Emailer.java)

public static void sendConfirmation(Order order) {

 try {

 CustomerInfo cust = order.getCustomerInfo();

 Message msg = getMessage( cust.getEmail() );

 msg.setSubject("XMLGifts.com Order Confirmation");

 msg.setText("Your order is being processed");

 msg.setText("Your order number is:" + order.getId() );

 Transport.send(msg);

 }

 catch (MessagingException mex) {

 }

 }

Метод sendShipped (листинг 5.14) аналогичен методу sendConfirmation, отличие касается только текста посылаемого сообщения. /

Листинг 5.14. Метод sendShipped (Emailer.java)

 public static void sendShipped(String email, String orderId ) {

 try {

 Message msg = getMessage( email );

 msg.setSubject("Your XMLGifts.com Order has shipped");

 msg.setText("Order number " + orderId + " has shipped" );

 Transport.send(msg);

 }

 catch (MessagingException mex) {

 }

 }

Метод getMessage используется другими методами класса для осуществления большинства действий, необходимых при работе с интерфейсом API JavaMail. Аргументом этого метода является электронный адрес клиента, которому посылается сообщение. В первую очередь в этом методе создается новый объект Session, а его значением становится имя сервера SMTP, которое, как вы помните, берется из статической переменной класса Emailег. Затем создается новый объект Message и устанавливаются значения полей, отведенных для адресов отправителя и получателя сообщения, а также для текущей даты (на которую можно впоследствии ссылаться как на дату отправки сообщения).

Интерфейс прикладных программ (API) JavaMail — это набор классов, которые моделируют систему электронной почты и являются стандартным расширением Java. Этот интерфейс можно использовать для получения и отправления сообщений с помощью стандартных почтовых протоколов. Более подробную информацию вы найдете по адресу http://java.sun.com/products/javamail, и оттуда же вы сможете бесплатно загрузить версию 1.2 этого интерфейса API. Метод getMessage (листинг 5.15) использует интерфейс JavaMail для создания нового объекта Message, в котором указаны адреса отправителя и получателя, а также текущая дата.

Листинг 5.15. Метод getMessage (Emailer.java)

static Message getMessage( String toEmail ) throws
MessagingException {

 Properties props = new Properties();

 props.put("mail.smtp.host", host);

 Session session = Session.getDefaultInstance(props, null);

 session.setDebug(true);

 Message msg = new MimeMessage(session);

 msg.setFrom(new InternetAddress(from));

 InternetAddress[] address = {new InternetAddress(toEmail)};

 msg.setRecipients(Message.RecipientType.TO, address);

 msg.setSentDate(new Date());

 return msg;

 }

 }

Страница Customerlnfo

Теперь мы перейдем к классам, с помощью которых осуществляется получение всей информации от клиента и передача ее рассмотренным выше классам. В первую очередь — это HTML-страница Customerlnfo.html. На этой странице имеется форма, поля которой предназначены для введения информации пользователем и соответствуют полям класса Customerlnfo (листинг 5.16). Как будет видно на JSP-странице Shippinglnfo, это соответствие упрощает передачу данных из формы в класс.

Листинг 5.16. Страница Customerlnfo (Customerlnfo.html)

<html>

 <head>

 <title>Customer Info</title>

 </head>

 <body>

 <form action="ShippingInfo.jsp">

 <p>

 First Name:<input name="firstName" />

 Last Name:<input name="lastName" />

 </p>

 <p>

 Address line 1:<input name="address1" /><br />

 Address line 2:<input name="address2" /><br />

 City:<input name="city" />

 State:<input size="2" name="state" />

 Zip:<input size="10" name="zip" />

 </p>

 <p>Email Address:<input name="email" /><br /></p>

 <p>Phone Number:<input size="13" name="phoneNumber" />
<br /></p>

 <input type="submit" value="Submit Information" />

 </form>

 </body>

 </html>

JSP-страница Shippinglnfo

После импорта необходимых классов для получения нового объекта класса Custo- merlnfo, связанного с текущим сеансом, используется элемент jsp:useBean. Затем свойства этого элемента с помощью элемента jsp:setProperty устанавливаются равными значениям, введенным в форму на странице Customerlnfo.html. После этого мы получаем класс Order, связанный с текущим сеансом, и вектор Vector, в котором содержится перечень всех заказанных товаров. Потом с помощью скриптлета [Скриптлетом (scriptlet) авторы называют код JSP внутри тегов <%...%>, по-видимому, по аналогии с апплетом и сервлетом (еще одним «изобретением» автора). — Примеч. ред. ] JSP вызываются методы setCustomerlnfo и setltems, которые добавляют соответствующие объекты в объект Order, как показано в листинге 5.17.

Листинг 5.17. Начало кода Shippinglnfo JSP (Shippinglnfo.jsp)

<%@ page import="com.XmlEcomBook.Chap05.*,java.util.*" %>

 <jsp:useBean scope="session" id="custInfo"
class="CustomerInfo" />

 <jsp:setProperty name="custInfo" property="*" />

 <jsp:useBean id="order" scope="session" class="Order" />

 <jsp:useBean id="theorder" scope="session" class="Vector" />

 <% order.setCustomerInfo( custInfo );

 order.setItems( theorder );

 %>

Затем JSP-страница выдает код HTML, который позволяет пользователю выбрать один из предложенных способов доставки. В этом коде используется объект ShippingCalculator, рассмотренный нами выше в этой главе. Скриптлет, встроенный в код HTML, создает объект ShippingCalculator, а затем с помощью метода getTypes предлагает несколько способов доставки. На этом этапе создается ряд переключателей (radio buttons), по одному на каждый из указанных способов. Это делается с помощью цикла for. Для каждого способа создается элемент input типа radio. Атрибут value (значение) элемента устанавливается с помощью выражения JSP "<t=types[i]X>". Затем такое же выражение используется для отображения этого значения на странице. Другой скриптлет обеспечивает получение данных о стоимости того или иного способа доставки с помощью метода getPrice объекта ShippingCalculator, как показано в листинге 5.18.

Листинг 5.18. Код HTML, который формируется в JSP-странице Shippinglnfo (Shippinglnfo.jsp)

<html>

 <head>

 <title>Shipping Info</title>

 </head>

 <body>

 <form action="CreditInfo.jsp">

 Select a Shipper and Class:<br />

 <table>

 <% ShippingCalculator calc = new ShippingCalculator( order );

 String[] types = calc.getTypes();

 for( int i = 0; i < types.length; i++ ) {

 %>
 <tr><td><input type="radio" name="shipperAndClass"
value="<%=types[i] %>" /><%=types[i] %></td>

 <td><%=calc.getPrice(types[i]) %></td></tr>

 <% } %>

 </table>

 <input type="submit" value="Submit information">

 </form>

 </body>

 </html>

JSP-страница Creditlnfo

Объект Shippinglnfo, связанный с определенным сеансом, создается в JSP-странице Creditlnfo. Информация о доставке, которая была введена в форму в JSP- странице Shippinglnfo, используется в JSP-странице Creditlnfo. Выбранный способ доставки заносится в параметр с именем shipperAndClass, а затем элемент jsp:setProperty вызывает метод setShipperAndClass объекта Shippinglnfo со значением этого параметра. Как уже говорилось ранее, при этом задается сразу и почтовая фирма, и вид доставки, то есть поля shipper и shippingClass (листинг 5.19).

Листинг 5.19. Указание способа доставки (Creditlnfo.jsp)

 <%@ page import="com.XmlEcomBook.Chap05.*" %>

 <jsp:useBean scope="session" id="shippingInfo"
class="Fulfillment" />

 <jsp:setProperty name="shippingInfo" property="*" />

В этой JSP-странице сначала задается объект Fullfilment, связанный с данным сеансом и содержащийся в объекте Order. Также создается объект ShippingCalculator, который используется для подсчета выбранного способа доставки. После того как знак $ убран из строки с указанием цены, Shippinglnfo преобразуется к типу double и используется для вызова метода setCostToCustomer объекта Shippinglnfo (листинг 5.20).

Листинг 5.20. Определение стоимости доставки (Creditlnfo.jsp)

<jsp:useBean scope="session" id="order" class="Order" />

 <% order.setFulfillment( shippingInfo );

 ShippingCalculator calc = new ShippingCalculator( order );

 String s = request.getParameter( "shipperAndClass" );

 String price = calc.getPrice( s );

 price = price.replace( '$', ' ' );

 shippingInfo.setCostToCustomer
( Double.parseDouble( price ) );%>

Код HTML этой JSP-страницы используется для сбора необходимой информации о кредитной карте клиента. В первую очередь мы хотели бы отобразить в таблице полностью всю сумму, которую мы собираемся снять со счета клиента. Мы показываем общую стоимость покупки, стоимость доставки и суммарную стоимость заказа. Далее выводится форма, в которой клиент указывает тип своей кредитной карты, номер и дату окончания срока действия, как показано в листинге 5.21.

Листинг 5.21. Код HTML, который формируется в JSP-странице Creditlnfo (Creditlnfo.jsp)

<html>

 <head><title>Credit Card Information</title></head>

 <body>

 Your order price<br />

 <table>

 <tr><td>Items</td><td><%=order.getTotalItemPrice()%>
</td></tr>

 <tr><td>Shipping</td><td><%=price%></td></tr>

 <tr bgcolor="yellow"><td>Total</td><td>
<%=order.getOrderTotal()%></td></tr>

 </table>

 Please enter your credit card information:

 <form action="ConfirmInfo.jsp">

 <p>

 Credit Card Type:

 <input type="radio" name="creditCardType" value="Visa">

 Visa

 </input>

 <input type="radio" name="creditCardType"
value="Master Card">

 Master Card

 </input>

 <input type="radio" name="creditCardType"
 value="American Express">

 American Express

 </input>

 <input type="radio" name="creditCardType" value="Discover">

 Discover

 </input>

 </p>

 <p>Credit Card Number:<input name="creditCardNumber" />
</p>

 <p>Expiration Date:<input name="expirationDate" /></p>

 <input type="submit" value="Submit information">

 </form>

 </body>

 </html>

JSP-страница Confirmlnfo

Первое, что мы делаем в JSP-странице Confirmlnfo, — это помещаем информацию, введенную в форму на JSP-странице Creditlnfo, в объект Creditlnfo. При этом используются элементы jsp:useBean и jsp:setProperty, как и в двух предыдущих JSP-страницах. После того как получен объект Order, скриптлет извлекает из него объект Customer-Information, а затем вызывается метод setCreditlnfo, чтобы записать полученные значения, как показано в листинге 5.22.

Листинг 5.22. Запись данных кредитной карты (Confirmlnfo.jsp)

 <%@ page import="com.XmlEcomBook.Chap05.*" %>

 <jsp:useBean scope="session" id="creditInfo"
class="CreditInfo" />

 <jsp:setProperty name="creditInfo" property="*" />

 <jsp:useBean scope="session" id="order" class="Order" />

 <% CustomerInfo cust = order.getCustomerInfo();

 cust.setCreditInfo( creditInfo );

 %>

Код HTML этой страницы вновь отображает все введенные клиентом данные. Это позволяет клиенту проверить всю введенную им информацию до того, как она будет окончательно отправлена на сервер поставщика для получения подтверждения. Для этого берутся объекты Customerlnfo и Fullfilment, связанные с данным сеансом, и с помощью элементов jsp:getProperty из них извлекаются различные параметры заказа, которые затем отображаются на странице. Внизу страницы располагается кнопка, которая позволяет клиенту отправить данные на сервер, если он не обнаружил ошибок. Чтобы исправить неверно введенные данные, клиент может воспользоваться кнопкой Back (Назад) в окне браузера. Листинг 5.23 содержит код для отображения и проверки введенных клиентом данных.

Листинг 5.23. Отображение информации для ее подтверждения клиентом (Confirmlnfo.jsp)

<html>

 <head><title>Confirm Info</title></head>

 <body>

 <jsp:useBean scope="session" id="custInfo"
class="CustomerInfo" />

 <jsp:useBean scope="session" id="shippingInfo"
class="Fulfillment" />

 <p>Verify the information you entered:</p>

 <p>Name: <b><jsp:getProperty name="custInfo"
property="firstName" />

 <jsp:getProperty name="custInfo" property="lastName" /></b>

 </p>

 <p>

 Address:<br />

 <b><jsp:getProperty name="custInfo" property="address1" />
<br />

 <jsp:getProperty name="custInfo" property="address2" /><br />

 <jsp:getProperty name="custInfo" property="city" />,

 <jsp:getProperty name="custInfo" property="state" />

 <jsp:getProperty name="custInfo" property="zip" />

 </b></p>

 <p>Email: <b><jsp:getProperty name="custInfo"
property="email" /></b></p>

 <p>Phone Number: <b><jsp:getProperty name="custInfo"
property="phoneNumber" />

 </b></p>

 <p>

 Credit Card Type : <b><jsp:getProperty name="creditInfo"

 property="creditCardType" /></b><br />

 Credit Card Number: <b><jsp:getProperty name="creditInfo"

 property="creditCardNumber" /></b><br />

 Expiration Date : <b><jsp:getProperty name="creditInfo"

 property="expirationDate" /></b>

 </p>

 <p>

 Shipper: <b><jsp:getProperty name="shippingInfo"

 property="shipper" /></b><br />

 Class : <b><jsp:getProperty name="shippingInfo"

 property="shippingClass" /></b>

 </p>

 <b><i>Press the back button on your browser to
correct any information.</i></b>

 <form action="servlet/SubmitOrder">

 <input type="submit" value="Submit Order" />

 </form>

 </body>

 </html>

Сервлет SubmitOrder

Когда подтвержденные клиентом данные (то есть JSP-страница Confirmlnfo) отправлены, необходимо послать поставщику услуг по обработке сведения, связанные с оплатой заказа. Это делается с помощью сервлета SubmitOrder. Здесь мы используем не JSP-страницу, а сервлет, поскольку данная процедура, с одной стороны, требует довольно много кода, а с другой стороны, генерирует сравнительно немного выходных данных. Дело в том, что, как правило, создавать и отлаживать сервлеты проще, чем JSP-страницы, но отрицательной стороной сер- влетов является сложность генерируемого кода HTML.

Главная точка входа в сервлет — метод doGet, который получает объект Session, соответствующий текущему сеансу (листинг 5.24). Затем мы получаем объект Order, сформированный для этого сеанса предыдущими JSP-страницами. Далее для получения подтверждения данных кредитной карты используется класс TestPaymentAuthorizer. Если подтверждение получено, клиенту посылается соответствующее сообщение с помощью класса Emailег, вызывается метод, который записывает заказ в специальный файл, и вызывается JSP-страница Approved.jsp, создающая сообщение с информацией о подтверждении заказа и указанием идентификатора заказа. Если подтверждение не было получено, вызывается JSP-страница Declined .jsp и создается другое сообщение.

Листинг 5.24. Метод doGet сервлета SubmitOrder (SubmitOrder.java)

import java.io.*;

 import java.util.*;

 import javax.servlet.*;

 import javax.servlet.http.*;

 import com.XmlEcomBook.Chap05.*;

 // Explain why we used a servlet: easier to code, debug

 public class SubmitOrder extends HttpServlet {

 public void doGet(HttpServletRequest req,

 HttpServletResponse res)

 throws IOException, ServletException {

 res.setContentType("text/html");

 PrintWriter out = res.getWriter();

 try {

 HttpSession session = req.getSession();

 Order order = (Order)session.getAttribute( "order" );

 Authorization auth =
TestPaymentAuthorizer.getAuthorization(order );

 order.setAuthorization( auth );

 if( auth.isApproved() ) {

 //Emailer.sendConfirmation( order );

 writeOrder( order );

 getServletContext()

 .getRequestDispatcher("/Approved.jsp").forward(req, res);

 }

 else {

 getServletContext()

 .getRequestDispatcher("/Declined.jsp").forward(req, res);

 }

 }

 catch( Exception e )

 {

 e.printStackTrace(out);
 }
 }

Метод writeOrder используется для записи информации о заказе в файл XML. Этот метод помещает все файлы с заказами, поступившими в течение одного дня, в отдельную папку. Поэтому для определения даты (год, месяц и число месяца) создается специальный объект, GregorianCalendar. В имени папки указывается соответствующая дата, а название файла содержит идентификатор заказа. Метод mkdir класса File вызывается для проверки наличия данной папки, а затем с помощью метода writeXML объекта Order информация о заказе заносится в файл, как показано в листинге 5.25.

Листинг 5.25. Метод writeOrder (SubmitOrder.java)

private void writeOrder( Order order ) {

 try

 {

 Calendar calendar = new GregorianCalendar();

 int day = calendar.get( Calendar.DAY_OF_MONTH );

 int month = calendar.get( Calendar.MONTH ) + 1;

 int year = calendar.get( Calendar.YEAR );

 String dir = "Orders_" + year + "-" + month + "-" + day;

 String filename = "Order_" + order.getId() + ".xml";

 File file = new File( dir );

 file.mkdir();

 FileWriter writer = new FileWriter( dir + File.separator + filename );

 order.writeXML(writer);

 writer.close();

 }

 catch( IOException e ) {

 }

 }

 }

JSP-страница Approved

Эта страница вызывается из сервлета SubmitOrder, когда заказ получает подтверждение поставщика услуг по обработке. Эта простая страница включает в себя идентификатор заказа (листинг 5.26); ее можно использовать как квитанцию и при необходимости распечатать данные заказа. Так как у нас имеется объект Order с полной информацией о заказе, в страницу Approved можно включить помимо идентификатора и другие характеристики заказа.

Листинг 5.26. JSP-страница Approved (Approved.jsp)

<%@ page import="com.XmlEcomBook.Chap05.*" %>

 <html>

 <head><title>Approved Order</title></head>

 <body>

 <jsp:useBean id="order" scope="session" class="Order" />

 Your order has been approved.

 Your order number is: <jsp:getProperty name="order"
property="id" />

 </body>

 </html>

JSP-страница Declined

JSP-страница Declined формируется, если по какой-либо причине кредитная карта клиента не прошла проверку поставщика. Страница генерирует сообщение, в котором указывается эта причина.

Листинг 5.27. JSP-страница Declined (Declined.jsp)

<%@ page import="com.XmlEcomBook.Chap05.*" %>

 <html>

 <head><title>Credit Card Declined</title></head>

 <body>

 <jsp:useBean id="order" scope="session" class="Order" />

 Your credit card was declined.<br />

 The reason given was:

 <% Authorization auth = order.getAuthorization();

 if( auth != null ) {

 out.println( auth.getReason() );

 }

 %>

 </body>

 </html>

Обновление информации о доставке

После того как клиент ввел и подтвердил информацию о заказе, отдел доставки электронного магазина должен иметь возможность просматривать эту информацию и обновлять значения таких характеристик, как номер заказа и дата отправки заказа клиенту. Мы также предложим способ формирования списка всех заказов, поступивших в конкретный день, и выбора из этого списка определенного заказа.

JSP-страница OrderDateSelector

Простая JSP-страница OrderDateSelector (листинг 5.28) отображает форму с единственным полем ввода. В этом поле по умолчанию указана текущая дата, но пользователь (в данном случае работник отдела доставки) может ввести другую дату. Эта JSP-страница является точкой входа в процесс обновления информации о доставке.

Листинг 5.28. JSP-страница OrderDateSelector (OrderDateSelector.jsp)

 <%@ page import="java.util.*" %>
 <html>
 <head><title>0rder Date Selector</title></head>
 <body>
 <% Calendar calendar = new GregorianCalendar();
 int day = calendar.get( Calendar.DAY_OF_HONTH );
 int month = calendar.get( Calendar.MONTH ) + 1;
 int year = calendar.get( Calendar.YEAR ); %>
<form action="SelectOrder.jsp">
Select a date:
<input name="date" value="
 <%= new String( year + "-" + month + "-" + day ); %>">
 <br /xinput type="submit" value="Get Orders for Date">
 </form>
 </input>
 </body>
 </html>

JSP-страница SelectOrder

JSP-страница SelectOredr (листинг 5.29) использует заранее определенную (стандартную) переменную request, чтобы получить введенную пользователем в JSP- страницу OredrDateSel ector дату. Эта дата требуется впоследствии для создания имени папки. Так как имя файла XML, содержащего сведения о заказе, включает в себя идентификатор данного заказа, все, что нужно сделать для отображения заказов, — это выделить идентификатор заказа из имени файла. Используя этот идентификатор, мы для каждого заказа создаем элемент HTML а для ссылки на JSP-страницу. Каждая ссылка содержит имя выбранного файла XML и папки, в которой этот файл содержится. Пользователь может просто щелкнуть на этой ссылке и увидеть соответствующий заказ. Например, если заказ с идентификатором 1014 был сделан 16 октября 2000 года, то ссылка будет иметь вид:

 <а href="ShowOrder.jsp?dir=
 Orders_2000-10-16&file=0rder_1014.xml">1014</a>

Листинг 5.29. JSP-страница SelectOrder (SelectOrder.jsp)

<%@ page import="java.io.*" %>

 <html>

 <head><title>Select Order</title></head>

 <body>

 <%

 String date = request.getParameter( "date" );

 File dir = new File( "Orders_" + date );

 File[] files = dir.listFiles();

 for( int i = 0; i < files.length; i++ ) {

 String name = files[i].getName();

 if( name.endsWith( ".xml" ) ) {

 int start = name.indexOf( '_' ) + 1;

 int end = name.indexOf( '.' );

 String orderNum = name.substring( start, end );

 %>

 <a href="ShowOrder.jsp?dir=<%= dir %>&file=<%= name %> ">

 <%= orderNum %>

 </a><br />

 <% }

 }

 %>

 </body>

 </html>

JSP-страница ShowOrder

Эта страница отображает данные о заказе, который был выбран пользователем для просмотра. Для создания объектной модели документа задействуются выбранные с помощью описанной выше страницы файл и папка. Используя скрипт- леты (код внутри тегов <%..%>), мы производим анализ файла с помощью стандартных методов DOM. Результат этого разбора записывается в JSP-выражения (код внутри тегов <*=!...*>). Страница ShowOrder представлена в листинге 5.30. Хотя ее код довольно длинный, многие кодовые фрагменты повторяются.

Листинг 5.30. JSP-страница ShowOrder (ShowOrder.jsp)

<%@ page

 import="javax.xml.parsers.*,java.util.*,java.io.*,org.w3c.dom.*,
org.xml.sax.*"

 %>

 <html>

 <head><title>Order</title></head>

 <body>

 <%

 double price = 0.0;;

 String dir = request.getParameter( "dir" );

 String file = request.getParameter( "file" );

 Document document = null;

 DocumentBuilderFactory factory

 = DocumentBuilderFactory.newInstance();

 try {

 DocumentBuilder builder = factory.newDocumentBuilder();

 document = builder.parse( new File( dir, file ) );

 }

 catch( ParserConfigurationException pce ) {

 throw new IOException( "Parser Configuration Error" );

 }

 catch( SAXException se ) {

 throw new IOException( "Parsing Excpetion" );

 }

 Element order = document.getDocumentElement();

 String id = order.getAttribute( "id" );

 %>

 <h1>Order #<%=id%></h1>

 <h2>Items</h2>

 <table border="1">

 <tr><th>Item</th><th>Description</th><th>Quantity</th>
<th>Price</th></tr>

 <% NodeList items = order.getElementsByTagName( "item" );

 int numItems = items.getLength();

 for( int i = 0;i < numItems; i++ ) {

 Element item = (Element)items.item( i );

 %>

 <tr><td><%=item.getAttribute( "id" )%></td>

 <td><%=item.getFirstChild().getNodeValue()%></td>

 <td><%=item.getAttribute( "quantity" )%></td>

 <td><%=item.getAttribute( "price" )%></td>

 <%String priceString = item.getAttribute( "price" );

 priceString = priceString.replace( '$', ' ' );

 price += Double.parseDouble( priceString ); %>

 </tr>

 <% }%>

 </table>

 <% NodeList n1 = order.getElementsByTagName
( "customer_info" );

 Element cust = (Element)n1.item( 0 );

 Node firstName = cust.getElementsByTagName
( "first_name").item(0);

 Node lastName = cust.getElementsByTagName
( "last_name" ).item(0);

 Node address1 = cust.getElementsByTagName
( "address1" ).item(0);

 Node address2 = cust.getElementsByTagName
( "address2" ).item(0);

 Node city = cust.getElementsByTagName( "city" ).item(0);

 Node state = cust.getElementsByTagName( "state" ).item(0);

 Node zip = cust.getElementsByTagName( "zip" ).item(0);

 Node email = cust.getElementsByTagName( "email" ).item(0);

 Node phone = cust.getElementsByTagName( "phone" ).item(0);

 %>

 <h2>Customer Information</h2>

 Name:

 <%=firstName.getFirstChild().getNodeValue()%>

 <%=lastName.getFirstChild().getNodeValue()%><br /><br />

 Address:<br />

 <%=address1.getFirstChild().getNodeValue()%><br/ >

 <%=address2.getFirstChild().getNodeValue()%><br />

 <%=city.getFirstChild().getNodeValue()%>

 <%=state.getFirstChild().getNodeValue()%>

 <%=zip.getFirstChild().getNodeValue()%><br /><br />

 Email:<%=email.getFirstChild().getNodeValue()%><br />

 Phone:<%=phone.getFirstChild().getNodeValue()%><br />

 <% NodeList n2 = order.getElementsByTagName( "credit_info" );

 Element credit = (Element)n2.item( 0 );

 Node number = credit.getElementsByTagName
( "card_number").item(0);

 Node type = credit.getElementsByTagName
( "card_type").item(0);

 Node exp = credit.getElementsByTagName
( "expiration_date").item(0);

 %>

 <h2>Credit Card Information</h2>

 Type:<%=type.getFirstChild().getNodeValue()%><br />

 Number:<%=number.getFirstChild().getNodeValue()%><br />

 Expiration Date:<%=exp.getFirstChild().getNodeValue()%><br />

 <% NodeList n3 = order.getElementsByTagName
( "authorization" );

 Element auth = (Element)n3.item( 0 );

 String approved = auth.getAttribute( "approved");

 Node reason = auth.getElementsByTagName
( "reason").item(0);

 Node auth_code = auth.getElementsByTagName
( "authorization_code").item(0);

 %>

 <h2>Authorization Information</h2>

 Auth Code:<%=auth_code.getFirstChild().getNodeValue()%>

<br />

 Approved:<%=approved%><br />

 Reason:<%=reason.getFirstChild().getNodeValue()%><br />

 <% NodeList n4 = order.getElementsByTagName( "fulfillment" );

 Element fulfillment = (Element)n4.item(0);

 Node shipper = fulfillment.getElementsByTagName
( "shipper" ).item(0);

 Node clas = fulfillment.getElementsByTagName( "class" ).item(0);

 Node cost = fulfillment.getElementsByTagName( "cost" ).item(0);

 Node tracking = fulfillment.getElementsByTagName
( "tracking_number" ).item(0);

 Node dateSent = fulfillment.getElementsByTagName
( "date_sent" ).item(0);

 String trackingString = tracking.getFirstChild().getNodeValue();

 String dateSentString = dateSent.getFirstChild().getNodeValue();

 %>

В нижней части этой JSP-страницы располагается форма, предназначенная для ввода номера отслеживания заказа и даты его отправки. Эти значения можно вводить в том случае, если в поле для номера указано значение NO_TRA- CKING_NUMBER (номер отсутствует). Эта строка используется для указания, что данное поле еще не инициализировано, так что пользователь может ввести новое значение. Если же значения уже заданы, они просто отобразятся и пользователь уже не сможет их редактировать. В этой форме (листинг 5.31) также имеется некоторое количество скрытых полей, которые содержат информацию, необходимую для JSP-страницы, обновляющей файл XML.

Листинг 5.31. Форма для ввода данных о доставке (ShowOrder.jsp)

<form action="UpdateFulfillment.jsp">

 <input type="hidden" name="dir" value="<%=dir%>" />

 <input type="hidden" name="file" value="<%=file%>" />

 <input type="hidden" name="email"

 value="<%=email.getFirstChild().getNodeValue()%>" />

 <input type="hidden" name="id" value="<%=id%>" />

 <input type="hidden" name="auth_code"

 value="<%=auth_code.getFirstChild().getNodeValue()%>" />

 <input type="hidden" name="price" value="<%=price%>" />

 <h2>Fulfillment Info</h2>

 Shipper:<%=shipper.getFirstChild().getNodeValue()%><br />

 Class:<%=clas.getFirstChild().getNodeValue()%><br />

 Cost:$<%=cost.getFirstChild().getNodeValue()%><br />

 Tracking #:

 <%if( trackingString.equals( "NO_TRACKING_NUMBER" ) ) { %>

 <input name="tracking" value="<%=trackingString%>" />

 Date Sent:<input name="date_sent"
value="<%=dateSentString%>" />

 <input type="submit" value="Submit New Fulfillment Data" />

 <%} else {%>

 <%=trackingString%><br />

 Date Sent:<%=dateSentString%>

 <%}%>

 </form>

 <br /><a href="OrderDateSelector.jsp">
Back to date selection</a>

 </body>

 </html>

JSP-страница UpdateFullfilment

Последняя JSP-страница, которую мы исследуем в этой главе, отвечает за обновление значений номера отслеживания и даты отправления, введенных на странице ShowOrder. На этой JSP-странице мы применим другой способ обработки файла XML. Перед нами стоит довольно-таки простая задача: нужно обновить значения двух переменных в файле XML. Вместо того чтобы использовать DOM и проводить анализ документа, а затем снова записывать его в виде файла XML, мы применим более простой метод текстовой обработки. Элементам, которые нам нужны, — tracki ng_number и date_sent — при создании файла XML были присвоены специальные значения, NO_TRACKING_NUMBER и NO_DATE_SENT. Обновление файла XML в данном случае сводится к элементарному контекстному поиску этих строк и замене их на новые значения.

Сначала JSP-страница получает пересланные ей параметры, включая скрытые поля формы. Для считывания файла XML создается объект BufferReader, a объект FileWriter используется для вывода обновленного файла XML. Каждая прочитанная строка XML передается методу replace для выполнения необходимых замен. Этот метод replace аналогичен методу replace класса String, единственное отличие заключается в том, что он оперирует не символами (то есть объектами типа char), а строками.

Описанная техника обработки текста в простых случаях пригодна для обработки документов XML. Ее использование позволяет избежать дополнительных сложностей и расходов, связанных с анализом документа XML. Но эта техника не всегда хорошо работает, если требуется произвести какие-либо сложные замены. Работая с документом XML как с простым текстовым документом, вы создаете слишком чувствительный к внешним условиям код, который не будет работать при любых изменениях DTD или формата XML. Использование анализатора кода является наилучшим решением, если требуется достаточно серьезная обработка документа XML.

После того как в документ XML внесены необходимые изменения, эта JSP- страница посылает клиенту с помощью класса Emailег сообщение, подтверждающее выполнение его заказа. Также она информирует поставщика услуг по обработке, что оформление заказа закончено, поэтому можно снимать деньги со счета клиента и переводить их на счет магазина, как показано в листинге 5.32.

Листинг 5.32. JSP-страница UpdateFullfilment (UpdateFullfilment.jsp)

<%@ page import="java.io.*,com.XmlEcomBook.Chap05.*" %>

 <html>

 <head><title>Update Complete</title></head>

 <body>

 <% String tracking = request.getParameter( "tracking" );

 String dateSent = request.getParameter( "date_sent" );

 String dir = request.getParameter( "dir" );

 String filename = request.getParameter( "file" );

 String email = request.getParameter( "email" );

 String id = request.getParameter( "id" );

 String auth_code = request.getParameter( "auth_code" );

 String priceString = request.getParameter( "price" );

 double price = Double.parseDouble( priceString );

 File inFile = new File( dir, filename );

 File outFile = new File( dir, filename + ".tmp" );

 BufferedReader reader = new BufferedReader
( new FileReader( inFile ) );

 FileWriter writer = new FileWriter( outFile );

 String line;

 while( (line = reader.readLine()) != null ) {

 String newLine = replace( line, "NOT_SENT_YET", dateSent );

 newLine = replace( newLine, "NO_TRACKING_NUMBER",
tracking );

 writer.write( newLine + "\n" );

 }

 reader.close();

 writer.close();

 inFile.renameTo( new File( dir, filename + ".old" ) );

 outFile.renameTo( inFile );

 Emailer.sendShipped( email, id );

 TestPaymentAuthorizer.capture( auth_code, price );

 %>

 <p>The fulfillment was updated with the new information.</p>

 <a href="OrderDateSelector.jsp">Back to date selection</a>

 </body>

 </html>

 <%!

 String replace( String s, String oldString, String newString ) {

 int pos = s.indexOf( oldString );

 String newLine = s;

 if( pos != -1 ) {

 newLine = s.substring( 0, pos );

 newLine += newString;

 newLine += s.substring( pos + oldString.length() );

 }

 return newLine;

 }

 %>

Листинги программ, приведенные в этой главе, показывают, как можно реализовать необходимые для работы магазина функции по обработке заказов и составлению счетов. Чтобы использовать эти программы в реальном магазине, необходима некоторая доработка. Наибольшие изменения будут касаться требований безопасности. JSP-страницы, которые задействуются в процессе ввода информации клиентом, должны использовать не простой протокол HTTP, a HTTPS. Далее, в нашем примере информация о клиентах хранилась в незашифрованном файле в той же системе файлов, в которой работает web-сервер магазина. В идеале вся персональная информация о клиентах должна храниться в зашифрованном, безопасном источнике данных на изолированном сервере, к которому нет доступа из Интернета.

С другой стороны, необходимо заменить класс, отвечающий за проверку номеров кредитных карт, на класс, который будет взаимодействовать с реальным поставщиком услуг по обработке. Но в процессе тестирования системы, на том этапе, когда еще не нужно взаимодействие с реальным поставщиком, можно воспользоваться приведенным здесь классом.


  • Глава 6. Обслуживание виртуального каталога
    • Общие принципы редактирования каталога
    • Объекты данных
      • Класс Catalog
      • Класс XMLWriter
      • Класс ProductLine
      • Класс Product
      • Класс Image
      • Класс Clip
      • Класс DateTime
      • Класс Util
    • Код для представления информации пользователю
      • Главная HTML-страница
      • Класс Main сервлета
      • JSP-страница Delete
      • JSP-страница Edit
      • Сервлет UpdateProduct

Глава 6.

Обслуживание виртуального каталога

В главе 2 мы создали определение типа документа (DTD) каталога товаров для нашего гипотетического сетевого магазина XMLGifts.com. В главе 3 мы создали JSP-страницы и сервлеты для просмотра этого каталога. Теперь нам необходимо обеспечить возможность обновления информации в каталоге, в том числе удаления из него данных о некоторых товарах и добавления данных о новых товарах. Операции такого типа объединены общим названием CRUD (Create, Read, Update, Delete — создание, чтение, обновление, удаление).

Общие принципы редактирования каталога

Так как данные в нашем каталоге представлены в виде кода XML, одним из способов редактирования является использование обычного текстового или XML- редактора. XML — это формат, доступный для чтения людьми, так что пользователь может просто открыть файл .xml в текстовом редакторе и вручную его отредактировать. Этот способ имеет, однако, два недостатка. Во-первых, редактирование файла XML с помощью стандартного текстового редактора может вызвать некоторые затруднения. Во-вторых, если сервер, формирующий XML, преобразовал файл XML в формат базы данных, то текстовые редакторы уже неприменимы. Для преодоления этих недостатков вы можете создать свой, пользовательский редактор каталогов.

Существует много способов написания такого редактора. Например, вы можете использовать приложение Java, считывающее данные XML и представляющее их с помощью стандартного интерфейса Swing GUI (Graphical User Interface — графический пользовательский интерфейс), в котором пользователь может редактировать эти данные. Если вам нужно распределенное решение, вы можете создать апплет, который будет работать в браузере. В настоящей главе мы создадим редактор, который будет использовать web-сервер, HTML и JSP, так же как было сделано в главе 3 при разработке системы представления каталога. Применение схожих технологий и инструментальных средств в рамках одного проекта имеет большое значение и позволяет упростить разработку и поддержку приложения. В разработке различных частей проекта могут участвовать одни и те же программисты, и, кроме того, есть вероятность многократного использования некоторых идей и фрагментов кода.

Для редактирования каталога мы создадим приложение, которое будет работать через браузер. Такое приложение позволит каждому, у кого имеется доступ к сайту магазина, редактировать каталог. Вы можете создавать JSP-страницы с формами HTML, которые пользователи смогут заполнять. Данные, внесенные в такие формы, будут отправляться на сервер, и, таким образом, можно будет создавать, редактировать и удалять информацию о товарах в каталоге.

Это решение включает в себя четыре уровня, начиная с данных XML и кончая браузером, который отображает информацию. Два промежуточных уровня подразумевают написание программ. К одному из них относятся объекты данных, которые осуществляют преобразование между XML и Java, а в другом создаются и интерпретируются данные HTML, отображаемые браузером. На рис. 6.1 схематично изображены эти уровни.

Рис. 6.1. Общие принципы решения задачи редактирования каталога

Первым шагом в создании приложения является конструирование объектов XML для представления информации каталога. В этих объектах будут содержаться данные о сериях товаров и об отдельных товарах, а также такие атрибуты информации об отдельных товарах, как их изображения (файлы с расширением .gif) или видеоклипы. Эти объекты конструируются на основе исходных данных XML, могут отображаться в формате XML и служат мостом между миром XML и миром объектов Java.

Затем вы создадите сервлеты и JSP-страницы, которые будут отображать информацию для пользователя и обрабатывать введенную им информацию. При этом информация будет извлекаться из объектов данных и с помощью браузера отображаться в формах. После заполнения форм пользователем информация отправится обратно на сервер. На этом этапе также потребуется программа, которая получит эту информацию и обновит объекты данных.

Объекты данных

Иногда взаимодействие с документом XML на уровне анализатора оказывается непростым делом. Если документ имеет достаточно сложную структуру, то работа с каждым элементом и атрибутом по отдельности превращается в чреватое многочисленными ошибками кропотливое, длительное занятие. Так получается потому, что анализаторы DOM или SAX не знают о деталях устройства схем отдельных документов. Эти анализаторы общего назначения должны уметь читать любой документ независимо от используемого определения DTD. Было бы гораздо проще, если бы существовали объекты, имеющие такую же структуру, как и тот документ XML, который они анализируют.

Процесс создания набора объектов, предназначенных для преобразования данных XML к более удобным объектам Java, называется связыванием данных (data binding). Связывание данных позволяет работать с исходными данными XML на уровне структуры документа, вместо того чтобы непосредственно обращаться к элементам и атрибутам. Когда набор объектов для связывания данных готов, использующий их программист может даже не подозревать, что основу этих объектов составляет XML.

В этой главе показано, как вручную создать объекты для связывания данных из нашего XML-каталога, но эти объекты можно также генерировать автоматически. Для этой цели имеется множество инструментальных средств. Для создания кода эти программы считывают DTD, схему XML или образец документа XML. Обычно для каждого элемента XML создается свой класс, а поля этого класса отводятся под атрибуты элемента XML.

В качестве примера приведем фрагмент DTD для описания адреса:

 <!ELEMENT person  (address)>
 <!ATTLIST person firstname   CDATA #IMPLIED>
            lastname CDATA #IMPLIED>
<!ELEMENT address  (Street, City,  Zip)>

Инструмент связывания данных может создать следующий класс для элемента person:

 class Person  {
 private String lastname;
 private String firstname: private Address address;
public Address getAddress()

{...} public void setAdress( Address a )

{...} public  String getFirstname( )

{...} public void setFistname( String s )

{...} public String getLastname( )

{...} public void setLastnarae( )

{...} }

Также можно создать класс Address, в котором имеются методы, чтобы задавать и извлекать названия улицы, города и почтовый индекс. Как видно, эти классы гораздо удобнее при программировании, чем классы DOM или SAX.

ПРИМЕЧАНИЕ

Одним из таких инструментов связывания данных является Castor, созданный группой Exolab. Более подробная информация содержится на сайте http://castor.exolab.org.

Создание этих классов вручную имеет кроме огромной пользы в плане обучения и другие преимущества. Полученный код будет более эффективным, чем код, генерируемый инструментом связывания данных общего назначения. Большинство инструментов связывания данных используют некоторую объектную модель для загрузки данных и затем создают объекты на ее основе. В результате этого при загрузке документов XML требуется большой объем памяти. Другим преимуществом ручного кодирования классов является возможность учитывать специфические требования каждого приложения. При ручном кодировании классов для связывания данных также достигается большая гибкость.

При разработке классов для связывания данных необходимо изучить DTD каталога и решить, для каких элементов нам требуется создать классы, а какие могут быть представлены стандартными классами, такими как String или Integer. Это решение зависит от того, насколько сложным является элемент. Элементы с содержимым типа PCDATA, лишенные атрибутов, легко могут быть представлены в виде Srting или Integer в зависимости от конкретного содержимого. Для таких простых элементов не нужно создавать новые классы. Но если у элемента имеется дочерний элемент или атрибут, простой класс String может оказаться недостаточным для представления этого элемента, и для него вам потребуется создать новый класс.

Рис. 6.2. UML-диаграмма классов

Посмотрев на DTD из главы 2, вы обнаружите довольно много элементов, не представимых простыми классами. Элементы catalog, product_1ine, product, image, clip и onsale_date содержат дочерние элементы, так что все они являются кандидатами на новые классы. Некоторые другие элементы являются простыми, так как не имеют атрибутов и содержат простые данные. Элементы author, arti st, quantity_in_stock и все дочерние элементы onsale_date являются простыми элементами и могут быть представлены классам String или Integer. Мы не упомянули здесь еще несколько элементов, таких как description (и его дочерние элементы) и price, которые мы будем обрабатывать несколько иначе. Позже мы рассмотрим способ их обработки более подробно.

На рис. 6.2 представлена UML-диаграмма, изображающая классы, которые нам нужно создать. Служебные классы не представлены на этой диаграмме, так как иначе она стала бы слишком сложной.

ПРИМЕЧАНИЕ

UML (Unified Modeling Language) — унифицированный язык моделирования. Это стандартный способ схематического изображения объектно-ориентированных конструкций. Полезно иметь стандартный способ документирования независимо от того, создаете ли вы формальный документ или просто набрасываете схему на листке бумаги. Стандарт помогает быстро и легко сообщать окружающим ваши идеи. Более полная информация по UML находится по адресу www.rational.com/uml.

Если вы внимательно изучите программы, находящиеся на прилагаемом к книге компакт-диске, вы увидите, что каждый класс объектов данных имеет методы toString. Эти методы очень полезны при отладке, но не являются составной частью классов, поэтому не включены в описание программ, чтобы в этой главе вы сосредоточились на основной части кода.

Класс Catalog

Класс Catalog представляет корневой элемент DTD каталога, приведенного ниже: <!ELEMENT catalog (product_line*)>

У этого элемента нет атрибутов и имеется только один многократно повторяемый дочерний элемент с именем productjline. В листинге 6.1 приведена первая часть кода для объявления класса Catalog.

Листинг 6.1. Первая часть кода для объявления класса Catalog (Catalog.java)

package com.XmlEcomBook.Chap06;
import javax.xml.parsers.*;

import java.util.*;

import java.io.*;

import org.w3c.dom.*;

import org.xml.sax.*;
public class Catalog {
private Vector productLines = new Vector();

В этом коде формат имен, принятый в XML (нижний регистр с символом подчеркивания в качестве разделителя, например quantity_in_stock), был изменен на формат, соответствующий стандартному синтаксису Java. Имена классов начинаются с прописной буквы, и для разделения различных слов, составляющих одно имя, используются также прописные буквы, а не символы подчеркивания. Элемент с именем catalog становится классом Catalog.

Так как у элемента catalog может быть любое количество дочерних элементов product_l i ne, для их представления используется вектор Vector. Вообще, для представления повторяющихся элементов пригодна любая коллекция. Выбор конкретного вида коллекции основан на ее предполагаемом назначении; Vector является хорошим вариантом коллекции для тех случаев, когда вы не знаете заранее всех подробностей того, как будет использоваться коллекция.

Поскольку задачей данного класса является преобразование данных из XML к объектам Java и затем снова к XML, создается конструктор, которому в качестве параметра передается имя файла XML и который анализирует этот файл для извлечения из него данных. Конструктор показан в листинге 6.2.

Первая часть кода конструктора создает необходимые для анализа файла XML объекты JAXP. Также создается объект DocumentBuilder, который используется для анализа документа и получения объекта DOM Document.

В следующей части кода извлекается корневой элемент документа и все элементы типа product_l i ne. Выполняется цикл по этим элементам, и для каждого создается новый объект ProductLine; затем вызывается метод, который добавляет этот объект к объекту Catalog. Как вы видите, в классе Catalog требуется только найти прямые потомки объекта Catalog и передать их для дальнейшей обработки классу ProductLine.

Листинг 6.2. Конструктор класса Catalog (Catalog.java)

public Catalog(  String filename) throws IOException {
  Document document = null;
  DocumentBuilderFactory factory
  = DocumentBuilderFactory.newInstance();
  try {
  DocumentBuilder builder = factory.newDocumentBuilder();
  document = builder.parse( new File( filename ) );
  }
  catch( ParserConfigurationException pce ) {
  throw new IOException( "Parser Configuration Error" );
  }
  catch( SAXException se ) {
  throw new IOException( "Parsing Excpetion" );
  }
 Element root = document.getDocumentElement();
  NodeList nodes = root.getElementsByTagName
( "product_line" );
  int num = nodes.getLength();
  for( int i = 0; i < num; i++ ) {
  Element e = (Element)nodes.item( i );
  ProductLine pl = new ProductLine( e );
  addProductLine( pl );
  }
  }

Устройство метода addProductLi ne, представленного в листинге 6.3, достаточно очевидно. Ему передается объект ProductLine, который затем добавляется в вектор productLines.

Листинг 6.З. Добавление новых серий товаров — элементов ProductLine (Catalog .Java)

public void addProductLine( ProductLine productLine )  {
 productLines.addEleraent( productLine ); }

Далее, вам нужен метод для доступа к объектам ProductLine, которые содержатся в объекте Catalog. Так как серии товаров идентифицируются по своим названиям, нужен метод, который извлекал бы объект на основании его имени. Этот метод показан в листинге 6.4. Методу getProductLine передается в качестве параметра название серии товаров, а затем в этом методе выполняется цикл по всем сериям товаров в каталоге, пока не будет найдена серия с этим именем. Если ни одна серия не найдена, возвращается nul 1.

Листинг 6.4. Поиск объекта ProductLine (Catalog.java)

public ProductLine  getProductLine( String name ) {
  Enumeration enum = productLines.elements();
  while( enum.hasMoreElements() ) {
  ProductLine pl = (ProductLine)enum.nextElement();
  if( pl.getName().equals( name ) ) {
  return pl;
  }
  }
  return null;
  }

Другая операция, полезная при работе с каталогом, — получение конкретного объекта Product (товар) по его идентификатору. Эта операция позволяет отображать и редактировать отдельные элементы объекта Product. Для этого в каждом объекте, соответствующем серии товаров каталога, должен быть предусмотрен метод поиска конкретного товара. Детали реализации этого метода пока нас не интересуют, они будут рассматриваться в объекте ProductLine. Это типичный пример инкапсуляции, свойственной объектно-ориентированным языкам программирования. Листинг 6.5 иллюстрирует поиск товара по его идентификатору.

Листинг 6.5. Поиск товара в каталоге (Catalog.java)

public Product  getProduct( String id ) {
  Enumeration enum = productLines.elements();
  while( enum.hasMoreElements() ) {
  ProductLine pl = (ProductLine)enum.nextElement();
  Product p = pl.getProduct( id );
  if( p != null ) {
  return p;
  }
  }
  return null;
  }

Если не удалось обнаружить товар с указанным идентификатором, здесь также возвращается объект nul 1. Полезно выработать определенный принцип действия, если поиск оказался неудачным, и в таких случаях либо всегда возвращать null, либо всегда вызывать исключение. В программах, которые мы рассматриваем в этой главе, в случае неудачного поиска мы решили возвращать объект null, и всюду последовательно придерживаемся этого решения.

Объект Catalog позволяет осуществлять еще одно важное действие — удаление объектов Products. Этот метод, приведенный в листинге 6.6, аналогичен методу getProducts, но при нахождении указанного объекта он сначала удаляется из объекта ProductLine, а потом метод возвращает этот объект. Опять-таки, если объекта с указанным идентификатором не удалось обнаружить, возвращается объект null.

Листинг 6.6. Удаление товара из каталога (Catalog.java)

public Product  deleteProduct( String id ) {
  Enumeration enum = productLines.elements();
  while( enum.hasMoreElements() ) {
  ProductLine pl = (ProductLine)enum.nextElement();
  Product p = pl.getProduct( id );
  if( p != null ) {
  pl.deleteProduct( id );
  return p;
  }
  }
  return null;
  }

Вам также необходимо, чтобы класс Catalog был способен выдавать содержащиеся в нем данные в виде документа XML. Для этого существует метод, представленный в листинге 6.7, который записывает данные с помощью объекта XMLWriter. Класс XMLWriter — служебный класс, который используется при выводе данных XML в выходной поток. Этот класс мы рассмотрим чуть позже, после класса Catal og. Как и в случае с конструктором класса Cal atog, где мы разобрали только начальную часть кода XML, которая необходима, чтобы можно было предоставить дальнейший анализ элементам ProductLine, здесь мы пишем только открывающий и закрывающий теги каталога. Запись элементов, соответствующих сериям товаров, является прерогативой самого класса ProductLine.

Листинг 6.7. Запись каталога в виде документа XML (Catalog.java)

public void  toXML( XMLWriter writer ) throws IOException {
  writer.writeln( "<catalog>" );
  writer.indent();
  Enumeration enum = productLines.elements();
  while( enum.hasMoreElements() ) {
  ProductLine pl = (ProductLine)enum.nextElement();
  pl.toXML( writer );
  writer.writeln( "" );
  }
  writer.unindent();
  writer.writeln( "</catalog>" );
  }
  }

Класс XMLWriter

Класс XMLWriter — это служебный класс, используемый для записи выходных данных в формате XML. Этот класс записывает данные в выходной поток Output - Stream, который задается конструктором. OutputStream является полем объекта класса XMLWriter. Данный класс также вставляет требуемые разделители строк. В различных операционных системах требуются различные разделители. Например, в Windows применяются символы возврата каретки и перевода строки, а в Unix — только перевода строки. Вам нужен какой-нибудь способ определения, какой их этих разделителей использовать. К счастью, в Java предусмотрено свойство System, которое указывает, какой разделитель применяется в данной операционной системе. В классе XMLWriter определен статический байтовый массив, содержащий это значение.

Класс XMLWriter также отвечает за структурирование отступов в тексте с выходными данными. Определяется байтовый массив, содержащий строку, которая задает расположение отступов. Также вам необходима булева переменная (поле), которая отслеживает, пишем ли мы в данный момент новую строку или продолжаем текущую строку. Начало класса с описанными полями представлено в листинге 6.8.

Листинг 6.8. Начало кода для класса XMLWriter (XMLWriter.java)

package com.XmlEcomBook.Chap06;
import java.io.*;
public class XMLWriter {
 static private final byte[] LINE_SEPARATOR =
System.getProperty( "line.separator"  ).getBytes();
  static private final byte[] INDENT = " ".getBytes();
 private OutputStream out;
  private int currentIndent;
  private boolean newLine = true;

Конструктору XMLWriter в качестве аргумента передается объект OutputStream, в который будут записаны данные. Передавая этот объект, мы можем направить выходной поток XMLWriter в файл, в стандартное устройство вывода или в ответ HTTP. При отладке приложения это свойство очень удобно. Конструктор показан в листинге 6.9.

Листинг 6.9. Конструктор XMLWriter (XMLWriter.java)

public XMLWriter(  OutputStream newOut ) {
  out = newOut;
  }

Вам необходимо иметь возможность регулировки размера отступа в тексте с выходными данными. Это делается с помощью двух простых методов — один уменьшает размер отступа, а другой увеличивает его. Эти два метода показаны в листинге 6.10.

Листинг 6.10. Регулировка размера отступа (XMLWriter.java)

public void  indent() {
  currentIndent++;
  }
 public void unindent() {
  currentIndent--;
  }

Наконец, необходимо осуществить фактическую запись выходных данных. Для этого имеются два метода — в одном после записи выходных данных располагается разделитель строки, а в другом эти данные просто добавляются в конец текущей строки. Эти два метода называются write и write! п. Они аналогичны методам print и println пакета java.io.PrintStream. Когда вы записываете выходные данные, всегда нужно проверять, записываются они в новую строку или нет. Если данные записываются в новую строку, нужно сделать отступ требуемого размера. Метод write! n просто повторяет метод write, а затем размещает символ разделителя строк. Эти методы приведены в листинге 6.11.

Листинг 6.11. Запись выходных данных (XMLWriter.java)

public void  write( String s ) throws IOException {
  if( newLine )
  {
  for( int i = 0; i < currentIndent; i++ ) {
  out.write( INDENT );
  }
  }
  out.write( s.getBytes() );
  newLine = false;
  }
 public void writeln( String s ) throws IOException {
  write( s );
  out.write( LINE_SEPARATOR );
  newLine = true;
  }
  }

Класс ProductLine

Следующий класс, ProductLine, основан на определении DTD для элемента productline:

<!ELEMENT product_line (product*) >
<!ATTLIST product_line name CDATA #IMPLIED>

Как и в элементе catalog, в этом элементе содержится один повторяющийся дочерний элемент. Поэтому и здесь мы применяем для представления этих дочерних элементов вектор Vector, как показано в листинге 6.12. У этого элемента имеется атрибут name, для которого тоже необходимо создать представление в классе. Добавим поле, имеющее тип Srting. Тип String обычно хорошо подходит для элементов CDATA.

Листинг 6.12. Начало кода класса ProductLine (ProductLine.java)

package com.XmlEcomBook.Chap06;
import java.util.*;
  import java.io.*;
import org.w3c.dom.*;
  public class ProductLine extends Object {
 private String name;
  private Vector products = new Vector();

Для этого класса у нас имеются два конструктора. Первый конструктор не имеет аргументов и просто создает пустой объект ProductLine для представления

серии товаров без указания имени и товаров. Второй конструктор этого класса использует объект DOM Element. Вы уже видели, что Element передается конструктору классом Catalog. Этот конструктор должен быть способен получить атрибут name этого элемента, а затем найти все дочерние элементы, соответствующие товарам данной серии, и создать новые элементы Product на их основе. Эти конструкторы показаны в листинге 6.13.

Листинг 6.13. Конструкторы ProductLine (ProductLine.java)

public ProductLine()  {
  }
 public ProductLine( Element element ) {
  name = element.getAttribute( "name" );
  NodeList productNodes =
element.getElementsByTagName( "product" );
  int num = productNodes.getLength();
  for( int i = 0; i < num; i++ ) {
  addProduct( new Product( (Element)productNodes.item( i ) ) );
  }
  }

Теперь перейдем к методам, открывающим доступ к элементам Product и позволяющим их модифицировать. Они аналогичны методам элемента Catalog, которые использовались для вектора, состоящего из элементов ProductLine. Один метод, показанный в листинге 6.14, требуется для получения элементов (товаров) по их имени, другой — для удаления элементов и еще один — для их добавления в каталог.

Листинг 6.14. Методы для получения и модификации элементов Product (ProductLine.java)

public void  addProduct( Product product ) {
  products.addElement( product );
  }
 public Product deleteProduct( String id ) {
  Enumeration enum = products.elements();
  while( enum.hasMoreElements() ) {
  Product p = (Product)enum.nextElement();
  if( p.getId().equals( id ) ) {
  products.remove( p );
  return p;
  }
  }
  return null;
  }
 public Product getProduct( String id ) {
  Enumeration enum = products.elements();
  while( enum.hasMoreElements() ) {
  Product p = (Product)enum.nextElement();
  if( p.getId().equals( id ) ) {
  return p;
  }
  }
  return null;
  }

Также необходимо иметь возможность получать доступ к полю name элемента Product и модифицировать это поле, как показано в листинге 6.15.

Листинг 6.15. Методы для получения и модификации имени элементов Product (ProductLine.java)

public String  getName(){
  return name;
  }
 public void setName( String newName ) {
  name = newName;
  }

Наконец, нам нужен метод для записи данных в формате XML. Этот метод приведен в листинге 6.16. Как и в классе Catalog, здесь имеется метод с названием toXML, который записывает код XML в указанный объект класса XMLWriter. В этот раз нам также нужно записать атрибут элемента name. В XML разрешается помещать значения атрибутов либо в одинарные, либо в двойные кавычки. То есть вы можете написать name = "ABC" или name = 'ABC' — обе записи эквивалентны. Чтобы упростить вывод данных в формате XML, следует пользоваться одинарными кавычками, так как при введении двойных кавычек их придется снабжать обратной косой чертой, что затрудняет восприятие кода.

Листинг 6.16. Запись данных ProductLine в формате XML (ProductLine.java)

public void  toXML( XMLWriter writer ) throws IOException {
  writer.write( "<product_line" );
  if( name != null )
  writer.write( " name='" + name );
  writer.writeln( "'>" );
  writer.indent();
  Enumeration enum = products.elements();
  while( enum.hasMoreElements() ) {
  Product p = (Product)enum.nextElement();
  p.toXML( writer );
  writer.writeln( "" );
  }
  writer.unindent();
  writer.writeln( "</product_line>" );
  }
}

Класс Product

Элемент product сложнее, чем элементы catalog и product_line, которые мы уже рассматривали. Ниже приводится DTD для этого элемента:

<!ELEMENT product  (name.author*,artist*.description,price,
quantity_in_stock,image*,onsale_date?,clip*)>
<!ATTLIST product    id ID           #REQUIRED>
<!ATTLIST product    keywords     CDATA #IMPLIED>

Элемент product может содержать до девяти различных типов дочерних элементов, многие из которых могут повторяться либо, напротив, отсутствовать. Этот элемент имеет два атрибута. В листинге 6.17 определены поля для каждого из этих элементов и атрибутов, во многом аналогично тому, как это было сделано для предыдущих классов. По-прежнему для повторяющихся элементов используется вектор Vector. Большая часть других элементов имеет тип Stri ng, но здесь есть несколько исключений. Так как мы знаем, что элемент price определяет цену, то есть доллары и центы, то для его представления используется тип double. Элемент quantity_in_stock содержит целое число, поэтому для его представления требуется тип int. Элемент on_sale_date представлен классом Date.

Листинг 6.17. Начало кода класса Product (Product.java)

 package com.XmlEcomBook.Chap06;
import java.io.*;
  import java.util.*;
  import java.text.*;
  import org.w3c.dom.*;
public class Product {
 private String id;
  private String keywords;
  private String name;
  private Vector authors = new Vector();
  private Vector artists = new Vector();
  private String description;
  private double price;
  private Integer discount;
  private int quantityInStock;
  private Vector images = new Vector();
  private DateTime onSaleDate = new DateTime();
  private Vector clips = new Vector();

Посмотрев на определения полей, вы можете заметить, что одно из них вам незнакомо. Для поля discount не имеется соответствующего дочернего элемента или атрибута в элементе product. Так получилось потому, что элемент price довольно-таки просто устроен — он содержит данные типа PCDATA и один атрибут discount, как показано ниже:

 <!ELEMENT price (#PCDATA)>
 <!ATTLIST price discount CDATA #IMPLIED>

Вместо того чтобы создавать для этого элемента новый класс всего лишь с двумя полями, мы включим оба этих поля прямо в класс Product. Поле discount имеет тип Integer. В данном случае мы не можем задействовать примитивный тип int, так как элемент discount является необязательным и может отсутствовать. В такой ситуации нужно воспользоваться объектом null типа Integer.

Другое отличие связано с полем description. В DTD это поле было представлено следующим образом:

 <!ENTITY % running_text "(#PCDATA|bold|italics|
 quote|link|general)*">
 <!ELEMENT description (paragraph|general)*  >
 <!ELEMENT paragraph %running_text;>
 <!ELEMENT bold (#PCDATA)>
 <!ELEMENT italics (#PCDATA)>
 <! ELEMENT quote (#PCDATA)>
 <!ATTLIST quote attrib CDATA #IMPLIED>

<!ELEMENT link (#PCDATA)>

<!ATTLIST link href CDATA «REQUIRED
 alt CDATA #IMPLIED>

<!ELEMENT general (#PCDATA)>

<!ATTLIST general type CDATA #REQUIRED>

Это сложная модель содержимого, включающая большое количество дочерних элементов, которые могут повторяться в произвольном порядке, что приводит к следующим двум затруднениям. Первое связано с представлением поля description в Java-программе. Вам нужно было бы создать класс description, которому можно передать объекты paragraph и general, а затем этот класс снова должен был бы получать к ним доступ. Сам класс Paragraph был бы классом сложного типа, способным содержать любой из своих пяти дочерних элементов и текстовых элементов. Второе затруднение связано с отображением всех этих данных в виде, допускающем редактирование. Было бы очень трудно отобразить эту структуру в формате HTML. Мы решили упростить обработку этих элементов.

Если вы посмотрите на определения полей в классе Product, вы увидите, что тип description определен как String. Вместо того чтобы копировать структуру элемента description, мы предпочли просто хранить ее в виде необработанной строки XML, то есть в виде простого текста, содержащего символьные данные вместе с разметкой. Когда нам потребуется предоставить пользователю описание товара (содержимое элемента description), мы просто отобразим необработанный текст XML, который и будет редактироваться пользователем.

Для класса Product предусмотрены два конструктора, показанные в листинге 6.18. Первый из них не имеет никаких аргументов и используется для создания нового элемента Product. Другому конструктору передается объект DOM Element, и на основе исходного кода XML конструктор создает объект Product. Конструктор сначала получает два атрибута, id и keywords, из элемента product. Для остальных полей созданы методы для извлечения всех элементов, дочерних по отношению к элементу product.

Листинг 6.18. Конструкторы класса Product (Product.java)

public Product()  {
  }
 public Product( Element productElement ) {
  id = productElement.getAttribute( "id" );
  keywords = productElement.getAttribute( "keywords" );
  extractName( productElement );
  extractAuthors( productElement );
  extractArtists( productElement );
  extractDescription( productElement );
  extractPrice( productElement );
  extractQuantityInStock( productElement );
  extractImages( productElement );
  extractDate( productElement );
  extractClips( productElement );
  }

Метод extractName устроен достаточно просто. Он показан в листинге 6.19. В нем используется служебный метод extractTextFrom, который извлекает текст из элемента name. В методах extractAuthors и extractArti sts также используется этот служебный метод. Оба этих метода получают список элементов, дочерних по отношению к элементу product, а затем осуществляют цикл по этим элементам, извлекая из каждого содержащийся в нем текст и вызывая соответствующий метод для добавления элемента к объекту.

Листинг 6.19. Методы extractName, extractAuthors и extractArtists (Product.java)

 private void  extractName( Element productElement ) {
  name = Util.extractTextFrom( "name", productElement );
  }
 private void extractAuthors( Element productElement ) {
  NodeList authorList =
productElement.getElementsByTagName( "author"  );
  for( int i = 0; i < authorList.getLength(); i++ ) {
  Element author = (Element)authorList.item(i);
  addAuthor( Util.extractTextFrom( "name", author ) );
  }
  }
 private void extractArtists( Element productElement ) {
  NodeList authorList =
productElement.getElementsByTagName( "artist"  );
  for( int i = 0; i < authorList.getLength(); i++ ) {
  Element author = (Element)authorList.item(i);
  addArtist( Util.extractTextFrom( "name", author ) );
  }
  }

Теперь мы перейдем к рассмотрению метода extractDescription. Он приведен в листинге 6.20. Этот метод получает элемент description из элемента product. Поскольку в интерфейсе API модели DOM не предусмотрена возможность получения только одного элемента с определенным именем, требуется метод getEle- mentsByTagName, который возвращает коллекцию узлов. Затем нужно проверить длину этой коллекции. Если эта длина больше нуля, в ней имеется элемент description. В таком случае нужно извлечь первый подходящий элемент, поскольку элемент description в коллекции только один. Наконец, мы используем служебный метод extractMarkupAsText для того, чтобы получить содержимое элемента description.

Листинг 6.20. Метод extractDescription (Product.java)

 private void  extractDescription( Element productElement ) {
  NodeList desc =
productElement.getElementsByTagName( "description"  );
  if( desc.getLength() > 0 ) {
  NodeList contents = desc.item(0).getChildNodes();
  description = Util.extractMarkupAsText( contents );
  }
  }

Метод extractPrice должен извлечь содержимое элемента price, что определит фактическую цену, а также значение атрибута discount для этого элемента. В первой строке этого метода извлекается текстовое содержимое элемента price с помощью служебного метода. Полученный текст преобразуется к типу double с помощью объекта NumberFormat. Этот объект способен проанализировать полученный текст и извлечь из строки, содержащей дополнительные символы (в частности, знак $), само число, представляющее цену. Затем нам нужно получить информацию о скидках. Для этого извлекается коллекция элемента price, а из нее — атрибут discount. Затем на основе строки (значения атрибута discount) создается объект типа Integer. Этот метод показан в листинге 6.21.

Листинг 6.21. Метод extractPrice (Product.java)

 private void  extractPrice( Element productElement ) {
  String s = Util.extractTextFrom( "price", productElement );
  NumberFormat nf = NumberFormat.getCurrencyInstance();
  try {
  Number n = nf.parse( s );
  price = n.doubleValue();
  }
  catch( ParseException pe )
  {
  price = 0;
  }
  NodeList priceNodes =
productElement.getElementsByTagName( "price"  );
  if( priceNodes.getLength() > 0 ) {
  Element price = (Element)priceNodes.item(0);
  if( price != null ){
  String d = price.getAttribute( "discount" );
  if( d != null && !d.equals( "" ) ) {
  discount = new Integer( d );
  }
  }
  }
  }

Извлечь информацию из элемента quantity_in_stock сравнительно легко. Используется знакомый нам метод extractTextFrom для получения строки, которая затем анализируется и преобразуется к типу i nt, как показано в листинге 6.22.

Листинг 6.22. Метод extractQuantitylnStock (Product.java)

 private void  extractQuantityInStock( Element productElement ) {
  String s = Util.extractTextFrom
( "quantity_in_stock", productElement  );
  quantityInStock = Integer.parseInt( s );
  }

Элемент image может встречаться несколько раз, поэтому нам необходимо извлечь все дочерние элементы product с именем image, а затем использовать метод addlmage для добавления их к объекту product. Метод extractClips практически идентичен методу extractlmages, только он извлекает не изображения, а клипы. Оба эти метода приведены в листинге 6.23.

Листинг 6.23. Методы extractlmages и extractClips (Product.java)

 private void  extractImages( Element productElement ) {
  NodeList descNode =
productElement.getElementsByTagName( "image" );
  for( int i = 0; i < descNode.getLength(); i++ ) {
  addImage( new Image( (Element)descNode.item(i) ) );
  }
  }
  private void extractClips( Element productElement ) {
  NodeList descNode =
productElement.getElementsByTagName( "clip" );
  for( int i = 0; i < descNode.getLength(); i++ ) {
  addClip( new Clip( (Element)descNode.item(i) ) );
  }
  }

Последний метод, который мы рассмотрим в связи с извлечением данных, — это метод extractDate, приведенный в листинге 6.24. В нем используется метод DOM getElementsByTagName для извлечения коллекции узлов с именем onsale_date. В случае обнаружения такого узла именно он используется для создания нового объекта Date (поскольку существует один и только один элемент onsale_date).

Листинг 6.24. Метод extractDate (Product.java)

 private void  extractDate( Element productElement ) {
  NodeList date =
productElement.getElementsByTagName( "onsale_date"  );
  if( date.getLength() > 0 ) {
  onSaleDate = new DateTime( (Element)date.item( 0 ) );
  }
  }

Теперь нам нужны методы для извлечения и задания значений всех полей. Большинство из этих методов достаточно просты, они похожи на методы getName и setName из листинга 6.25. Для полей id, keywords, description, price, discount, quantitylnStock и onSaleDate существуют аналогичные методы. Мы не приводим их здесь из соображений экономии места, но все они включены в исходный код примеров, находящихся на прилагаемом к книге компакт-диске.

Листинг 6.25. Методы getName и setName (Product.java)

 public String  getName() {
  return name;
  }
 public void setName( String newName ) {
  name = newName;
  }

Поле authors относится к типу Vector, поэтому его нужно обрабатывать иными методами, чем приведенные выше методы getXxx и setXxx. У нас имеется метод addAuthor, который добавляет строку к вектору authors с помощью метода addElement. Чтобы удалить авторов из списка, имеется метод removeAll Authors, который удаляет всех авторов из элемента Product. Таким образом, можно заменять список авторов, для этого требуется удалить весь предыдущий список и добавить новый. Имеется также метод getAuthors, который возвращает перечень всех авторов. Для удаления, добавления и получения элементов artist, image и clip используются такие же методы, как и для элементов author, только все операции выполняются над соответствующими векторами. Эти методы показаны в листинге 6.26.

Листинг 6.26. Операции над элементами Author и Artist (Product.java)

 public void  addAuthor( String newAuthor ) {
  authors.addElement( newAuthor );
  }
 public void removeAllAuthors() {
  authors.removeAllElements();
  }
 public Enumeration getAuthors() {
  return authors.elements();
  }

Как и другие объекты связывания данных, объекты Product преобразуются в формат XML с помощью метода toXML, приведенного в листинге 6.27. Этот метод начинается с записи открывающего тега для элемента product. Атрибут id является обязательным, поэтому его нужно записывать всегда. Поскольку элемент keywords является необязательным, всегда нужно проверять его наличие, прежде чем записывать. Затем мы записываем дочерние элементы, поэтому нужно сделать отступ. Дочерний элемент name не вызывает никаких затруднений, так как это просто строка, то есть объект типа String. Вам нужно просто записать открывающий тег, саму строку и закрывающий тег. Элементы author и artist являются повторяющимися, поэтому нужно выполнить цикл по вектору Vector и записать каждый элемент по отдельности. Элементы description, price и quantity_in_stock представляют собой простые строки, так что они записываются тем же способом, что и элемент name. Вектор изображений содержит экземпляры класса Image, поэтому нужно сделать цикл по этому вектору и к каждому изображению применить метод toXML К объекту onSaleDate также применяется метод toXML, а вектор, содержащий элементы clip, обрабатывается так же, как и другие вектора, то есть с помощью цикла. К каждому из составляющих вектор объектов clip поочередно применяется метод toXML. Наконец, нужно установить отступ равным исходному и написать закрывающий тег элемента product.

Листинг 6.27. Преобразование Product в XML (Product.java)

 public void  toXML( XMLWriter writer ) throws IOException {
  writer.write( "<product id='" + id + "'" );
  if( keywords != null )
  writer.write( " keywords='" + keywords + "'" );
  writer.writeln( ">" );
  writer.indent();
  writer.writeln( "<name>" + name + "</name>"  );
  for( int i = 0; i < authors.size(); i++ ) {
  writer.writeln( "<author>" + authors.elementAt( i ) + "</author>"  );
  }
  for( int i = 0; i < artists.size(); i++ ) {
  writer.writeln( "<artist>" + artists.elementAt( i ) + "</artist>"  );
  }
  writer.writeln( "<description>" + description + "</description>"  );
  writer.writeln( "<price>" + price + "</price>"  );
  writer.writeln( "<quantity_in_stock>" +
quantityInStock + "</quantity_in_stock>"  );
  for( int i = 0; i < images.size(); i++ ) {
  Image image = (Image)images.elementAt( i );
  image.toXML( writer );
  }
  if( onSaleDate != null )
  onSaleDate.toXML( writer );
  for( int i = 0; i < clips.size(); i++ ) {
  Clip clip = (Clip)clips.elementAt( i );
  clip.toXML( writer );
  }
  writer.unindent();
  writer.writeln( "</product>" );
  }
}

Класс Image

Очередной класс, который мы рассмотрим, — это класс Image. Он основан на следующем определении DTD:

 <!ELEMENT image (caption?)>
 <!ATTLIST image format (gif|png|jpg) #REQUIRED
            width CDATA   #IMPLIED height   CDATA
                    #IMPLIED src    CDATA
                    #REQUIRED>

<!ELEMENT caption (paragraph)* >

Определенный здесь элемент caption аналогичен элементу description в объекте product. Поскольку структура элемента paragraph достаточно сложная, мы представим этот элемент в виде простой строки XML, которую пользователь может непосредственно редактировать. Атрибуты format и scr можно представить в виде полей типа String. Атрибуты height и width не являются обязательными, поэтому следует учесть, что иногда эти атрибуты будут отсутствовать. Для этого нужно использовать класс Integer, в котором имеется значение null, означающее отсутствие атрибута. Начало класса Image показано в листинге 6.28.

Листинг 6.28. Начало кода класса Image (Image.java)

 package com.XmlEcomBook.Chap06;
import java.io.*;
  import org.w3c.dom.*;
  public class Image {
 private String format;
  private Integer width;
  private Integer height;
  private String src;
  private String caption;

Конструкторы класса Image действуют по тому же принципу, что и конструкторы других классов связывания данных. Конструктор без аргументов создает объект Image, устанавливаемый по умолчанию; затем второй конструктор создает объект Image на основе объекта DOM Element. Аргументы scr и format извлекаются непосредственно из элемента image. Для атрибутов width и height используется служебный метод get Integer, так как существует вероятность того, что эти атрибуты не будут присутствовать, поскольку они являются необязательными. Метод getlnteger возвращает null в тех случаях, когда ему передается пустой объект. Наконец, если существует элемент caption, его содержимое извлекается как текст. Эти конструкторы показаны в листинге 6.29.

Листинг 6.29. Конструкторы класса Image (Image.java)

 public Image( Element imgElement) {
  format = imgElement.getAttribute( "format" );
  width = Util.getInteger( imgElement.getAttribute( "width" ) );
  height = Util.getInteger( imgElement.getAttribute( "height" ) );
  src = imgElement.getAttribute( "src" );
  NodeList captionList =
imgElement.getElementsByTagName( "caption"  );
  if( captionList.getLength() > 0 ) {
  Element captionElement = (Element)captionList.item( 0 );
  caption =
Util.extractMarkupAsText(captionElement.getChildNodes());
  }
  }

Каждое из полей класса Image имеет методы getXxx и setXxx. Эти простые методы задают или возвращают значение соответствующего поля. Из соображений экономии места мы не приводим их в книге, но на прилагаемом компакт-диске все эти методы присутствуют.

Метод toXML, который генерирует элемент image, показан в листинге 6.30. Открывающий тег содержит четыре атрибута — атрибуты format и scr присутствуют всегда, так как они являются обязательными, а для необязательных атрибутов width и height организована проверка их наличия. Перед тем как записать их, следует проверить, содержат ли они какое-либо значение (отличное от null).

Листинг 6.30. Преобразование Image в XML (Image.java)

 public void  toXML( XMLWriter writer ) throws IOException {
  writer.write( "<image " );
  writer.write( "format='" + format + "' " );
  if( width != null )
  writer.write( "width='" + width + "' " );
  if( height != null )
  writer.write( "height='" + height + "' " );
  writer.writeln( "src='" + src + "'>" );
  writer.indent();
  if( caption != null )
  writer.writeln( "<caption>" + caption + "</caption>"  );
  writer.unindent();
  writer.writeln( "</image>" );
  }
  }

Класс Clip

Элемент clip очень похож на элемент image, что видно из его DTD:

 <!ELEMENT clip (titie,description?)>
 <!ATTLIST clip format (mp3|mpeg|mov|rm)  #REQUIRED
              length  CDATA  #IMPLIED
             size    CDATA  #IMPLIED
             src     CDATA  #REQUIRED>
 <!ELEMENT title (#PCDATA)>

Все атрибуты элемента clip имеют тип String, как показано в листинге 6.31. Причина этого заключается в том, что атрибуты length и size имеют свободный формат и допускают включение единиц измерения. Например, атрибут size (размер) может содержать строку типа "1.1 Mb".

Листинг 6.31. Начало кода класса Clip (Clip.java)

 package com.XmlEcomBook.Chap06;
import org.w3c.dom.*;
  import java.io.*;
public class Clip extends Object {
 private String format;
  private String length;
  private String size;
  private String src;
  private String title;
  private String description;

Одному из конструкторов класса Clip не передается никаких аргументов; этот конструктор создает стандартный объект Clip. Другой конструирует объект Clip на основе объекта DOM Element, как показано в листинге 6.32. В первых четырех строках второго конструктора извлекаются атрибуты элемента clip. Элемент description обрабатывается тем же способом, что элемент description в product и элемент caption в Image, то есть извлекается простой текст XML и хранится в виде строки. Последняя строка второго конструктора извлекает значение элемента ti tl e также в виде строки.

Листинг 6.32. Конструкторы класса Clip (Clip.java)

 public Clip()  {
  }
 public Clip( Element clipElement ) {
  format = clipElement.getAttribute( "format" );
  length = clipElement.getAttribute( "length" );
  size = clipElement.getAttribute( "size" );
  src = clipElement.getAttribute( "src" );
  NodeList descList =
clipElement.getElementsByTagName( "description"  );
  if( descList.getLength() > 0 ) {
  Element descElement = (Element)descList.item( 0 );
  description =
Util.extractMarkupAsText(descElement.getChildNodes());
  }
  title = Util.extractTextFrom( "title", clipElement );
  }

В классе Clip также имеются методы setXxx и getXxx для каждого из его шести полей. Они включены в прилагаемый к книге компакт-диск, но здесь не приводятся. Метод toXML аналогичен тем, которые мы изучали ранее для других классов. Как и у других объектов данных, у класса Clip имеется метод для преобразования объекта в код XML. Этот метод показан в листинге 6.33.

Листинг 6.33. Преобразование класса Clip в XML (Clip.java)

 public void  toXML( XMLWriter writer ) throws IOException {
  writer.write( "<clip " );
  writer.write( "format='" + format + "' " );
  if( length != null )
  writer.write( "length='" + length + "' " );
  if( size != null )
  writer.write( "size='" + size + "' " );
  writer.writeln( "src='" + src + "'>" );
  writer.indent();
  writer.writeln( "<title>" + title + "</title>"  );
  if( description != null )
  writer.writeln( "<description>" + description + "</description>"  );
  writer.unindent();
  writer.writeln( "</clip>" );
  }
}

Класс DateTime

Класс DateTime используется для представления даты. Он основан на следующем фрагменте DTD:

<!ENTITY % date_time "(day_of_week?.month?,day_of_month?,
         year?,(hour,minute,seconds?)?)">
<!ELEMENT day_of_week  (#PCDATA)>
<!ELEMENT month     (#PCDATA)>
<!ELEMENT day_of_month (#PCOATA)>
<!ELEMENT year     (#PCDATA)>
<!ELEMENT hour     (#PCDATA)>
<!ELEMENT minute    (#PCDATA)>
<!ELEMENT seconds    (#PCDATA)>

Параметрическая сущность datejtime используется для представления даты и времени. В DTD каталога эта сущность задействуется только в элементе onsale_date, но, если расширить DTD, ее можно использовать и в других местах. В классе DateTime имеется свое поле для каждого из элементов, определенных в DTD. Также в нем имеются два конструктора, один из которых не имеет аргументов, а другому в качестве аргумента передается элемент, подлежащий анализу. Они показаны в листинге 6.34.

Листинг 6.34. Начало кода класса DateTime (DateTime.java)

package com.XmlEcomBook.Chap06;
import org.w3c.dom.Element;
  import java.util.StringTokenizer;
  import java.io.IOException;
  import java.io.OutputStream;
public class DateTime extends Object {
 private String dayOfWeek = null;
  private Integer month = null;
  private Integer dayOfMonth = null;
  private Integer year = null;
  private Integer hour = null;
  private Integer minute = null;
  private Integer seconds = null;
 public DateTime() {
  }
 public DateTime(Element dateElement) {
  dayOfWeek = Util.extractTextFrom( "day_of_week",
dateElement );
  month = Util.extractIntFrom( "month", dateElement );
  dayOfMonth = Util.extractIntFrom
( "day_of_month", dateElement );
  year = Util.extractIntFrom( "year", dateElement );
  hour = Util.extractIntFrom( "hour", dateElement );
  minute = Util.extractIntFrom( "minute", dateElement );
  seconds = Util.extractIntFrom( "seconds", dateElement );
  }

Класс DateTime анализирует дату и время, полученные из форм HTML. Анализ осуществляется с помощью метода fromString, приведенного в листинге 6.36. Этот метод использует объект StringTokerrizer для разделения строки на фрагменты — лексемы. Каждая лексема, выделенная из строки объектом String- Tokenizer, проходит проверку. Если лексема содержит в себе строку day (день), то это день недели, и соответствующее значение сохраняется в поле dayOfWeek (день недели). Так как часть строки, содержащая дату, разделена символами дефиса (-), то при обнаружении такого символа из строки выделяются лексемы, идентифицирующие месяц, день месяца и год. Если в строке обнаружен символ двоеточия (:), то это указывает, что данная часть строки содержит значение времени суток, поэтому из строки выделяются часы, минуты и секунды.

Листинг 6.35. Метод fromString (DateTime.java)

 public void  fromString( String newDate ) {
  StringTokenizer tokenizer = new StringTokenizer( newDate, " " );
  while( tokenizer.hasMoreTokens() ) {
  String next = tokenizer.nextToken();
  if( next.indexOf( "day" ) > 0 ) {
  dayOfWeek = next;
  }
  if( next.indexOf( '-' ) > 0 ) {
  int first = next.indexOf( '-' );
  int second = next.indexOf( '-', first + 1 );
  month = new Integer( next.substring( 0, first ) );
  dayOfMonth = new Integer( next.substring( first + 1, second ) );
  year = new Integer( next.substring( second + 1, next.length() ) );
  }
  if( next.indexOf( ':' ) > 0 ) {
  int first = next.indexOf( ':' );
  int second = next.indexOf( ':', first + 1 );
  hour = new Integer( next.substring( 0, first ) );
  minute = new Integer( next.substring( first + 1, second ) );
  seconds = new Integer( next.substring( second + 1,
next.length() ) );
  }
  }
  }

Последний метод этого класса, toXML, показан в листинге 6.36. Он аналогичен другим методам toXML, и записывает по отдельности значение каждого поля, снабжая его соответствующими тегами.

Листинг 6.36. Метод toXML (DateTime.java)

 public void  toXML( XMLWriter writer ) throws IOException {
  writer.writeln( "<onsale_date>" );
  writer.indent();
  if( dayOfWeek != null )
  writer.writeln( "<day_of_week>" + dayOfWeek +
"</day_of_week>"  );
  if( month != null )
  writer.writeln( "<month>" + month + "</month>"  );
  if( dayOfMonth != null )
  writer.writeln( "<day_of_month>" + dayOfMonth +
"</day_of_month>"  );
  if( year != null )
  writer.writeln( "<year>" + year + "</year>"  );
  if( hour != null )
  writer.writeln( "<hour>" + hour + "</hour>"  );
  if( minute != null )
  writer.writeln( "<minute>" + minute + "</minute>"  );
  if( seconds != null )
  writer.writeln( "<seconds>" + seconds + "</seconds>"  );
  writer.unindent();
  writer.writeln( "</onsale_date>" );
  }
  }

Класс Util

В классе Util имеется несколько удобных служебных методов, которые вызываются как из объекта данных, так и из классов представления. Все они являются открытыми и статическими, поэтому их можно вызывать откуда угодно. Первые два метода этого класса, приведенные в листинге 6.37, используются для извлечения информации из данных в формате XML. Метод extractTextFrom ищет конкретный элемент, и если находит, извлекает его текстовое содержимое и возвращает. Следующий метод, extractlntFrom, использует первый метод для извлечения текста из элемента, а затем преобразует этот текст в формат Integer, после чего возвращает полученное значение.

Листинг 6.37. Начало кода класса Util и методы extractTextFrom и extractlntFrom (Util.java)

 package com.XmlEcomBook.Chap06;
import org.w3c.dom.*;
  import java.io.*;
public class Util {
 static public String extractTextFrom
( String childElementName, Element element  ) {
  NodeList nameList = element.getElementsByTagName
( childElementName );
  if( nameList.getLength() < 1 ) {
  return null;
  }
  Text nameText = (Text)nameList.item(0).getFirstChild();
  return nameText.getData();
  }
 static public Integer extractIntFrom
( String childElementName, Element element  ) {
  String s = Util.extractTextFrom( childElementName, element );
  if( s == null || s.equals( "" ) ) {
  return null;
  }
  return new Integer( s );
  }

Следующий метод также помогает работать с данными XML. Этому методу, приведенному в листинге 6.38, передается объект NodeList (список узлов), а возвращается он уже в виде текста. Основная обработка происходит в цикле for, где конструируется строка. Для каждого узла из списка узлов к строке добавляется символ открывающей угловой скобки (<) и имя тега. Затем в открывающий тег добавляется каждый из атрибутов этого узла, и тег завершается символом закрывающей угловой скобки (>). После этого рекурсивным образом вызывается метод extractMarkupAsText, чтобы преобразовать все дочерние элементы текущего узла к текстовому формату. Если узел, обрабатываемый в цикле for, является текстовым, то он непосредственно добавляется к строке.

Листинг 6.38. Метод extractMarkupAsText (Util.java)

 static public  String extractMarkupAsText( NodeList nodeList ) {
  //recursively extract
  String text = "";
  if( nodeList != null ) {
  for( int i = 0; i < nodeList.getLength(); i++ ) {
  Node node = nodeList.item(i);
  if( node instanceof Element ) {
  Element el = (Element)node;
  text += "<" + el.getTagName();
  NamedNodeMap attList = el.getAttributes();
  int length = attList.getLength();
  for( int j = 0; j < attList.getLength(); j++ ) {
  Attr att = (Attr)attList.item( j );
  text += " " + att.getName() + "='" + att.getValue() + "'";
  }
  text += ">";
  text += extractMarkupAsText( el.getChildNodes() );
  text += "</" + el.getTagName() + ">";
  }
  if( node instanceof Text ) {
  text += ((Text)node).getData();
  }
  }
  }
  return text;
  }

Следующие три метода, показанные в листинге 6.39, используются для отображения различных типов данных. Пустые объекты типа String, Integer и Float отображаются как null, а мы не хотели бы, чтобы пользователям приходилось видеть это на экране. Поэтому мы используем приведенные ниже вспомогательные методы, которые заменяют nul 1 на пустую строку.

Листинг 6.39. Методы notNull (Util.java)

 static public  String notNull( String s ) {
  if( s == null ) {
  return "";
  }
  return s;
  }
 static public String notNull( Integer i ) {
  if( i == null ) {
  return "";
  }
  return i.toString();
  }
 static public String notNull( Float f ) {
  if( f == null ) {
  return "";
  }
  return f.toString();
  }

Следующий ряд методов, показанный в листинге 6.40, используется для преобразования строки, введенной пользователем (то есть объекта String), в некоторое значение. Мы хотим быть уверены, что, если пользователь оставил какую- либо форму незаполненной, мы получим значение null.

Листинг 6.40. Преобразование строки в значение (Util.java)

 static public  int getInt( String s ) {
  if( s == null || s.equals( "" ) ) {
  return 0;
  }
  return Integer.parseInt( s );
  }
 static public Integer getInteger( String s ) {
  if( s == null || s.equals( "" ) ) {
  return null;
  }
  return new Integer( s );
  }
 static public float getFloat( String s ) {
  if( s == null || s.equals( "" ) ) {
  return 0.0f;
  }
  return Float.parseFloat( s );
  }
 static public double getDouble( String s ) {
  if( s == null || s.equals( "" ) ) {
  return 0.0;
  }
  return Double.parseDouble( s );
  }
}

Код для представления информации пользователю

Теперь нам нужен код для представления пользователю всей информации, содержащейся в классах данных. Необходимо также обеспечить пользователю возможность создавать, редактировать и удалять элементы каталога. Это делается с помощью кода HTML, JSP-страниц и сервлетов. JSP-страницы и сервлеты генерируют код HTML, который отображается в браузере пользователя. Для работы с каталогом пользователь выбирает HTML-страницу, которая обеспечивает возможности создания, редактирования и удаления элементов каталога. На этой странице есть ссылки на сервлет, вызывающий JSP-страницу, которая представляет редактируемую форму HTML, предназначенную для того, чтобы пользователь вносил в нее новую информацию о товарах или изменял старую.

Всю оставшуюся часть этой главы мы посвятим изучению кода HTML, JSP- страниц и сервлетов, которые обеспечивают эту функциональность.

Главная HTML-страница

На главной HTML-странице пользователю предлагаются три возможности: добавить товар, редактировать товар или удалить товар. Существует также возможность отменить все изменения, сделанные пользователем в текущем сеансе, или сохранить эти изменения. Для всех этих операций вызывается один и тот же сервлет, но всякий раз с различными значениями скрытого поля с именем operation. В начале HTML-страницы (листинг 6.41) содержится ее заголовок и открывающий тег элемента body.

Листинг 6.41. Начало HTML-страницы (main.html)

 <html>
 <head><title>Catalog Upkeep</title></head>

<body>

Каждая из пяти операций, предложенных пользователю, представлена специальной формой на странице. Первую операцию, Add Product (Добавить товар), иллюстрирует листинг 6.42. Для нового товара требуется уникальный идентификатор, поэтому нужно проверить все уже существующие идентификаторы, чтобы не получилось повторения. Также требуется указать серию товаров, к которой принадлежит новый товар. Атрибут action элемента form содержит имя сервлета, который вызывается, когда форма заполнена и отправлена на сервер. В данном случае мы вызываем сервлет Main. В следующей строке указывается заголовок для формы Add Product.

Первый элемент этой формы является скрытым. Это значит, что пользователь не увидит его на экране своего компьютера. Он просто передается сервлету вместе со всеми остальными введенными данными. Этот элемент позволяет сервлету определить, что именно хотел сделать пользователь. Следующие две строки предназначены для того, чтобы пользователь мог ввести идентификатор товара и указать серию товаров, к которой он будет принадлежать. Последняя строка элемента form нужна для того, чтобы добавить кнопку, с помощью которой пользователь сможет отправить введенные данные на сервер.

Листинг 6.42. Форма для добавления товара (main.html)

 <form action="servlet/Main">
 <h3>Add Product:</h3>
 <input type="hidden" name="operation" value="add" />
 Product ID:<input name="productid"  />
 Product Line:<input name="productline"  />
 <input type="submit" value="Add"/> </form>

Формы Delete Product (Удалить товар) и Edit Product (Редактировать товар) аналогичны форме Add Product, но в них требуется ввести только идентификатор товара. Для визуального разграничения этих форм между ними добавляется горизонтальная линия. Код для форм Delete Product и Edit Product приведен в листинге 6.43.

Листинг 6.43. Формы Delete Product и Edit Product (main.html)

 <hr />
 <form action="servlet/Hain">
 <h3>Delete Product:</h3>
 <input type="hidden" name="operation" value="delete" />
 Product ID:<input name="productid" />
 <input type="submit" value="Delete"/>

</form>

<hr />
<form action="servlet/Main">
<h3>Edit Product:</h3>
 <input type="hidden" name="operation" value="edit" />

Product ID:<input name="productid"  />

<input type="subrait" value="Edit"/>

</form>

Последние две операции — отмена и сохранение всех изменений, внесенных пользователем в течение данного сеанса, — иллюстрирует листинг 6.44. Сервлет и JSP-страница загружают данные XML из файла, когда пользователь начинает работу с каталогом. Форма Cancel All Changes (Отменить все изменения) отменяет все изменения, которые были сделаны с момента загрузки файла. Форма Save All Changes (Отменить все изменения) записывает в файл XML все изменения, сделанные пользователем. Визуально обе эти формы представляют собой просто кнопки, на которых может щелкнуть пользователь. В каждой форме имеется скрытый элемент input, который сообщает сервлету, какую из этих операций выбрал пользователь.

Листинг 6.44. Формы Cancel All Changes и Save All Changes (main.html)

 <hr />
 <form action="servlet/Main">
 <input type="hidden" name="operation" value="refresh" />

<input type="submit" value="Cancel All Changes"/>
 </form>
 <form action="servlet/Main">
 <input type="hidden" name="operation" value="save" />

<input type="submit" value="Save All Changes"/>
 </form>
 </body> </html>

Класс Main сервлета

Класс Main сервлета, в котором обрабатывается информация, введенная пользователем на главной HTML-странице (main.html), показан в листинге 6.45. Центральной точкой входа в этот класс сервлета является метод doGet. После задания исходных значений некоторых переменных этот метод пытается получить объект Session из запроса. Если этого объекта еще не существует, нужно создать новый объект Session. Затем создается новый объект Catalog на основе файла catalog.xml. Этот объект Catalog присоединяется к сеансу с помощью метода setAttribute. Если же обнаружен существующий объект Session, объект catalog можно получить как атрибут уже существующего сеанса.

Листинг 6.45. Начало кода класса Main и метод doGet (Main.java)

 import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import com.XmlEcomBook.Chap06.*;
public class Main extends HttpServlet {
  static private final String FILE_NAME = "catalog.xml";
 public void doGet(HttpServletRequest req,
  HttpServletResponse res) throws IOException,
ServletException {
  // Access the output stream.
  PrintWriter out = res.getWriter();
  res.setContentType("text/html");
  HttpSession session = req.getSession(false);
  Catalog catalog = null;
 if( session == null ) {
  //There is no session, create a new one
  session = req.getSession( true );
  catalog = new Catalog( FILE_NAME );
  session.setAttribute( "catalog", catalog );
  } else {
  catalog = (Catalog)session.getAttribute( "catalog" );
  }

После этого метод doGet переходит к выполнению следующей задачи — извлечению всех значений, которые пользователь ввел в форму HTML, как показано в верхней части листинга 6.46. Эти значения извлекаются из объекта HttpServl etRequest с помощью метода getParameter. Затем эти значения проверяются на наличие ошибок. В первую очередь, если выбрана одна из основных операций (добавление, удаление или редактирование товара), проверяется, указан ли идентификатор товара. Затем из каталога извлекается элемент (товар) с этим идентификатором. Если выполняется операция по удалению или редактированию элемента, требуется проверить, что этот элемент действительно присутствует в каталоге, иначе выдается сообщение об ошибке. Если выполняется операция по добавлению товара в каталог, то нужно проверить, нет ли уже в каталоге товара с таким идентификатором, поскольку каждый идентификатор должен быть уникален. Также при добавлении товара в каталог нужно убедиться, что указанная для товара серия действительно присутствует в каталоге. Нужно проверить состояние булевой переменной error (признак ошибки); если ее значение установлено равным true, то нужно отобразить страницу с сообщением об ошибке. Метод, который отображает эту страницу, будет рассмотрен далее в этом разделе.

Листинг 6.46. Проверка ошибок во введенных параметрах (Main.java)

 String operation  = req.getParameter("operation");
  String productID = req.getParameter("productid");
  String productLine = req.getParameter("productline");
 boolean error = false;
  String errorMsg = "Unkown Error";
  if( operation.equals( "delete" )
  || operation.equals( "edit" )
  || operation.equals( "add" ) ) {
  if( productID == null || productID.equals("") ) {
  error = true;
  errorMsg = "You must select a product ID with the " +
operation + " operation.";
  } else {
  Product product = catalog.getProduct( productID );
  if( operation.equals( "edit" ) || operation.equals( "delete"  ) ) {
  if( product == null ) {
  error = true;
  errorMsg = "Invalid product ID:" + productID;
  }
  }
  if( operation.equals( "add" ) ) {
  if( product != null ) {
  error = true;
  errorMsg = "Cannot add, product id " + productID +
" already  exists.";
  }
  ProductLine pl = catalog.getProductLine( productLine );
  if( pl == null ) {
  error = true;
  errorMsg = "No product line " + productLine + " exists.";
  }
  }
  }
  }
 if( error ) {
  outputPage( out, "Error", errorMsg );
  return;
  }

После того как вы убедились, что все параметры введены правильно, можно приступить к их фактической обработке, как показано в листинге 6.47. У вас имеется последовательность инструкций if для определения, какие действия следует выполнять в том или ином случае. Для добавления и редактирования данных о товаре используется одна и та же JSP-страница. Чтобы вызвать из сервлета JSP- страницу, вызывается метод getRequestDi spatcher объекта класса ServletContext и задается URL-адрес относительно корневой папки сервлета (например, Edit.jsp). После получения объекта класса RequestDi spatcher вызывается его метод forward, которому в качестве параметров передаются объекты классов HttpServletRequest и HttpServl etResponse, то есть текущие запрос (req) и ответ (res). Это позволяет передать JSP-странице все текущие параметры. Операция по удалению товара аналогичным образом вызывает другую JSP-страницу. Операция по отмене всех изменений выполняется непосредственно в методе doGet. Для этого просто вызывается метод invalidate объекта session. Этот метод делает текущий сеанс недействительным и прекращает все связи с объектами. Последняя команда, которая сохраняет все сделанные в течение сеанса изменения, также выполняется непосредственно в методе doGet. Здесь создается новый объект класса XMLWriter, использующий файл catalog.xml. Туда вписываются необходимые строки, содержащие объявление XML и объявление типа документа. Затем используется метод toXML объекта catal og для того, чтобы записать данные в формате XML в этот файл.

Листинг 6.47. Обработка различных операций (Main.java)

 if( operation.equals(  "add" ) || operation.equals( "edit" ) ) {
  getServletContext().getRequestDispatcher
("/Edit.jsp").forward(req,  res);
  }
  if( operation.equals( "delete" ) ) {
  getServletContext().
  getRequestDispatcher("/Delete.jsp").forward(req, res);
  }
  if( operation.equals( "refresh" ) ) {
  session.invalidate();
  outputPage( out, "Session Cancelled",
"The session has been canelled"  );
  return;
  }
  if( operation.equals( "save" ) ) {
  try {
  FileOutputStream outFile =
new FileOutputStream( FILE_NAME );
  XMLWriter writer = new XMLWriter( outFile );
  writer.writeln( "<?xml version='1.0' standalone='no' ?>" );
  writer.writeln( "<!DOCTYPE catalog SYSTEM 'catalog.dtd'>" );
  catalog.toXML( writer );
  }
  catch( IOException e ) {
  outputPage( out, "Error", "I/O Exception writing XML file"  );
  return;
  }
  outputPage( out, "Changes saved",
"The changes have been saved"  );
  }
  }

В сервлете Main имеется метод для создания и отображения простой HTML- страницы. Этот метод приведен в листинге 6.48. Ему передается объект PrintWriter, заголовок и текст, который будет отображен на странице. На этой странице также будет расположена ссылка на главную HTML-страницу.

Листинг 6.48. Отображение страницы (Main.java)

 private void  outputPage( PrintWriter out, String title, String text )
 {
  out.println("<html><head><title>" + title );
  out.println("</title></head><body>");
  out.println("<p>" + text + "</p>");
  out.println("<a href='/main.html'>Return to main page.</a>");
  out.println("</body></html>");
  }
  }

JSP-страница Delete

Страница Del ete, представленная в листинге 6.49, устроена довольно просто. Вы получаете идентификатор товара из встроенного объекта request. Это тот же самый объект, который был адресован JSP-странице сервлетом Main. Затем вы получаете объект catal од из текущего сеанса. Нужно просто удалить товар из каталога и отобразить HTML-страницу с сообщением о том, что удаление прошло успешно.

Листинг 6.49. JSP-страница Delete (Delete.jsp)

 <%@ page  import="com.XmlEcomBook.Chap06.*" %>
  <%
  String pid = request.getParameter( "productid" );
  Catalog catalog = (Catalog)session.getValue( "catalog" );
  catalog.deleteProduct( pid );
  %>
<html>
  <head>
  <title>Delete</title>
  </head>
  <body>
  <p><b>Product <%= pid %> deleted.<b></p>
  <a href='/main.html'>Return to main page.<a>
  </body>
  </html>

JSP-страница Edit

Страница Edit гораздо длиннее и сложнее, чем страница Delete. Она должна генерировать форму, в которой содержатся все данные о товаре в таком виде, который позволяет пользователю редактировать эти данные. В верхней части страницы содержится несколько элементов сценария. Как видно из листинга 6.50, после импорта необходимых пакетов из объекта request извлекаются требуемые параметры. Это те же самые параметры, которые были введены пользователем и затем переданы JSP-странице сервлетом Main.

Эта JSP-страница выполняет команды добавления и редактирования сервле- та Main, поэтому мы должны проверить параметр operation, указывающий, какая из двух операций будет выполняться. Если значение этого параметра равно edit, то мы знаем, что нужно извлечь указанный элемент из каталога. Если значение его не равно edit, очевидно, оно равно add; тогда нужно создать новый элемент каталога и установить его идентификатор равным тому значению, которое введено пользователем.

Листинг 6.50. Начало JSP-страницы Edit (Edit.jsp)

 <%@ page  import="com.XmlEcomBook.Chap06.*" %>
  <%@ page import="java.util.*" %>
  <%
  String pid = request.getParameter( "productid" );
  String operation = request.getParameter( "operation" );
  String productLine = request.getParameter( "productline" );
  Catalog catalog = (Catalog)session.getValue( "catalog" );
  Product product = null;
  String name = "";
  if( operation.equals( "edit" ) ) {
  product = catalog.getProduct( pid );
  name = "Edit";
  }
  else { //it’s an “add” operation
  product = new Product();
  product.setId( pid );
  name = "Add";
  }
  %>

Далее начинается фактическое формирование HTML-страницы, как показано в листинге 6.51. Сначала нужно установить заголовок страницы в тегах <head> и <hl>, после чего следует элемент form, содержащий остальную часть страницы. Элемент form вызывает сервлет с именем UpdateProduct, который мы обсудим в следующем разделе. Первые два элемента input этой формы — скрытые поля, содержащие информацию, необходимую для сервлета UpdateProduct. Это введенные пользователем идентификатор товара и серия товаров, к которой он относится. Название товара, серия и идентификатор вставляются в выходные данные JSP-страницы с помощью выражения (элемента сценария), которое начинается с символов <*=.

Листинг 6.51. Начало кода элементов HTML-страницы (Edit.jsp)

 <html>
  <head><title><%= name %></title></head>
  <body>
  <h1><%= name %> Product</h1>
  <form action="/servlet/UpdateProduct">
  <input name="productline" type="hidden" value="<%=
request.getParameter( "productline" ) %>" />
  <input name="id" type="hidden" value="<%=
product.getId()  %>" />

Далее начинается элемент table, который помогает выровнять строки, входящие в форму, как показано в листинге 6.52. Первый элемент, который вы отображаете, — это идентификатор товара. Он просто выводится в первой строке таблицы table. Название товара отображается в той же строке таблицы, что и его идентификатор, но в виде отдельного текстового поля, состоящего из одной строки, — этот тип для объекта input задается по умолчанию. Служебный метод notNull используется потому, что метод getName может выдать значение null, а мы хотели бы, чтобы в таких случаях отображалась пустая строка, а не строка "null". В следующей строке таблицы отображаются ключевые слова, характеризующие данный товар. Размер этого текстового поля установлен равным 40, потому что предполагается, что вводимая строка может быть несколько длиннее

Листинг 6.52. Отображение названия товара и ключевых слов (Edit.jsp)

 <table>
  <tr>
  <td>Product ID</td>
  <td><%= product.getId() %></td>
  <td>Name</td>
  <td><input name="name" value="<%=
Util.notNull(product.getName())  %>" /></td>
  </tr>
  <tr>
  <td>Keywords</td>
  <td colspan="3"><input size ="40" name="keywords"
value="<%= Util.notNull(product.getKeywords()) %>" /></td>
  </tr>

Далее, у нас имеется два скриптлета, представленные в листинге 6.53, которые выводят для товара элементы Author и Artist. Первый скриптлет осуществляет цикл по всем элементам Author для данного товара и использует метод outputAuthor для их отображения. Для идентификации каждого автора (элемента Author) счетчик цикла преобразуется в строку путем добавления его значения к пустой строке. Затем формируется пустой элемент Author с идентификатором New (новый). Это позволяет пользователю дополнить список авторов, введя новый элемент Author. Второй скриптлет выполняет такие же действия для элементов Arti st. Код методов outputAuthors и outputArti sts мы рассмотрим ниже в этом разделе.

Листинг 6.53. Отображение элементов Author и Artist (Edit.jsp)

 <% Enumeration  authors = product.getAuthors();
  for( int i = 0; authors.hasMoreElements(); i++ ) {
  out.print( outputAuthor( "" + i, (String)authors.nextElement() ) );
  }
  out.print( outputAuthor( "New", "" ) );
  %>
  <% Enumeration artists = product.getArtists();
  for( int i = 0; artists.hasMoreElements(); i++ ) {
  out.print( outputArtist( "" + i, (String)artists.nextElement() ) );
  }
  out.print( outputArtist( "New", "" ) );
  %>

Следующая часть JSP-страницы выводит значения цены, количество экземпляров данного товара в магазине и дату начала продаж, а также описание товара, как показано в листинге 6.54. Каждому из этих элементов отводится своя строка в таблице, причем каждая строка содержит два элемента данных — идентификатор данного поля ввода и само поле ввода. Поскольку описание может содержать большой текстовый фрагмент, для него используется элемент textarea (текстовое поле с несколькими строками) вместо одиночной строки элемента input.

Листинг 6.54. Отображение цены, количества экземпляров, даты начала продаж и описания товара (Edit.jsp)

 <tr><td>Price</td>
  <td><input name="price" value="<%=
product.getPrice()  %>" />
</td>
  <td>Discount</td>
  <td><input name="discount" value="<%=
Util.notNull(product.getDiscount())  %>" /></td>
  </tr>
  <tr><td>Quantity in Stock</td>
  <td><input name="quantity" value="<%=
product.getQuantityInStock()  %>" /></td>
  </tr>
  <tr><td>On Sale Date</td>
  <td colspan="3"><input name='onSaleDate' value='<%=
product.getOnSaleDate()  %>' />
  (mm-dd-yyyy hh:mm:ss)</td>
  </tr>
  <tr><td>Description</td>
  <td colspan="3">
  <textarea rows="5" cols="40" name="description"><%=
Util.notNull(product.getDescription()) %>
  </textarea>
  </td>
  </tr>
  </table>

Для отображения элементов Image и Clip, относящихся к данному товару, за- действуется новая таблица, задаваемая в листинге 6.55. Скриптлеты в данном случае аналогичны тем, которые использовались для отображения элементов Author и Artist. После отображения таблицы в нижней части страницы появляется кнопка Submit (Принять), на которой после завершения редактирования пользователь может щелкнуть для отправки содержимого формы на сервер.

Листинг 6.55. Отображение элементов Image, Clip и кнопки Submit (Edit.jsp)

 <tr><td>Price</td>
  <td><input name="price" value="<%=
product.getPrice()  %>" />
</td>
  <td>Discount</td>
  <td><input name="discount" value="<%=
Util.notNull(product.getDiscount())  %>" /></td>
  </tr>
  <tr><td>Quantity in Stock</td>
  <td><input name="quantity" value="<%=
product.getQuantityInStock()  %>" /></td>
  </tr>
  <tr><td>On Sale Date</td>
  <td colspan="3"><input name='onSaleDate' value='<%=
product.getOnSaleDate()  %>' />
  (mm-dd-yyyy hh:mm:ss)</td>
  </tr>
  <tr><td>Description</td>
  <td colspan="3">
  <textarea rows="5" cols="40" name="description"><%=
Util.notNull(product.getDescription()) %>
  </textarea>
  </td>
  </tr>
  </table>

В нижней части этой JSP-страницы определено несколько вспомогательных методов. Первый из них называется output Image. Он призван отобразить элемент формы для объекта Image в нужных местах, которых может быть несколько. Этот метод не входит ни в один стандартный класс Java, вызываемый из JSP-страницы, так как он сильно связан с логикой представления. Полезно иметь отдельный класс для определения методов, которые выполняют в JSP-странице какие- либо задачи, не связанные с логикой представления; но в данном случае метод предназначен для получения кода HTML. Если вы поместите этот метод в отдельный класс, получится, что одна и та же страница обрабатывается в двух местах, в результате усложнится обслуживание кода. Лучше весь код, отвечающий за представление страницы, держать в одном месте.

Методу outputlmage, приведенному в листинге 6.56, передаются два параметра: строка, служащая идентификатором изображения, и сам объект Image. Имя поля input будет составлено из строки image (изображение) и переданного идентификатора. Таким образом вы получаете уникальное название для каждого изображения. Это название используется как для отображения на странице для пользователя, так и в качестве значения атрибута name объекта input. Каждый из атрибутов и элементов объекта Image отображается в отдельном текстовом поле, которое пользователь может заполнять.

Листинг 6.56. Вывод объекта Image (Edit.jsp)

 <%!
  private String outputImage(String i, Image image) {
  String s;
  s = "<tr><td>" + i + ")</td>";
  s += "<td>Format</td>";
  s += "<td><input name='img" + i + "-format' value='"  +
Util.notNull(image.getFormat()) + "' /></td>" ;
  s += "<td></td><td>Source File</td>";
  s += "<td><input name='img" + i + "-src' value='"  +
Util.notNull(image.getSrc()) + "' /></td>" ;
  s += "</tr>";
  s += "<tr>";
  s += "<td></td><td>Height</td>";
  s += "<td><input name='img" + i + "-height' value='"  +
Util.notNull(image.getHeight()) + "' /></td>";
  s += "<td></td><td>Width</td>";
  s += "<td><input name='img" + i + "-width' value='"  +
Util.notNull(image.getWidth()) + "' /></td>";
  s += "</tr>";
  s += "<tr>";
  s += "<td></td><td>Caption</td>";
  s += "<td colspan='4'><textarea rows='5' cols='40' name='img"
  + i + "-caption'>" + Util.notNull(image.getCaption()) + "
</textarea></td>";
  s += "</tr>\n";
  return s;
  }
  %>

Вывод объектов Author, Artist и Clip очень похож на вывод объектов Image. Каждый из элементов и атрибутов отображается в отдельной строке таблицы. Строка, которая идентифицирует конкретный объект, также используется двояким образом: для отображения в поле ввода и как значение атрибута name объекта input. Этот метод для объекта Clip показан в листинге 6.57.

Листинг 6.57. Отображение объекта Clip (Edit.jsp)

 <%!
  private String outputClip( String i, Clip clip )
  {
  String s;
  s = "<tr><td>" + i + ")</td>";
  s += "<td>Format</td>";
  s += "<td><input name='clip" + i + "-format' value='"  +
Util.notNull(clip.getFormat()) + "' /></td>" ;
  s += "<td></td><td>Source File</td>";
  s += "<td><input name='clip" + i + "-src' value='"  +
Util.notNull(clip.getSrc()) + "' /></td>" ;
  s += "</tr>";
  s += "<tr>";
  s += "<td></td><td>Title</td>";
  s += "<td colspan='3'><input name='clip" + i + "-title'  value='" +
Util.notNull(clip.getTitle()) + "' /></td>";
  s += "</tr>";
  s += "<tr>";
  s += "<td></td><td>Length</td>";
  s += "<td><input name='clip" + i + "-length' value='"  +
Util.notNull(clip.getLength()) + "' /></td>" ;
  s += "<td></td><td>Size</td>";
  s += "<td><input name='clip" + i + "-size' value='"  +
Util.notNull(clip.getSize()) + "' /></td>";
  s += "</tr>";
  s += "<tr>";
  s += "<td></td><td>Description</td>";
  s += "<td colspan='4'><textarea rows='5' cols='40' name='clip"
  + i + "-description'>" + Util.notNull(clip.getDescription()) +
"</textarea></td>" ;
  s += "</tr>";
  return s;
  }
private String outputAuthor( String i, String author ) {
  String s = "<tr><td>Author</td>";
  s += "<td><input name='author" + i + "' value='"  + author + "' />
</td></tr>";
  return s;
  }
private String outputArtist( String i, String artist ) {
  String s = "<tr><td>Artist</td>";
  s += "<td><input name='artist" + i + "' value='"  + artist + "' />
</td></tr>";
  return s;
  }
  %>

Сервлет UpdateProduct

Сервлет UpdateProduct вызывается из JSP-страницы Edit, когда пользователь заканчивает ввод в форму информации о товаре. Задача этого сервлета заключается в том, чтобы собрать данные из параметров запроса (объекта request) и обновить объект связывания данных Product. После некоторой стандартной инициализации в методе doGet из текущего объекта HttpServletRequest извлекается объект Session. Затем из него вы получаете объект Catalog, с которым и работаете в данном сеансе. Из каталога вы получаете объект Product, снабженный правильным идентификатором. Все это иллюстрирует листинг 6.58.

Листинг 6.58. Начало кода сервлета UpdateProduct (UpdateProduct.java)

 import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
import com.XmlEcomBook.Chap06.*;
public class UpdateProduct extends HttpServlet {
 public void doGet(HttpServletRequest req,
  HttpServletResponse res) throws IOException,
ServletException
{
  res.setContentType("text/html");
  PrintWriter out = res.getWriter();
 HttpSession session = req.getSession();
  Catalog catalog = (Catalog)session.getAttribute( "catalog" );
  String id = req.getParameter( "id" );
  Product product = catalog.getProduct( id );

Далее, как показано в листинге 6.59, проверяется, не является ли элемент product пустым, то есть не равен ли он null. Если это так, то мы делаем вывод, что была выполнена операция Add Product, поэтому требуется добавить новый товар в каталог, то есть создать новый объект Product с правильным значением идентификатора и добавить его к соответствующей серии товаров. Затем из запроса считываются такие параметры, как имя и ключевые слова, и в объект Product вносятся обновленные значения этих параметров.

Листинг 6.59. Создание нового объекта product, обновление ключевых слов и имени (UpdateProduct.java)

 if( product  == null ) {
  //new product
  product = new Product();
  product.setId( id );
  String productLineString = req.getParameter( "productline" );
  ProductLine productLine =
catalog.getProductLine( productLineString );
  productLine.addProduct( product );
  }
  String keywords = req.getParameter( "keywords" );
  product.setKeywords( keywords );
  String name = req.getParameter( "name" );
  product.setName( name );

Далее внесенные в форму объекты Author добавляются в Product. Прежде чем вы впишете новых авторов, необходимо удалить все существующие на данный момент записи об авторах. Для этого используется метод removeAHAuthors. После этого все готово для добавления новых авторов. Для этого используется метод getAuthor, определенный ниже в этом разделе. Метод getAuthor возвращает булеву величину true, если им был обнаружен автор с указанным идентификатором, и false, если не было найдено ни одного автора с таким идентификатором. Также вам нужно проверить, не был ли добавлен какой-либо новый автор; для этого используется идентификатор New. Далее весь процесс повторяется для объектов Artist, как показано в листинге 6.60.

Листинг 6.60. Добавление Author и Artist (UpdateProduct.java)

 product.removeAllAuthors();
  for( int i = 0; getAuthor( "" + i, req, product ); i++ )
  ;//do nothing
  getAuthor( "New", req, product );
 product.removeAllArtists();
  for( int i = 0; getArtist( "New", req, product ); i++ )
  ;//do nothing
  getArtist( "New", req, product );

Для обновления остальных характеристик товара используется код, во многом похожий на уже рассмотренный. Он приведен в листинге 6.61. С помощью метода getParameter данные извлекаются из объекта request, а затем используется соответствующий метод set объекта Product. Для двух полей, clip и image, задействован тот же процесс, который мы рассматривали для полей artists и authors. Наконец, создается и отображается для пользователя HTML-страница, которая сообщает, что обновление данных прошло успешно, и на которой имеется ссылка на главную страницу.

Листинг 6.61. Добавление остальных характеристик товара (UpdateProduct.java)

 String price  = req.getParameter( "price" );
  price = price.replace( '$', ' ' );
  product.setPrice( Util.getDouble( price ) );
  String quantity = req.getParameter( "quantity" );
  product.setQuantityInStock( Util.getInt( quantity ) );
  String dateString = req.getParameter( "onSaleDate" );
  Date date = product.getOnSaleDate();
  if( date == null ) {
  date = new Date();
  product.setOnSaleDate( date );
  }
  date.fromString( dateString );
  String description = req.getParameter( "description" );
  product.setDescription( description );
 product.removeAllImages();
  for( int i = 1; getImage( "" + i, req, product ); i++ )
  ;//do nothing
  getImage( "New", req, product );
 product.removeAllClips();
  for( int i = 1; getClip( new String( "" + i ), req, product ); i++  )
  ;//do nothing
  getClip( "New", req, product );
  // Return HTML.
  out.println( "<html><head><title>Update Successful</title>
</head>"  );
  out.println( "<body><h2>Update Succesful</h2>" );
  out.println( "<a href='/main.html'>Return to main page</a>
</body></html>"  );
  }

Метод getAuthor, показанный в листинге 6.62, вызывается из метода doPost cep- влета, чтобы извлекать информацию из запроса и добавлять ее в Product. В JSP- странице Edit мы идентифицировали каждый элемент Author с помощью значения счетчика, которое добавлялось к строке author, а теперь мы ищем ту же строку для извлечения информации. Если строка не найдена или значение элемента Author пропущено, возвращается f al se. Если же значение обнаружено, то оно добавляется в Product как имя автора и метод возвращает булеву величину true.

Листинг 6.62. Метод getAuthor (UpdateProduct.java)

 boolean getAuthor(  String i, HttpServletRequest req,
Product product ) {
  String author = req.getParameter( "author" + i );
  if( author == null || author.equals( "" ) ) {
  return false;
  }
  product.addAuthor( author );
  return true;
  }

Методы getArtist, getlmage и getClip, показанные в листинге 6.63, используют ту же технику, что и в методе getAuthor. Метод getArtist почти идентичен getAuthor, а методы getlmage и getClip несколько длиннее, так как для объектов Image и Clip требуется извлечь большее количество параметров запроса и установить их в качестве значений полей объекта Product.

Листинг 6.63. Методы getArtist, getlmage и getClip (UpdateProduct.java)

 boolean getArtist(  String i, HttpServletRequest req,
Product product ) {
  String artist = req.getParameter( "artist" + i );
  if( artist == null || artist.equals( "" ) ) {
  return false;
  }
  product.addArtist( artist );
  return true;
  }
 boolean getImage( String i, HttpServletRequest req,
Product product ) {
  String format = req.getParameter( "img" + i + "-format"  );
  if( format == null || format.equals("") ) {
  return false;
  }
  Image img = new Image();
  img.setFormat( format );
  String src = req.getParameter( "img" + i + "-src" );
  img.setSrc( src );
  String height = req.getParameter( "img" + i + "-height"  );
  img.setHeight( Util.getInteger( height ) );
  String width = req.getParameter( "img" + i + "-width" );
  img.setWidth( Util.getInteger( width ) );
  String caption = req.getParameter( "img" + i + "-caption"  );
  img.setCaption( caption );
  product.addImage( img );
  return true;
  }
 boolean getClip( String i, HttpServletRequest req,
Product product ) {
  String format = req.getParameter( "clip" + i + "-format"  );
  if( format == null || format.equals("") ) {
  return false;
  }
  Clip clip = new Clip();
  clip.setFormat( format );
  String src = req.getParameter( "clip" + i + "-src" );
  clip.setSrc( src );
  String title = req.getParameter( "clip" + i + "-title" );
  clip.setTitle( title );
  String length = req.getParameter( "clip" + i + "-length"  );
  clip.setLength( length );
  String size = req.getParameter( "clip" + i + "-size" );
  clip.setSize( size );
  String description = req.getParameter
( "clip" + i + "-description"  );
  clip.setDescription( description );
  product.addClip( clip );
  return true;
  }
  }

JSP-страницы и сервлеты, представленные в этой главе, показывают, как можно использовать приложение, работающее через браузер, для добавления, редактирования и удаления элементов каталога, размещенного на сервере. Таким образом, возможности WWW расширяются от простого представления статических данных до полноценной системы обработки информации.

Представленное в этой главе решение лишено некоторых свойств, которые потребовались бы для настоящего крупномасштабного сайта. Следовало бы улучшить обработку ошибок, которая не слишком хорошо организована. У нас отсутствует синхронизация доступа к файлам XML, так что если два человека одновременно возьмутся редактировать один и тот же файл, то изменения, внесенные одним из них, будут потеряны. Но, несмотря на эти недостатки, данное приложение справилось бы с задачей редактирования XML-каталога товаров небольшого сайта.


  • Глава 7. Поиск своего покупателя с помощью опросов
    • Обеспечение конфиденциальности
      • Промышленные стандарты
      • Общественные организации
      • Создание системы опросов с помощью XML-сценария
      • Определение последовательности вопросов
      • Пример анкеты
    • Сервлет управления опросом
      • Код сервлета управления опросом
        • Класс Interpreter
        • Отображение вопросов
        • Отображение введения
        • Управление ветвлением опроса
        • Обработка элемента Terminal
        • Вспомогательные методы класса Interpreter
        • Класс Recorder
      • Варианты анализа анкеты
        • Класс для создания файлов снимков
        • Класс для создания таблиц
        • Пример сервлета для просмотра результатов отчета
      • Служебная библиотека документа XML

Глава 7.

Поиск своего покупателя с помощью опросов

Для успешной работы вашего электронного магазина необходимо, чтобы вы имели некоторое представление о ваших покупателях. Для этого в первую очередь вы должны использовать архивы данных о продажах и о взаимодействии пользователей с вашим web-сайтом. Однако помимо этих записей у вас есть возможность с помощью проводимых на сайте опросов напрямую получать информацию от покупателей. Большинство из них с удовольствием ответят на ваши вопросы, если вы сможете убедить их в соблюдении необходимой конфиденциальности.

Для создания систем сетевых опросов хорошо подходит комбинация XML и сервлетов Java. В этой главе мы не ограничиваемся простым линейно организованным опросом, а создаем систему, которая может задавать различные вопросы в зависимости от предыдущих ответов пользователя.

Обеспечение конфиденциальности

В настоящее время многие разделяют беспокойство по поводу сохранения права потребителей на частную жизнь и с подозрением воспринимают любую организацию, которая хранит информацию в компьютерных базах данных. С одной стороны, все фирмы убеждены, что только максимально полная информация о потенциальных покупателях является ключом к победе в жестокой конкурентной борьбе. С другой стороны, люди испытывают тревогу при мысли, что подробности их частной жизни могут стать известными. Вы можете помочь своим клиентам чувствовать себя более уверенно при посещении вашего сайта, если доступно объясните те правила соблюдения конфиденциальности, которым вы следуете.

Промышленные стандарты

Желание решить описанную проблему в области электронной коммерции без вмешательства правительства привело к созданию независимой некоммерческой инициативной организации под названием TRUSTe [Trust (англ.) — доверие. — Примеч. перев. ] (www.truste.org). Основными принципами TRUSTe являются следующие.

  • Пользователь имеет право на осведомленность и осознанный выбор того, каким образом будет использоваться его персональная информация.

  • Ни один из принципов соблюдения конфиденциальности не годится для всех случаев.

Организация TRUSTe осуществляет проверку соблюдения принципов конфиденциальности, так же как, например, организация UL (United Laboratories — объединенные лаборатории) проводит проверки в области электротехнического оборудования- Организации — члены программы получают право на размещение на своих сайтах логотипа TRUSRe, если принципы обеспечения конфиденциальности, которым они следуют, удовлетворяют стандартам TRUSTe. Также эти организации должны заплатить членский взнос и пройти контрольную проверку. Членский взнос составляет сумму 299 долларов для компаний, чей годовой доход не превышает одного миллиона долларов, так что членство в программе TRUSTe вполне доступно.

ПРИМЕЧАНИЕ

Сайты, которые могут собирать информацию у детей в возрасте до 13 лет, должны удовлетворять еще более жестким требованиям в отношении соблюдения конфиденциальности. В настоящее время Конгресс США готовит новое законодательство в данной области.

TRUSTe постоянно проводит наблюдения за теми сайтами, которые являются членами программы, и проверяет соблюдение ими заявленной практики обеспечения конфиденциальности. В процессе наблюдения, в частности, на сайт отправляется вымышленная персональная информация, а затем отслеживается ее использование. Кроме того, TRUSTe активно преследует те сайты, которые несанкционированно размещают логотип TRUSTe.

Как для членов программы, так и для других организаций сайт www.truste.org представляет собой прекрасное место для знакомства с новостями в области охраны права пользователей на конфиденциальность, особенно в США. TRUSTe также сотрудничает со многими другими организациями, которые пытаются установить стандарты в сфере охраны частной информации пользователей.

Общественные организации

Многие считают, что частная информация подвергается такой же опасности при сборе данных, проводимых правительственными организациями, как и при опросах коммерческих организаций. Поэтому неудивительно, что существует несколько общественных организаций, деятельность которых связана с этой проблемой. Одной из таких организаций является EPIC (Electronic Privacy Information Center — Центр конфиденциальности электронной информации), расположенная в Вашингтоне, штат Колумбия (www.epic.org/privacy).

Организация EPIC занимается гражданскими правами и правами на частную жизнь. Часто она дает свидетельские показания на слушаниях дел в судах и, используя Акт о свободе информации (Freedom of Information Act), расследует и предает гласности нарушения правительственными организациями США прав граждан на частную жизнь.

Организация EPIC связана с Privacy International (www.privacy.org/pi) — международной коалицией групп, работающих в области защиты прав потребителей на конфиденциальность общения. Эта организация, основанная в 1990 году, базируется в Лондоне; она проводит международные конференции по проблемам соблюдения конфиденциальности. Каждый год она публикует список правительственных и коммерческих организаций, получивших титул «Big Brother» [Большой Брат. — Примеч. перев. ]. Этот титул Privacy International присваивает тем организациям, которые, по ее мнению, ведут наиболее агрессивную и нечестную политику в отношении использования частной информации.

Знание потребностей и интересов своих покупателей существенно для успеха любого предприятия. Но для того, чтобы у посетителей вашего сайта не возникло никаких опасений за их персональную информацию, лучше всего ясно изложить им принципы вашей политики в этом отношении и всегда предоставлять им возможность отказаться от сообщения несущественной информации. Если ваш сайт ориентирован на детей, вам также следует убедиться, что принципы работы с персональной информацией соответствуют принятому законодательству.

Создание системы опросов с помощью XML-сценария

Простая, но эффективная форма сбора информации — это проведение опроса по сети. Поскольку язык XML предназначен, в частности, для определения структуры документов, а опрос осуществляется как раз с помощью четко структурированных документов, XML как нельзя лучше подходит для решения нашей задачи. Основанная на XML система опросов, которой посвящены все остальные разделы этой главы, отличается гибкостью. В этой системе конкретный пользователь не связывается со своими ответами; система просто собирает ответы всех пользователей вместе. В систему несложно внести изменения, которые позволили бы сохранять ответы каждого пользователя в базе данных, но в таком случае вам пришлось бы объяснять пользователю, что именно вы собираетесь делать с его персональной информацией.

В этом разделе мы займемся разработкой обобщенной структуры XML для создания системы сетевых опросов. Мы начнем со списка критериев, которым должна отвечать система.

  • Управление внешним видом. В идеале, мы хотели бы, чтобы дизайн-страницы с вопросами не нарушал общего стиля нашего web-сайта.

  • Гибкость методов опроса. Мы должны иметь возможность применять различные методы опросов: от простых вопросов, на которые пользователь отвечает «да» или «нет», до выбора одного из предложенных ответов.

  • Возможность ветвления. В одном и том же сценарии очередность вопросов должна зависеть от ответов пользователей. Например, если из ответа на некоторый вопрос понятно, что данный пользователь никогда не покупает музыкальные компакт-диски через Интернет, не имеет смысла расспрашивать его о музыкальных предпочтениях и нужно выбрать другую ветвь вопросов.

  • Расширяемость. Если в систему потребуется добавить новую форму представления вопросов, необходимые изменения кода должны быть минимальными.

  • Запись результатов. Результаты опроса каждого участника должны быть записаны полностью и не должны зависеть от результатов других участников. Это дает максимальную гибкость при анализе.

Определение последовательности вопросов

На рис. 7.1 показаны возможные ветви сценария опроса. Существенно следующее: имеются блоки вопросов, в которых ветвление отсутствует и которые заканчиваются многовариантным вопросом — ответ на такой вопрос ведет к переходу по той или иной ветви. Многовариантный вопрос может приводить несколько ветвей к одному блоку (например, блоки С и D приводят к одному блоку Е) или к различным блокам (например, из блока А можно попасть, в зависимости от ответа на многовариантный вопрос, в блок В, С или D). Блоки либо заканчиваются многовариантными вопросами, либо являются завершающими и прекращают опрос. По достижении завершающего блока система записывает все ответы в файл. Система может быть устроена таким образом, чтобы каждому завершающему блоку соответствовал свой уникальный файл.

Переводя эту диаграмму на язык сущностей XML, мы приходим к следующей структуре. В документе Questionnaire на первом уровне имеется элемент Intro (введение) и одна или несколько сущностей Block. Каждая сущность Block содержит одну или более сущностей Ques (вопросы) и может заканчиваться сущностью Terminal. В сущности Block имеется атрибут name, который используется для адресации ветвления, и атрибут type, значение которого равно terminal, если блок заканчивается тегом Terminal. Этот внешний уровень структуры системы опросов схематически описан в листинге 7.1 [Questionnaire — анкета, intro — введение (сокращение от introduction). — Примеч. перев. ].

Листинг 7.1. Первый и второй уровень иерархии анкеты

 <Questionnai re> <Intro> </Intro>
 <Block name="A"> </Block>
 <Block name="B" type="terminal"> </Block>
 <Block name="C"> </Block>
 <Block name="D"> </Block>
 <Block name="E" type="terminal"> </Block>
 </Questionnai re>

Рис. 7.1. Возможные ветви сценария

Каждый вопрос в блоке создается с помощью тега dues, который включает в себя текстовый фрагмент и два или более варианта выбора, создаваемых тегами Qopt. Атрибуты тегов dues и Qopt позволяют контролировать ветвление и способ представления вопросов. Самый простой способ увидеть, как все это устроено, — рассмотреть несколько вопросов, представленных в примере анкеты в следующем разделе.

Пример анкеты

В листинге 7.2 показаны введение и первый вопрос анкеты, которую вы могли бы использовать для определения, какие товары добавить в ваш каталог.

ПРИМЕЧАНИЕ

Полная версия анкеты имеется на прилагаемом к книге компакт-диске. Тип первого вопроса (атрибут type) определен как QMC (Question Multiple Choice — вопрос, допускающий выбор одного ответа из списка); это вопрос, в качестве ответа на который пользователь должен выбрать один из предложенных вариантов.

Листинг 7.2. Начало документа XML, определяющего анкету (customsurvey.xml)

 <?xml  version="1.0" standalone="yes" ?>
  <Questionnaire title="Example Customer Survey"
  author="wbb" date="May 30, 2000"
  method="xml" file="e:\scripts\questionnaire\surveyresult.xml"
  >
  <Intro><![CDATA[
  <h1>Welcome Customers</h1><br>
  <p>We here at <i>BuyStuff.com</i>
want to meet your every  desire  to buy <b>STUFF</b>.
To that end we are greatly expanding
  our on-line catalog and we want to concentrate on
<b>STUFF</b>
  you will want to buy as soon as you see it. Please help by
  completing this simple survey.
  </p>]]>
  </Intro>
  <Block name="intro" type="terminal" >
  <Ques id="intro:1" type="QMC" >
  <Qtext>Which of the following are you most interested in
buying on-line?
  </Qtext>
  <Qopt val="a" branch="books" >Books</Qopt>
  <Qopt val="b" branch="cds" >Cds</Qopt>
  <Qopt val="c" branch="gadgets">Electronic goodies</Qopt>
  <Qopt val="d" >I am not interested in buying Stuff!</Qopt>
  </Ques>
  <!-- this terminates the block and the questionnaire -
  could substitute different file for recording -->
  <Terminal><![CDATA[<h2>Thanks for looking anyway!</h2>
  ]]>
  </Terminal>
  </Block>

Элементы Qopt в первом вопросе со значениями атрибута val, равными a, b и с, служат ответвлениями к другим блокам, в то время как вариант ответа d приводит к отображению завершающего сообщения. Можно выбрать только один из предложенных ответов, потому что тип вопроса указан как QMC. В этой главе мы используем вопросы только двух типов: QMC (Question Multiple Choice — вопрос, допускающий выбор одного ответа из списка) и QMCM (Question Multiple Choice Multiple Answer — вопрос, допускающий выбор нескольких ответов из списка). Результаты ответа пользователя на этот вопрос записываются с помощью атрибута id вопроса и значений атрибутов val элементов Qopt.

В листинге 7.3 показан блок (из листинга 7.2), на который указывает атрибут branch тега Qopt, причем в последнем атрибут val имеет значение а. Вопрос с идентификатором books:! относится к типу QMCM, то есть позволяет выбрать несколько вариантов ответа. Блоки cds и gadgets устроены похожим образом.

Листинг 7.3. Блок вопросов «Книги» (custom.ersurvey.xml)

 <Block  name="books" type="terminal" >
  <Ques id="books:1" type="QMCM" >
  <Qtext>Please select all of the book categories you
  would like to see in our catalog.
  </Qtext>
  <Qopt val="0">Best Sellers of all types</Qopt>
  <Qopt val="1">Science Fiction</Qopt>
  <Qopt val="2">Fantasy Fiction</Qopt>
  <Qopt val="3">History and Biography</Qopt>
  <Qopt val="4">Computer Technology</Qopt>
  <Qopt val="5">Business Related</Qopt>
  </Ques>
  <Terminal><![CDATA[<h2>Thanks for answering the survey!
</h2>]]>
  </Terminal>
  </Block>

Сервлет управления опросом

В этом разделе приводится полный код сервлета Java, который управляет опросом, основанным на приведенном в предыдущем разделе документе XML. Этот сервлет записывает результаты в форме, удобной для дальнейшего анализа. Также мы опишем служебный класс, который позволяет управлять объектами document в процессоре сервлетов, и сервлет для составления таблицы и отчета по результатам опроса.

Код сервлета управления опросом

В листинге 7.4 показаны инструкции импорта, объявления классов и метод init для сервлета QuestionnaireServ. Статическая переменная homedir, значение которой может быть получено из ServletConfig, используется в методе init для считывания файла свойств. С помощью этого файла задается путь к исходному файлу XML для данной анкеты. Также файл свойств может потребоваться для того, чтобы задать значение переменной handler типа Srting. Это значение равно URL- адресу, который используется web-сервером для данного сервлета.

Листинг 7.4. Начало кода сервлета QuestionnaireServ (QuestionnaireServ.java)

 package com.XmlEcomBook.Chap07;
import com.XmlEcomBook.DOMlibrary ;
  import org.w3c.dom.* ;
  import java.io.*;
  import java.util.* ;
  import javax.servlet.*;
  import javax.servlet.http.*;
public class QuestionnaireServ extends HttpServlet
  {
  static String brcrlf = "<br>\r\n" ;
  static String homedir = "e:\\scripts\\questionnaire" ;
  static String handler =
"http://www.lanw.com/servlet/Questionnaire"  ;
  static String version = "v1.0";
  Properties qProp ;
 public void init(ServletConfig config) throws ServletException
  {
  super.init(config);
  String tmp = config.getInitParameter("homedir");
  if( tmp != null ) homedir = tmp ;
  System.out.println("Start QuestionnaireServ using " + homedir );
  File f = new File( homedir, "questionnaire.properties");
  try { qProp = new Properties();
  qProp.load( new FileInputStream(f) );
  tmp = qProp.getProperty("handler");
  if( tmp != null ) handler = tmp ;
  System.out.println
("Loaded properties for Questionnaire handler: "
  + handler );
  }catch(IOException e){
  System.out.println("Error loading " + e );
  }
  }

Сервлет QuestionnaireServ отслеживает взаимодействие с каждым пользователем, отвечающим на вопросы анкеты, в сеансах. Предполагается, что исходный запрос — это запрос методом GET с HTML-страницы, на которой имеется простая форма для определения значения переменной qname, идентифицирующей требуемую анкету. Приспосабливая этот сервлет для своего приложения, вы на этом этапе можете также записать идентификатор пользователя.

Метод doGet, показанный в листинге 7.5, отыскивает объект document, соответствующий значению переменной qname, используя путь, указанный в файле свойств, и служебную библиотеку DOMlibrary. Если ему удается найти этот объект, далее он получает объект HttpSession и присоединяет к сеансу новый объект Interpreter, в котором хранится документ. Новый объект Recorder также инициализируется и присоединяется к сеансу. Для обработки любой ошибки и создания соответствующего сообщения используется метод errorMsg (см. листинг 7.7).

Объект Interpreter отвечает за создание форм HTML, с помощью которых будет осуществляться опрос, а объект Recorder отвечает за запись ответов пользователя. Эти классы обсуждаются в разделах «Класс Interpreter» и «Класс Recorder».

Результатом вызова метода doGet является страница с текстом, содержащимся в теге Intro документа XML. Для отображения этого текста на странице используется метод dolntro объекта Interpreter; также на странице располагается форма с кнопкой, при щелчке на которой появляется первый вопрос анкеты.

Листинг 7.5. Код метода doGet (QuestionnaireServ.java)

 public void  doGet
(HttpServletRequest req, HttpServletResponse resp)
  throws ServletException, IOException
  {
  resp.setContentType("text/html");
  PrintWriter out = new PrintWriter(resp.getOutputStream());
  String qname = req.getParameter("qname") ;
  // System.out.println("Start doGet");
  if( qname == null || qname.length() == 0 ){
  errorMsg( out, "Bad QNAME data", null); return;
  }
  // MUST have qname = name of xml file
  String src = qProp.getProperty( qname );
  if( src == null ) {
  errorMsg( out, "Bad QNAME lookup", null ); return ;
  }
  String userid = "unknown" ; // customer or student id or unknown
  String tmp = req.getParameter("userid");
  if( tmp != null ) userid = tmp;
  String usertype = "unknown" ; // "student" "customer"  etc etc
  tmp = req.getParameter("usertype");
  if(tmp != null ) usertype = tmp ;
  DOMlibrary lib = DOMlibrary.getLibrary();
  System.out.println("DOMlibrary initialized, try for " + src );
  Document doc = lib.getDOM( src );
  if( doc == null ){
  errorMsg( out, "DOM doc failed - unable to continue", null );
  return ;
  }
  HttpSession session = req.getSession( true );
  // if not new must be re-entering - could recover here
  Interpreter terpret = new Interpreter( doc, handler );
  session.putValue( "xmldocument", terpret );
  // session.setAttribute("xmldocument",terpret );
  // the putValue method was used in the 2.1 API but is now
  // a deprecated method,
  //
  Recorder rb = new Recorder(userid, usertype,
  session.getId(), src );
  rb.setMethods( doc );
  session.putValue("recorder", rb );
  //session.setAttribute("recorder", rb );
  try { //
  terpret.doIntro( out ); // includes head and Form
  footer( out );
  }catch(Exception e){
  errorMsg( out, "doGet ", e );
  }

После введения в анкету, как видно из листинга 7.6, все запросы и ответы проходят через метод doPost. После извлечения из объекта HttpSession объектов Interpreter и Recorder данные запроса собираются и с помощью объектов Interpreter и Recorder генерируется ответ.

Листинг 7.6. Метод doPost класса QuestionnaireServ (QuestionnaireServ.java)

 public void  doPost
(HttpServletRequest req, HttpServletResponse resp)
  throws ServletException, IOException
  {
  resp.setContentType("text/html");
  PrintWriter out = new PrintWriter(resp.getOutputStream());
  //System.out.println("Start doPost");
  HttpSession session = req.getSession(false);
  try {
  if( session == null ){ errorMsg(out, "No Session ", null );
  return ;
  }
  Interpreter terpret =
  (Interpreter)session.getValue("xmldocument"); //deprecated
  //in 2.2 API we use this
  // (Interpreter)session.getAttribute("xmldocument");
  Recorder rb =
  (Recorder) session.getValue("recorder");
  // (Recorder) session.getValue("recorder");
  if( terpret == null ||
  rb == null ){
  errorMsg( out, "Data not recovered from Session", null );
  return;
  }
  terpret.doPostQ( out, req, rb );
  footer( out );
  }catch(Exception e ){
  errorMsg( out, "doPost ", e );
  }
  }

В листинге 7.7 показаны некоторые полезные вспомогательные методы класса QuestionnaireServ. Используя метод footer для размещения закрывающих те- гов на каждой странице, можно внизу указывать версию сервлета. Это очень удобно на этапе разработки, а впоследствии из финальной версии сервлета эти теги можно легко удалить.

Отладка сервлетов может оказаться очень непростым делом, особенно если ошибки проявляются лишь время от времени. Отладка особенно затрудняется в том случае, если пользователи не могут четко и однозначно выразить свои проблемы. Для сообщения об ошибках мы предпочитаем использовать метод erroMsg. Если ошибка связана с каким-либо исключением, то на HTML-странице появится сообщение с содержимым стека. Там же указывается и электронный адрес, по которому пользователь может послать сообщение об ошибке и, мы надеемся, копию содержимого стека. Разумеется, вам следует заменить приведенный ниже электронный адрес на свой.

Листинг 7.7. Вспомогательные методы сервлета QuestionnaireServ (QuestionnaireServ.java)

 public void  footer( PrintWriter out ){
  out.println("<hr> Servlet version: " + version + "<br>");
  out.println("</body>");
  out.println("</html>");
  out.close();
  }
 // assumes response has been set to text/html
  private void errorMsg
( PrintWriter out, String msg, Exception ex ){
  out.println("<html>");
  out.println("<head>
<title>QuestionnaireServ Output</title></head>");
  out.println("<body>");
  out.println("<h2>Error: " ); out.println( msg );
  out.println("</h2><br>");
  if( ex != null ){
  ex.printStackTrace( out );
  }
  out.println("<br>");
  out.println("<a href=\"mailto:wbrogden@bga.com\">
Please  mail me the error message.</a><br>");
  footer( out );
  }
 public String getServletInfo()
  {
  return "Administers a questionnaire";
  }
  }

Класс Interpreter

В классе Interpreter инкапсулирована вся работа по созданию на основе документа XML форм для вопросов. Этот класс устроен так, чтобы быть максимально гибким в отношении способов анкетирования. Хотя в данной версии поддерживаются только два типа вопросов, используемый механизм может быть легко расширен для включения в анкету других типов вопросов.

Статические методы и инструкции импорта для класса Interpreter показаны в листинге 7.9. Константы QMC и QMCM означают два допустимых в нашей версии типа вопросов: QMC — вопрос, допускающий выбор одного ответа из списка, а QMCM — вопрос, допускающий выбор нескольких ответов из списка. Рассмотрим в качестве примера код XML для вопроса о музыкальных предпочтениях, приведенный в листинге 7.8.

Листинг 7.8. Начало блока вопросов в документе XML (customersurvey.xml)

 <Block  name="cds" type="terminal" >
  <Ques type="QMCM" id="palm:1">
  <Qtext>Please select all of the categories of
  CD that you would like to see in our catalog
  </Qtext>
  <Qopt val="0">Classical music</Qopt>
  <Qopt val="1">Country and Western</Qopt>
  <Qopt val="2">The latest Pop Groups</Qopt>
  <Qopt val="3">Current Rock</Qopt>
  <Qopt val="4">Golden Oldies Rock</Qopt>
  <Qopt val="5">Environmental</Qopt>
  <Qopt val="6">Novelty and Humor</Qopt>
  </Ques>

Открывающий тег Ques использует атрибут type для задания типа вопросов, в данном случае QMCM. Атрибут id является уникальным идентификатором этого вопроса.

Вместо того чтобы сравнивать строку, являющуюся значением атрибута type, с возможными типами вопросов, мы используем хэш-таблицу Hashtable и отыскиваем в ней целочисленное значение типа int, которое можно использовать в инструкции switch для выбора нужного способа представления вопроса. Эта хэш-таблица называется typeHash, а поиск в ней осуществляется с помощью метода 1 ookUpType, показанного в листинге 7.9.

Для добавления нового типа вы просто должны будете определить новую строку String и целочисленную константу типа int в качестве статических переменных класса Interpreter и в хэш-таблице typeHash.

Листинг 7.9. Константы и инструкции импорта в начале исходного кода класса Interpreter (Interpreter.java)

package com.XmlEcomBook.Chap07;
  //
  import org.w3c.dom.* ;
  import com.sun.xml.tree.* ;
  import java.io.*;
  import java.util.* ;
  import javax.servlet.*;
  import javax.servlet.http.*;
public class Interpreter
  {
  static final String brcrlf = "<br>\r\n";
  static final int QMC = 1 ;
  static final int QMCM = 2 ;
 static Hashtable typeHash = new Hashtable();
  static { // static initialization block
  typeHash.put("QMC", new Integer( QMC ));
  typeHash.put("QMCM", new Integer( QMCM ));
  }
 static int lookUpType( String type ){
  Integer N = (Integer)typeHash.get( type );
  if( N == null ) return 0 ;
  return N.intValue();
  }

В листинге 7.10 приводятся переменные экземпляра и конструктор класса Interpreter. Для каждого сеанса работы пользователя создается экземпляр класса Interpreter, в котором хранится сам документ и отмечается текущая позиция пользователя в процессе его «продвижения» по анкете. Переменные nowBlock и nowNode являются ссылками на объекты, реализующие интерфейс org.w3c.dom.Node.

Листинг 7.10. Переменные экземпляра и конструктор класса Interpreter (Interpreter.java)

 // instance  variables below this
  Document theDom ;
  Node nowBlock, nowNode ; // nowNode should be quest type
  boolean terminal = false ; // true if the block is terminal
  String title ;
  String css = "" ; // may change from block to block
  String actionStr ;
 NodeList blockNodeList ; // Nodes that are <Block type
  // the constructor
  public Interpreter( Document doc, String handler ){
  theDom = doc ; actionStr = handler ;
  Element E = theDom.getDocumentElement(); // the root
  blockNodeList = E.getElementsByTagName("Block");
  // note that in contrast to other get methods, getAttributes
  // returns "" if the attribute does not exist
  title = E.getAttribute("title");
  css = E.getAttribute("css"); // used for <Intro>
  }

Для того чтобы обеспечить некоторую гибкость в форматировании вопросов, предусмотрена возможность задавать каскадную таблицу стилей для всего документа и заменять определенный по умолчанию стиль для каждого блока. Метод writeHead, показанный в листинге 7.11, управляет выводом начала HTML-страницы и включает в себя ссылку на таблицу стилей, если она применяется. В этом листинге также показаны методы startForm и endForm. Заметим, что переменная quesid записывается в форму как скрытая переменная, которая впоследствии извлекается в методе doPostQ (листинг 7.16).

Листинг 7.11. Методы для создания различных частей HTML-страницы (Interpreter.java)

 void writeHead( PrintWriter out ){
    out.println("<html>");
    out.println("<head><title>" + title + "</title></head>");
    if( css.length() > 0 ){
     out.println("<link href=\"" + css +
       "\" type=\"text/css\" rel=\"stylesheet\">" );
    }
    out.println("<body>");
  }
 // assumes nowNOde is set to the first question
  // output form start and question text
  public void startForm(PrintWriter out ){
  out.print("<form method=\"POST\" action=\"" );
  out.print( actionStr ); out.println("\" >");
  }
 // fills in hidden variable and button
  public void endForm( PrintWriter out, String id ){
  out.print("<input type=\"hidden\" name=\"quesid\"  value=\""
  + id + "\" ><br>" );
  out.print("<input type=\"submit\" value=\"" );
  out.print("Next" );
  out.println("\" name=\"action\" ><br>");
  out.println("</form><br>");
  }

Отображение вопросов

Метод genQues, приведенный в листинге 7.12, вызывается после того, как переменной nowNode присваивается значение того элемента dues, который требуется отобразить. Обратите внимание на то, как используется указатель типа вопроса (QMC или QMCM) для выбора сообщения-подсказки. После того как напечатаны текст вопроса и подсказка, метод genQues создает форму, содержащую различные варианты ответа.

Листинг 7.12. Метод genQuest (Interpreter-Java)

 public void  genQuest( PrintWriter out ){
  Element E = (Element) nowNode ;
  String qid = E.getAttribute("id") ;
  String type = E.getAttribute("type");
  String lim = E.getAttribute("limit");
  // out.print("Question id: " + qid + " type: " + type +  brcrlf );
  writeHead( out );
  NodeList nm = E.getElementsByTagName("Qtext");
  out.print( nm.item(0).getFirstChild().getNodeValue() );
  out.println(brcrlf );
  NodeList opm = E.getElementsByTagName("Qopt");
  int optCt = opm.getLength();
  int typeN = lookUpType( type );
  switch( typeN ){
  case QMC :
  out.print("Choose one"); break ;
  case QMCM :
  if( lim.length() == 0 ){
  out.print("Choose any number");
  }
  else { out.print("Choose up to " + lim );
  }
  break ;
  default :
  out.print("Unknown type");
  }
  out.print( brcrlf );
  startForm( out ); // creates <form...
  for( int i = 0 ; i < optCt ; i++ ){
  doOption(out, opm.item(i), typeN );
  }
  endForm( out, qid );
  }

Метод genQuest в предыдущем листинге вызывает метод doOption (листинг 7.13) для каждого элемента <Qopt>. Если вы захотите добавить дополнительные типы ответов, например поле для ввода текста, вам потребуется модифицировать именно этот метод. В этом листинге также показан метод checkBl ockType, который используется для проверки атрибутов элемента Block.

Листинг 7.13. Метод doOption (Interpretr.java)

 // opN is  from node list of <Qopt> - create output
  // <Qopt val="a" branch="" >Option a.</Qopt>
  private void doOption(PrintWriter out, Node opN, int typeN ){
  Element E = (Element) opN;
  String val = E.getAttribute("val") ;
  String branch = E.getAttribute("branch");
  String content = E.getFirstChild().getNodeValue();
  // what else? type of option display?
  switch( typeN ){ // known valid
  case QMC :
  out.print("<input name=\"opt\" value=\"" + val +
  "\" type=\"RADIO\" >" );
  break ;
  case QMCM :
  out.print("<input name=\"opt\" value=\"" + val +
  "\" type=\"CHECKBOX\" >" );
  break ;
  } // now for the text
  out.println( content );
  out.println( brcrlf );
  }
 // look at the type and css attributes in <Block>
  private void checkBlockType( ){
  Element E = (Element)nowBlock ;
  String tmp = E.getAttribute("type");
  terminal = tmp.equals("terminal");
  tmp = E.getAttribute("css");
  if( tmp.length() > 0 ) css = tmp ;
  System.out.println("checkBlockType - css:" + css );
  }

Отображение введения

В листинге 7.14 представлен метод dolntro, который задает начальный элемент Block, отыскивая первый элемент в списке узлов ЫockNodeList. Когда значение переменной nowBlock задано, вызывается метод setQNodelnBlock для задания значения переменной nowNode равным первому элементу Ques. Если все это успешно выполнено, отображается текст введения и простая форма, которая вызывает первый вопрос.

Листинг 7.14. Метод dolntro, который выводит текст из тега Intro (Interpreter.java)

 // <head>  has been set, we are in <body>
  public void doIntro(PrintWriter out ){
  writeHead( out );
  nowBlock = blockNodeList.item(0);
  if( nowBlock == null ){
  out.println("Error 1 setting up first question.<br>");
  return ;
  }
  if( setQnodeInBlock( 0 )== null ){
  out.println("Error 2 setting up first question.<br>");
  return ;
  }
  checkBlockType( ); // sets the terminal flag
  out.println( getIntro() );
  out.print("<form method=\"POST\" action=\"" );
  out.print( actionStr ); out.println("\" >");
  endForm( out, "intro" );
  }

Управление ветвлением опроса

В листинге 7.15 показан метод setBranch, который вызывается из метода doPostQ, когда ответ пользователя на данный вопрос определяет выбор очередной ветви опроса. Этот метод просто просматривает все элементы ВТ ock и ищет имя (значение атрибута name), совпадающее с заданным именем блока. В этом методе также устанавливаются значения переменных nowBlock и nowNode.

Листинг 7.15. Метод setBranch (Interpreter.java)

// jump to another block has been detected
  private void setBranch(String block ){
  int ct = blockNodeList.getLength();
  for( int i = 1 ; i < ct ; i++ ){ // block 0 was the start
  nowBlock = blockNodeList.item(i);
  String name = ((Element)nowBlock).getAttribute("name");
  if( name.equals( block )){
  checkBlockType() ; // to set terminal flag
  setQnodeInBlock( 0 ) ; // set nowNode
  return ;
  }
  }
  System.err.println("Interpreter.setBranch failed to find "
+ block  );
  nowBlock = nowNode = null ;
  }

Метод doPostQ, начало которого приводится в листинге 7.16, вызывается из метода doPost и управляет созданием новой HTML-страницы. Обратите внимание на то, что в первую очередь этот метод проверяет, не был ли зафиксирован соответствующим объектом Recorder тот факт, что опрос завершен. Это сделано для того, чтобы пользователь не мог с помощью кнопки Back (Назад) браузера возвратиться на предыдущую страницу для ввода данных, когда опрос уже завершен и данные записаны.

Следующая часть кода отводится для обработки специального случая, когда переменная quesid равна intro, то есть вопрос является первым в анкете. Во всех других случаях ответ пользователя записывается путем сравнения значений opt, взятых из формы, с атрибутами тега Qopt с помощью объекта Recorder этого сеанса.

Листинг 7.16. Начало метода doPost (Interpreter.java)

 // req contains  user response
  public void doPostQ( PrintWriter out, HttpServletRequest req,
  Recorder recordB ){
  if( recordB.terminated ){
  writeHead( out );
  out.println("<b>This questionnaire has been terminated</b>");
  return ;
  }
  String action = req.getParameter("action");
  String quesid = req.getParameter("quesid");
  if( !action.equals("Next") ){
  out.println("Unexpected state in Interpreter.doPost<br>");
  return ;
  }
  if( quesid.equals("intro") ){
  // this calls for generating first question
  // doIntro already set nowNode to first <Ques> node
  genQuest( out );
  return ;
  }
  // if here, not generating first question, examine request
  Element E = (Element) nowNode ;
  NodeList oplist = E.getElementsByTagName("Qopt");
  int type = lookUpType( E.getAttribute("type"));
  String lim = E.getAttribute("limit"); // ?
  String[] optS = req.getParameterValues("opt");
  recordB.record( quesid, type, optS );

Следующий шаг метода doPostQ, как показано в листинге 7.17, сводится к тому, чтобы определить, инициирует ли последний записанный ответ пользователя переход к новой ветви опроса. Естественно, новая ветвь начинается с первого вопроса в блоке, как указано в методе setBranch. Если переходить на новую ветвь не нужно, определяется место данного вопроса в текущем элементе Block и выполняется переход к следующему вопросу. Также нужно предусмотреть ситуацию, когда элемент, следующий за текущим вопросом, является завершающим; в этом случае мы вызываем метод getTerminal, функции которого описаны в следующем разделе и связаны с формированием последней страницы опроса.

Листинг 7.17. Метод doPost, продолжение (Interpreter.java)

 String branch  = branchLookUp( oplist, optS );
  if( branch != null ){
  //System.out.println("Taking Branch:" + branch );
  setBranch( branch );
// sets nowBlock and nowNode to new value
  if( nowNode == null ) genTerminal( out, recordB );
  else genQuest( out );
  return ;
  }
 // branch is null, nowBlock has 1 or more <Ques
  NodeList qlist =
((Element)nowBlock).getElementsByTagName("Ques");
  int n = 0 ;
  int nct = qlist.getLength();
  while( qlist.item(n) != nowNode &&
  n < nct ) n++ ;
  // n = nowNode
  Node nxtN = qlist.item(n+1);
  if( nxtN != null ){
  nowNode = nxtN ;
  genQuest( out );
  System.out.println("Found nextQ");
  return ;
  }
  if( terminal ) genTerminal( out, recordB );
  else out.println("nextQ NULL, not terminal<br>" );
  } // end doPostQ

Обработка элемента Terminal

Как показано в листинге 7.18, метод doTerminal выполняет две основные задачи. Во-первых, он формирует завершающую страницу опроса, используя либо текст, содержащийся в элементе Terminal, либо какую-нибудь стандартную фразу. Кроме того, он пробует найти атрибут altfile тега Terminal. Если этот атрибут указан, то его значение (путь доступа к файлу) используется методом Recorder для сохранения результатов опроса в заданном файле; в противном случае метод Recorder по умолчанию выполняет запись в файл, который задается в начале документа XML.

Листинг 7.18. Метод genTerminal (Interpreter.Java)

 // have reached  the end of a terminal block
  // note that a <Terminal>
 tag may have an altfile="filepathandname"
  // that replaces the default established in the file attribute of
  // the <Questionnaire> tag for this particular branch
  private void genTerminal( PrintWriter out, Recorder recordB ){
  NodeList nl = ((Element)nowBlock).getElementsByTagName
("Terminal");
  int ct = nl.getLength();
  String altfile = "" ;
  writeHead( out );
  if( ct == 0 ){
  out.println("Thank you for participating.<br>");
  }
  else { // use text from <Terminal>...</Terminal>
  Element E = (Element)nl.item(0); // only one <Terminal> tag
  out.println( E.getFirstChild().getNodeValue() );
  altfile = E.getAttribute("altfile");
  }
  try {
  recordB.terminal( altfile );
  }catch(IOException e ){
  out.println("Problem recording results, please notify
webmaster");
  }
  }

Вспомогательные методы класса Interpreter

В листинге 7.19 показаны вспомогательные методы класса Interpreter.

Листинг 7.19. Завершение кода класса Interpreter (Interpreter.Java)

 private Node  setQnodeInBlock( int n ){
  Element E = (Element) nowBlock ;
  NodeList nl = E.getElementsByTagName("Ques");
  nowNode = nl.item( n );
  return nowNode ;
  }
  //public String getTitle(){return title;}
  //public String getCSS(){return css ; }
  public String getIntro() {
  Element E = theDom.getDocumentElement(); // the root
  NodeList nl = E.getElementsByTagName("Intro");
  Element I = (Element)nl.item(0);
  nl = I.getChildNodes();
  int ct = nl.getLength();
  if( ct == 0 ) return "Bad Intro Data<br>" ;
  return nl.item(0).getNodeValue();
  }
 // return String if any chosen opt has a branch="", else null
  private String branchLookUp( NodeList oplist,String[] optS ){
  if( optS == null ||
  optS.length == 0 ) return null ;
  Hashtable opHash = new Hashtable();
  int i, ct = oplist.getLength();
  String val, branch ;
  for( i = 0 ; i < ct ;i++ ){
  val = ((Element)oplist.item(i)).getAttribute("val");
  branch = ((Element)oplist.item(i)).getAttribute("branch");
  opHash.put( val, branch ); // branch = "" if no attribute
  }
  if( opHash.size() == 0 ) return null ; // branch not possible
  for( i = 0 ; i < optS.length ; i++ ){
  branch = (String)opHash.get( optS[i] );
  if( branch != null &&
  branch.length() > 0 ) return branch ;
  }
  return null ;
  }
 public String toString()
  { StringBuffer sb = new StringBuffer("Interpreter ");
  return sb.toString() ;
  }
  }

Класс Recorder

Класс Recorder отвечает за хранение ответов пользователя на каждый вопрос и за их запись в файл для дальнейшего анализа. Каждому пользователю отводится экземпляр класса Recorder, который связан с данным сеансом и записывает ответы только этого пользователя.

Корневым элементом документа XML является тег Questionnaire, у которого имеется несколько атрибутов, используемых классом Recorder. Как показано в следующем примере тега Questionnaire, эти атрибуты называются title, author, date, method и file:

 <Questionnaire title="Survey 1"
author="WBB" date="May 19,  2000"
method="xml" file="e:\scripts\questionnaire\test result.xml" >

Атрибуты title, author и date просто записываются в класс Recorder и отображаются позже, но атрибуты method и file управляют действиями класса Recorder. Мы включили атрибут method для того, чтобы можно было записывать результаты опроса в формате, отличном от XML. Например, если вы предпочитаете использовать программу анализа таблиц, можете добавить методы, которые запишут результаты в виде строки текста, разделенного запятыми. Атрибут file задает путь к стандартному файлу, куда будут записываться результаты, если не назначен другой файл в теге Terminal.

В формате XML, который мы здесь используем, ответы каждого пользователя записываются в тег Qresults, для каждого вопроса отводится тег Ques, а для каждого выбранного ответа — тег Qopt. Конечно, такая структура является более объемной, чем запись в одну строку, но зато она очень гибкая. Получившаяся структура не является законченным документом XML, так как в ней отсутствует корневой элемент. Мы покажем, как решается этот вопрос в разделе «Варианты анализа анкеты».

В листинге 7.20 показаны инструкции импорта, переменные экземпляра и единственная статическая переменная класса Recorder. Статическая переменная filelock является объектом типа String, который используется в инструкции synchronized для того, чтобы гарантировать, что в каждый момент времени только один экземпляр объекта Recorder фактически осуществляет запись в файл.

Листинг 7.20. Начало исходного кода класса Recorder (Recorder.java)

package com.XmlEcomBook.Chap07;
import org.w3c.dom.* ;
  import com.sun.xml.tree.* ;
  import java.io.*;
  import java.util.* ;
  import javax.servlet.*;
  import javax.servlet.http.*;
public class Recorder
  { // this String is used to prevent more than one Recorder from
  // writing anywhere at the "same time"
  static String filelock = "RecorderLock" ;
 // these are instance variables
  String userid, usertype, sessionid ;
  String qresultStr ;
  String source ; // the xml file
  String method, output ; // how and where we save
  Hashtable record ; // one string per response
  public boolean terminated = false ;

Конструктор класса Recorder, показанный в листинге 7.21, вызывается из метода doGet сервлета. Он задает несколько переменных, характеризующих конкретного пользователя. Также он создает хэш-таблицу record, которая будет использоваться для записи ответов на вопросы.

В листинге 7.21 также показан метод setMethods, который определяет.метод записи результатов и атрибутов файла с помощью документа XML. Также он создает открывающий тег Qresults и сохраняет его в переменной qresultStr для дальнейшего использования.

Листинг 7.21. Конструктор класса Recorder и метод setMethods (Recorder.java)

 public Recorder(String  id, String typ, String ses,String src ){
  userid = id ; usertype = typ ; sessionid = ses ;
  source = src ;
  record = new Hashtable();
  }
 /* method information from <Questionnaire> attributes
  <Questionnaire title="First Test Quest"
author="wbb"  date="May 19, 2000"
  method="xml" file="e:\scripts\questionnaire\testresult.xml"  >
  */
  public void setMethods( Document doc ){
  NamedNodeMap nnm =
doc.getDocumentElement().getAttributes();
  method = nnm.getNamedItem("method").getNodeValue();
  output = nnm.getNamedItem("file").getNodeValue();
  // for xml method
  StringBuffer sb = new StringBuffer( 50 );
  sb.append("<Qresults source=\"");
  sb.append( source ); sb.append( "\" date=\"" );
  sb.append( new Date().toString() );
  sb.append( "\" userid=\""); sb.append(userid);
  sb.append( "\" usertype=\"" ) ; sb.append( usertype );
  sb.append( "\" sessionid=\""); sb.append( sessionid );
  sb.append("\">\r\n");
  qresultStr = sb.toString();
  }

В листинге 7.22 показаны методы terminal, record и toString. Метод terminal отвечает за запись собранных ответов данного пользователя в отведенный для этого файл. Метод toString используется как вспомогательный при отладке.

Метод record вызывается из метода doPost объекта Interpreter после того, как приходит ответ на очередной вопрос. Отметим, что если вам нужно будет создать какой-либо новый тип, требующий специального способа записи, метод record допускает переключение между типами вопросов. Например, если вам потребуется принимать введенный пользователем текст, он будет записан в раздел CDATA, чтобы в случае наличия в этом тексте каких-либо символов, имеющих специальное назначение в XML, не возникло бы затруднений.

При каждом вызове метода record создается строка, содержащая тег Ques, которая записывается в хэш-таблице record и ключом для которой служит переменная quesid. Поскольку хэш-таблица не сохраняет порядок добавления в нее элементов, очередность расположения тегов Ques в ней непредсказуема. Но это не страшно, поскольку для определения порядка следования вопросов мы можем использовать документ XML, содержащий сценарий опроса.

Листинг 7.22. Код класса Recorder, продолжение (Recorder.java)

 // called  when a <Terminal block is reached
  // if altdest is not "" this changes the default output
  public void terminal(String altdest ) throws IOException {
  if( altdest != null &&
  altdest.length() > 4 ) output = altdest ;
  if( output == null || output.length() < 5 ){
  System.out.println("QARG output is: " + output );
  return ;
  }
  terminated = true ;
  // write in append mode
  synchronized( filelock ){
  FileWriter fw = new FileWriter( output,true );
  PrintWriter pw = new PrintWriter( fw );
  pw.print( qresultStr );
  Enumeration e = record.elements();
  while( e.hasMoreElements() ){
  pw.print( (String)e.nextElement() ) ;
  }
  pw.print("</Qresults>\r\n");
  pw.close();
  } // end synchronized block
  }
  // recording format in xml
  /*<Qresults source=.... date= >
  <Ques id="start:1">
  <Qopt val="a"></Qopt><Qopt val="b"></Qopt>
  </Ques>
</Qresults>
  */
  public void record( String quesid, int type, String[] optS ){
  if( terminated ) return ; // prevent backing up from terminal Q
  // System.out.println("Start record: " + quesid );
  StringBuffer sb = new StringBuffer( 100 );
  sb.append("<Ques id=\"" ); sb.append( quesid );
sb.append("\"  >");
  switch( type ){
  case Interpreter.QMC :
  case Interpreter.QMCM :
  if( optS == null || optS.length == 0 ) break ;
  for(int i =0 ; i < optS.length ; i++ ){
  sb.append("<Qopt val=\""); sb.append( optS[i] );
  sb.append("\"></Qopt>");
  }
  break ;
  default :
  sb.append("UNKNOWN TYPE");
  }
  sb.append("\r\n</Ques>\r\n");
  String tmp = sb.toString();
  // note this will replace answer if user backed up with
browser back record.put( quesid, tmp );
  return ;
  }
 public String toString() // for debugging
  { StringBuffer sb = new StringBuffer( "Recorder user: " );
  sb.append( userid ); sb.append(" type: " );
  sb.append( usertype ); sb.append(" session: " );
  sb.append( sessionid );sb.append(" method: ");
  sb.append( method ); sb.append( " output: " );
  sb.append( output ); // how and where we save
  return sb.toString() ;
  }
}

В листинге 23 показаны результаты ответа одного пользователя на простой опрос. Атрибут source указывает, какой файл XML использовался для создания анкеты. В атрибут date записывается дата первого вхождения пользователя в систему и открытия страницы введения в анкету. Мы также включили атрибут sessionid для помощи в отладке, но, вероятно, без него можно обойтись.

Листинг 7.23. Запись результатов опроса одного пользователя на XML

 <Qresuits source="e:\scripts\javatest.xml"
date="Mon May 22 22:30:20 CDT 2000"
userid="unknown"
usertype="passed"
sessionid="9590526208594804">

<Ques id="studying:4"  >
 <Qopt val="a"></Qopt><Qopt va1="b"></Qopt>
<Qopt val="d"></Qopt><Qopt val=*e"> </Qopt>
<Qopt val="k"></Qopt><Qopt val="l"> </Qopt>
<Qopt val="m"> </Qopt>
</Ques>
 <Ques id="studying:3"><Qopt val="l"></Qopt>
</Ques>
<Ques id="studying:2"><Qopt val="2"> </Qopt>
</Ques>
<Ques id="studying:1" > <Qopt val="a"> </Qopt>
<Qopt val="f"></Qopt>
</Ques>
</Qresults>

Варианты анализа анкеты

Поскольку формат записи результатов опроса таков, что ответы каждого пользователя сохраняются в виде отдельной записи, имеется много вариантов анализа этих ответов. Для наших целей годится самый простой способ — составление таблицы по каждому вопросу, в которой для каждого варианта ответа указано количество выбравших его пользователей. Задействованные для составления таблиц классы отделены от сервлета, отвечающего за представление опроса в сети, и могут быть использованы для автономного создания HTML-страниц.

Первая проблема, которую предстоит решить, — это преобразование всех написанных сервлетом Questionnaire выходных файлов в формат, пригодный для анализа результатов опроса. Вспомним, что класс Recorder просто записывает теги <Qresults>, которые аккумулируются в выходном файле (или файлах). Нам нужно создать файл, в котором имелся бы корневой элемент. Фактически этот файл явился бы снимком (snapshot) собранных результатов опроса. Для этого программа, анализирующая результаты, должна выполнить следующие шаги.

  1. Получить объект org.w3c.dom.Document, содержащий сценарий опроса.

  2. Отыскать имена выходных файлов.

  3. Создать для каждого новый файл, объединяя корневые элементы с текущим содержимым выходного файла.

Класс для создания файлов снимков

Создание файлов снимков осуществляется классом PrepQxml, как показано в листинге 7.24 и следующих листингах. Конструктор берет объект XML Document и извлекает имена выходных файлов, которые являются значениями атрибута file в тегах Questionnaire и Terminal. Этот класс используется в сервлете, выполняющем анализ результатов (этот сервлет рассматривается далее в этой главе), но может использоваться также и в других процессах.

Листинг 7.24. Код класса PrepQxml (PrepQxml.java)

package com.XmlEcomBook.Chap07;
import com.XmlEcomBook.DOMlibrary ;
  import org.w3c.dom.* ;
  import com.sun.xml.tree.* ;
  import java.io.*;
  import java.util.* ;
public class PrepQxml
  {
  public int state = 1 ;
  Document doc ;
  public String primaryfile ;
  public String title ;
  public String author ;
  public String date ;
  String[] files ;
  Vector allfiles = new Vector() ;
  Hashtable prepHash = new Hashtable() ;
 PrepQxml( Document d ){
  doc = d ;
  Element E = doc.getDocumentElement();
  primaryfile = E.getAttribute("file");
  allfiles.addElement( primaryfile );
  title = E.getAttribute("title");
  author = E.getAttribute("author");
  date = E.getAttribute("date");
  NodeList terminals = E.getElementsByTagName("Terminal");
  int ct = terminals.getLength();
  // this locates any output files created by <Terminal> tags
  for( int i = 0 ; i < ct ; i++ ){
  E = (Element)terminals.item(i);
  String tmp = E.getAttribute("file");
  if( tmp.length() > 0 ) allfiles.addElement( tmp );
  }
  }
 public String[] getFiles(){ return files ; }

Для каждого выходного файла из опроса метод createFiles, показанный в листинге 7.25, вызывает метод makeXML, чтобы создать файл, у которого имеется открывающий и закрывающий теги <QResu1tSet>, необходимые для создания корневого элемента. Имя этого файла получается добавлением символов FMT к имени файла с результатами.

Листинг 7.25. Методы createFiles и makeXML (PrepQxml.java)

 // for every  file in allfiles, create a temporary with xml root
  // return array of file path/names
  public String[] createFiles() throws IOException {
  files = new String[ allfiles.size() ];
  int n = 0 ;
  Enumeration e = allfiles.elements();
  while( e.hasMoreElements() ){
  String tmp = (String)e.nextElement();
  files[n++] = tmp ;
  System.out.println("Create temporary for " + tmp );
  prepHash.put( tmp, makeXML( tmp ) );
  }
  return files ;
  }
  // fn is the name of the answers file, return the name of
  // the formatted file with root for creation of DOM
  public String getAnsXml( String fn ){
  return (String) prepHash.get( fn );
  }
  // this creates a complete XML document by adding a root
  // element to the Questionnaire output file contents
  private String makeXML(String fn )throws IOException {
  File inf = new File( fn );
  BufferedReader read = new BufferedReader
( new FileReader( inf ));
  int p = fn.lastIndexOf('.');
  String outFN = fn.substring( 0,p ) + "FMT" + fn.substring(p);
  File outf = new File( outFN );
  BufferedWriter bw = new BufferedWriter
( new FileWriter( outf ), 4096);
  PrintWriter write = new PrintWriter( bw );
  write.println( "<?xml version=\"1.0\" standalone=\"yes\"  ?>");
  write.println( "<!-- formatted Questionnaire results -->");
  write.println( "<QresultSet title=\"" + title + "\"  >");
  String tmp = read.readLine();
  int ct = 1 ;
  while( tmp != null ){
  write.println( tmp );
  tmp = read.readLine();
  }
  read.close();
  write.println("</QresultSet>");
  write.close();
  System.out.println("Created " + outFN );
  return outFN ;
  }
 public String toString()
  { StringBuffer sb = new StringBuffer("PrepQxml title: ");
  sb.append( title ); sb.append(" author: ");
  sb.append( author ); sb.append(" date: " );
  sb.append( date ); sb.append(" primary output: ");
  sb.append( primaryfile ); sb.append(" other files: " );
  sb.append( "none");
  return sb.toString();
  }
}

Класс для создания таблиц

Класс TallyQues использует интерфейс SAX для обработки документа QResultsSet. В результате этой обработки формируется таблица, в которой указывается, сколько раз встретился тот или иной ответ на данный вопрос. Выбор интерфейса SAX в данном случае очевиден — количество занимаемой памяти не зависит от общего числа ответов.

В листинге 7.26 показаны инструкции импорта, объявления классов, переменных и конструктор для класса TallyQues. Заметим, что мы основываем этот класс на классе Handler-Base пакета org.xml.sax, потому что в нем по умолчанию предусмотрены методы обработки событий. Все, что нам остается сделать, — переписать встроенные обработчики событий так, чтобы они обрабатывали те события, которые представляют для нас интерес.

Для работы класса TallyQues используется хэш-таблица, в которой каждому варианту ответа из полного набора вариантов по всем вопросам отводится свой элемент. Эти элементы являются экземплярами внутреннего класса Counter (см. листинг 7.30). Ключом для каждого элемента является атрибут id тега Ques вместе с атрибутом val тега Qopt.

Также у нас имеется вектор Vector с названием ordered (в котором хранится информация по всем вопросам в том порядке, в котором они следуют в анкете) и хэш-таблица qtext (которая содержит строки Qtext с текстом вопросов, снабженные атрибутами id тегов Quest в качестве ключей). Конструктор использует объект Document, соответствующий нашей анкете, для создания всех этих объектов.

Листинг 7.26. Начало кода класса TallyQues (TallyQues.java)

package com.XmlEcomBook.Chap07;
import java.io.* ;
  import java.util.* ;
  import org.w3c.dom.* ;
  import org.xml.sax.* ;
  import org.xml.sax.helpers.ParserFactory ;
  import com.sun.xml.parser.Resolver ;
/* org.xml.sax.HandlerBase is a convenience class that
  extends java.lang.Object and implements the SAX interfaces
  implements EntityResolver, DTDHandler,
DocumentHandler, ErrorHandler */
  public class TallyQues extends HandlerBase
  {
  static public String parserClass = "com.sun.xml.parser.Parser" ;
 private Hashtable tally = new Hashtable();
// Counters keyed by unique
  // ordered has a Vector of Counters per question
  private Vector ordered = new Vector();
  private Hashtable qtext = new Hashtable(); // <Qtext> by id
 public String tableStyle = "align=\"center\" border=\"3\"  " ;
  public String lastErr = null ;
  public int resultCt = 0 ;
  String id ; // <Ques> attribute "id" as detected during parse
  // constructor creates the vectors and hashtables to store results
  // qd is the questionnaire source XML doc
  public TallyQues( Document qd ){
  Element E = qd.getDocumentElement();
  NodeList qnl = E.getElementsByTagName("Ques");
  int ct = qnl.getLength();
  for( int i = 0; i < ct ; i++ ){
  Vector quesv = new Vector(); // for this <Ques>
  ordered.addElement( quesv );
  E = (Element)qnl.item(i); // Element is a <Ques>
  NodeList txn = E.getElementsByTagName("Qtext");
  String tx = txn.item(0).getFirstChild().getNodeValue();
// question text
  String id = E.getAttribute( "id" );
  qtext.put( id, tx );
  quesv.addElement( id ); // first element of quesv is the id
  NodeList opt = E.getElementsByTagName("Qopt");
  int opct =opt.getLength();
  for( int n = 0 ; n < opct ; n++ ){
  Element opE = (Element) opt.item(n);
  String val = opE.getAttribute("val");
  String text = opE.getFirstChild().getNodeValue();
  Counter cntr = new Counter( id, val, text );
  quesv.addElement( cntr );
  tally.put( cntr.unique, cntr );
  }
  }
  }

Обработка снимка опроса

Фактическая обработка файла, содержащего снимок результатов опроса, начинается с метода tallyAns. Как показано в листинге 7.27, работа этого метода состоит из следующих этапов.

  1. Файл открывается как объект org.xml .InputSource.

  2. Создается анализатор в соответствии со строкой parserClass (см. листинг 7.26). Мы используем анализатор из пакета Sun, но вы можете заменить его на любой подходящий вам анализатор.

  3. Объект TallyQues присоединяется к анализатору, с тем чтобы он мог получать вызовы методов обработки событий.

  4. Вызывается метод parse и начинается анализ.

Если не происходит никаких ошибок, то по выполнении метода parse таблица готова. Если происходит ошибка, метод tallyAns возвращает null, а если все прошло успешно, то возвращается переменная ordered.

Листинг 7.27. Метод tallyAns (TallyQues.java)

 // srcdoc  is complete path to a formatted answer set file
  public Vector tallyAns(String srcdoc ){
  Parser parser ;
  InputSource input ;
  try {
  File f = new File( srcdoc );
  input = Resolver.createInputSource( f );
  parser = ParserFactory.makeParser( parserClass );
  parser.setDocumentHandler( this );
  System.out.println("Start parse");
  parser.parse( input );
  }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();
  ordered = null ;
  }catch(Exception e){
  lastErr = e.toString();
  ordered = null ;
  }
  return ordered ;

Обработка событий с помощью интерфейса SAX

Теперь рассмотрим метод обработки событий, относящийся к методам интерфейса SAX и представленный в листинге 7.28. Из всех методов интерфейса SAX нам нужен только один — startElement. Для каждого тега Ques мы получаем значение атрибута id, которое используется вместе с тегами Qopt, содержащими варианты ответов на вопрос Ques Для каждого тега Qopt мы создаем строку, которая объединяет значение атрибута id вопроса со значением атрибута val элемента Qopt (варианта ответа) Эта строка используется как ключ для извлечения из хэш-таблицы tally соответствующего объекта класса Counter Таким образом подсчитывается, сколько раз встретился данный вариант ответа на данный вопрос анкеты

Листинг 7.28. Методы обработки событий SAX (TallyQues.java)

 // this is  the SAX specified "callback" called when the
  // parser detects an element
  public void startElement( String name, AttributeList attrib)
  throws SAXException {
  if( name.equals("Ques") ){
  id = attrib.getValue("id");
  }
  else {
  if( name.equals("Qopt") ){
  String unique = id + ":" + attrib.getValue("val");
  Counter cntr = (Counter)tally.get( unique );
  if( cntr != null ) cntr.countIt();
  }
  else {
  if( name.equals("Qresults"))resultCt++ ;
  }
  }
  }

Чтобы объединить всю статистическую информацию, полученную классом TallyQues, используется вектор ordered, в котором для каждого вопроса отведен свой элемент, причем эти элементы следуют в том же порядке, в котором расположены вопросы в исходном XML-сценарии анкеты Каждый такой элемент сам по себе также является вектором, в котором содержится идентификатор вопроса id (строка), а затем следуют объекты класса Counter для каждого из вариантов ответа В хэш-таблице qtext указан текст каждого вопроса, ключом к которому является идентификатор данного вопроса

Форматирование полученных результатов

Рассмотрим теперь, каким образом отображается полученная совокупность данных Метод formatAlly, приведенный в листинге 7 29, выводит для каждого вопроса HTML-таблицу, придерживаясь исходного порядка расположения элементов На рис 7 2 показана одна из таблиц, сформированная в результате опроса, который мы недавно проводили на нашем web-сайте в связи с экзаменом на получение сертификата программиста на Java

Листинг 7.29. Метод formatTally создает таблицу HTML (TallyQues.java)

 // assumes  that tallyAns was just run
  public void formatTally(PrintWriter out ){
  out.println("<center><h2>" + ordered.size() + " Questions  "
  + resultCt + " Responses</h2></center>");
  Enumeration e = ordered.elements();
  while( e.hasMoreElements() ){
  Vector v = (Vector) e.nextElement();
  String id = (String)v.firstElement();
  out.println("<center><h2>Question: " + id + "</h2>");
  out.println("<p>" + qtext.get(id) + "</p>" )  ;
  out.println("<table cols=\"3\"" + tableStyle + "  >");
  out.print("<tr>");
  out.print("<th>Val</th><th>Count</th><th>Short
Option Text</th>");
  out.println("</tr>");
  for( int i = 1 ; i < v.size(); i++ ){
  Counter c = (Counter) v.elementAt(i);
  out.print("<tr><td>" + c.val + "</td>");
  out.print("<td>" + c.count + "</td>" );
  out.println("<td>" + c.text + "</td></tr>");
  }
  out.println("</table></center><br><hr>");
  }
  }
 public String toString()
  { StringBuffer sb = new StringBuffer("TallyQues ");
  return sb.toString() ;
  }

Рис. 7.2. Отображение в браузере таблицы, сформированной методом fbrmatTally

Остается рассмотреть еще один компонент класса TallyQues — внутренний класс Counter. Как показано в листинге 7.30, в объект Counter входят идентификатор вопроса, значение атрибута val данного варианта ответа и текст варианта ответа. Этот текст мы ограничили по длине, чтобы таблица оставалась компактной, но вы легко можете снять это ограничение.

Листинг 7.30. Внутренний класс Counter (TallyQues.java)

 // counter objects represent a single question/option combo
 class Counter {
 public String val ;
 public String unique ; // <Ques id plus ":" plus <Qopt val
 public String text ; // the first counterTextLen chars
 public int count = 0 ;
 Counter( String id, String v, String tx ){
  val = v ;
  unique = id + ":" + val ;
  if( tx.length() > counterTextLen ) {
  text = tx.substring(0, counterTextLen);
  }
  else { text = tx ;
  }
  }
  public void countIt(){ count++ ; }
 public String toString(){
  return "ID: " + unique + " " + count + " " + text  ;
  }
  }
  }

Классы PrepQxm и TallyQues, рассмотренные в предыдущих разделах, можно по-разному использовать для создания таблиц в формате HTML. В нашем случае мы задействуем сервлет QanalyslsServ, описанный в следующем разделе.

Пример сервлета для просмотра результатов отчета

Сервлет QanalysisServ, рассматриваемый в этом разделе, предоставляет доступ по сети к снимку результатов проходящего опроса, поэтому его можно назвать сер- влетом для просмотра результатов отчета. Он использует файл questionnaire.pro- perties для поиска всех текущих опросов и предлагает вам выбрать один из них. Затем он определяет, какие выходные файлы генерируются в этом опросе, и предлагает выбрать один из них для формирования отчета.

В листинге 7.31 показаны инструкции импорта, статические переменные и метод init — начало кода сервлета.

Листинг 7.31. Сервлет QanalysisServ (QanalysisServ.java)

package com.XmlEcomBook.Chap07;
import com.XmlEcomBook.DOMlibrary ;
  import org.w3c.dom.* ;
  import com.sun.xml.tree.* ;
  import java.io.*;
  import java.util.* ;
  import javax.servlet.*;
  import javax.servlet.http.*;
public class QanalysisServ extends HttpServlet
  {
  static String brcrlf = "<br>\r\n" ;
  static String homedir = "e:\\scripts\\questionnaire" ;
  static String handler = "http://www.lanw.com/servlet/Qanalysis" ;
  static String passwd = "lovexml" ;
  static String version = "v1.0 May 28";
  Properties qProp ;
 // note we share properties file with QuestionnaireServ
  public void init(ServletConfig config) throws ServletException
  {
  super.init(config);
  System.out.println("Start QanalysisServ ");
  homedir = config.getInitParameter("homedir") ;
  File f = new File( homedir, "questionnaire.properties");
  try { qProp = new Properties();
  qProp.load( new FileInputStream(f) );
  String tmp = qProp.getProperty("analysis");
  if( tmp != null ) handler = tmp ;
  System.out.println("Loaded properties for Qanalysis: "
  + handler );
  }catch(IOException e){
  System.out.println("QanalysisServ Error loading " + e );
  }
  }

Входом в сервлет служит метод doGet, показанный в листинге 7.32. Обычно для входа используется форма, в которую требуется ввести пароль пользователя, просто чтобы избежать риска случайного доступа к сервлету постороннего пользователя. Предположим, что все прошло успешно; тогда этот метод генерирует страницу с формой, которая позволяет осуществить выбор среди всех имеющихся в наличии отчетов. Выбранный файл передается методу doPost.

Листинг 7.32. Метод doGet класса QanalysisServ (QanalysisServ.java)

 // entry  with password
  public void doGet(HttpServletRequest req,
HttpServletResponse resp)
  throws ServletException, IOException
  { System.out.println("Qanalysis doGet");
  resp.setContentType("text/html");
  PrintWriter out = new PrintWriter(resp.getOutputStream());
  String user = req.getParameter("username");
  String tmp = req.getParameter("userpw");
  // Obviously this could be a lot more complex
  if( !passwd.equals( tmp )){
  errorMsg( out, "404 page not found", null );
  return ;
  }
  if( qProp == null ||
  qProp.size() == 0 ){
  errorMsg( out, "Bad Initialization", null ); return ;
  }
  HttpSession session = req.getSession( true );
  session.putValue( "username", user );
  //
  Enumeration e = qProp.keys();
  Vector v = new Vector();
  while( e.hasMoreElements()){
  String key = (String)e.nextElement();
  // everything not "handler" or "analysis" is a XML file
path name
  if( !( key.equals("handler") || key.equals("analysis"))){
  v.addElement( key );
  }
  }
  if( v.size() == 0 ){
  errorMsg( out, "No Questionnaire files found", null );
  return ;
  }
  out.println("<HTML>");
  out.println("<HEAD><TITLE>QanalysisServ Output</TITLE>
</HEAD>");
  out.println("<BODY>");
  out.println("<h2>Select The Questionnaire XML File</h2>");
  out.println("Found " + v.size() + " XML files" + brcrlf  );
  out.println("<form method=\"POST\"
action=\"http://localhost/servlet/Qanalysis\"  >");
  out.println("<select name=\"source\" >");
  for( int i = 0 ; i < v.size() ; i++){
  tmp = (String) v.elementAt( i );
  out.println("<option value=\"" + tmp + "\" >"  + tmp );
  }
  out.println("</select>");
  out.println("<input type=\"hidden\" name=\"username\"
value=\""  + user + "\"><br>" );
  out.println("<input type=\"hidden\" name=\"action\"
 value=\"select\" ><br>");
  out.println("<input type=\"submit\" value=\"Start\"  ><br>" );
  out.println("</form>");
  footer( out );
  }

В первом запросе, выполняемом методом POST, значение переменной action равно select. Как показано в листинге 7.33, такой запрос инициирует вывод раскрывающегося списка всех доступных файлов с результатами. При этом используется метод createQList.

Листинг 7.33. Первая часть метода doPost (QanalysisServ.java)

 public void  doPost(HttpServletRequest req,
HttpServletResponse resp)
  throws ServletException, IOException
  {
  resp.setContentType("text/html");
  PrintWriter out = new PrintWriter(resp.getOutputStream());
  String source = req.getParameter( "source");
  String action = req.getParameter( "action");
  String ansfile = req.getParameter("ansfile");
  // select when choosing quesionnaire XML file
  // analyze when choosing reformatted result file
  String[] files = null ;
  if( action == null ||
  source == null || source.length() == 0 ){
  errorMsg(out,"Bad source selection", null );return ;
  } // source is short name from properties
  String srcfile = qProp.getProperty( source );
  if( srcfile == null ) {
  errorMsg( out, "Bad Source lookup", null ); return ;
  }
  HttpSession session = req.getSession(false);
  try {
  if( session == null ){ errorMsg(out, "No Session ", null );
  return ;
  }
  DOMlibrary lib = DOMlibrary.getLibrary();
  System.out.println("DOMlibrary ok, try for " + srcfile );
  Document doc = lib.getDOM( srcfile );
  if( doc == null ){
  errorMsg( out, "DOM doc failed - unable to continue", null );
  return ;
  }
  PrepQxml pQ = (PrepQxml)session.getValue("prepqxml");
  // substitute getAttribute if using 2.2 api
  header( out );
  if( pQ == null ){ // first pass
  pQ = new PrepQxml( doc );
  files = pQ.createFiles();
  session.putValue("prepqxml",pQ);
  }
  else {
  files = pQ.getFiles();
  }
  if( action.equals("select") ){
  out.println("<h1>Test: " + pQ.title +"</h1>"  );
  out.println("<p>XML questionnaire file: <i>"
+ source  + "</i></p>");
  out.println("<p>Author: " + pQ.author + " Dated: "
 + pQ.date + "</p>");
  out.println("<p>The primary answer file is: "
+ pQ.primaryfile  + "</p>" );
  out.println("<p>There " );
  if( files.length < 2 ) out.println("are no other ");
  if( files.length == 2 ) out.println("is one other ");
  if( files.length > 2 ) out.println( (files.length - 1) + " other ");
  out.println("answer file(s). Select a file and click
<b>Start</b></p>");
  createQList( out, source, files );
  }

Когда пользователь выберет один из файлов с результатами, переменная action принимает значение analyze. Как показано в листинге 7.34, это инициирует создание нового объекта TallyQues, который используется для формирования таблицы с результатами.

Листинг 7.34. Метод doPost, продолжение (QanalysisServ.java)

 if( action.equals("analyze")  ){
  out.println("<h1>Analysis</h1>");
  out.println("<p>XML questionnaire file: <i>"
+ source  + "</i></p>");
  String ansXml = pQ.getAnsXml( ansfile );
  out.println("<p>Answer file: " + ansfile + "</p>");
  out.println("<p>Processing file: " + ansXml + "</p>");
  TallyQues tQ = new TallyQues( doc );
// build with questions
  if( tQ.tallyAns( ansXml )== null ){
  out.println("<h2>Error " + tQ.lastErr + "</h2>")  ;
  }
  else {
  tQ.formatTally( out );
  }
  }
  footer( out );
  }catch( Exception ex ){
  errorMsg( out, "QanalysisServ.doPost ", ex );
  }
  }

Метод createQList, показанный в листинге 7.35, создает форму HTML, которая используется для вывода всех возможных файлов с ответами.

Листинг 7.35. Метод createList (QanalysisServ.java)

 // the PrepQxml  has located all of the answer files - only one
  // can be analyzed at at time
  void createQList( PrintWriter out, String source, String[] files ){
  out.println("<form method=\"POST\" action=
\"http://localhost/servlet/Qanalysis\"  >");
  out.println("<input type=\"hidden\" name=
\"action\"  value=\"analyze\" ><br>");
  out.println("<input type=\"hidden\" name=
\"source\"  value=\""
  + source + "\" ><br>");
  out.println("<select name=\"ansfile\" >");
  for( int i = 0 ; i < files.length ; i++){
  String tmp = files[i];
  out.println("<option value=\"" + tmp + "\" >"  + tmp );
  }
  out.println("</select>");
  out.println("<input type=\"submit\" value=\"Start\"  ><br>" );
  out.println("</form><br>");
  }

Мы почти завершили рассмотрение класса QanalysisServ! В листинге 7.36 показаны некоторые служебные методы, необходимые для форматирования выходных страниц и сообщений об ошибках.

Листинг 7.36. Служебные методы в классе QanalysisServ (QanalysisServ.java)

 public void  header( PrintWriter out ){
  out.println("<HTML>");
  out.println("<HEAD><TITLE>QanalysisServ Output</TITLE>
</HEAD>");
  out.println("<BODY>");
  }
  public void footer( PrintWriter out ){
  out.println("<hr>" + version + "<br>");
  out.println("</BODY>");
  out.println("</HTML>");
  out.close();
  }
  // assumes response has been set to text/html
  private void errorMsg
( PrintWriter out, String msg, Exception ex ){
  out.println("<html>");
  out.println("<head><title>QanalysisServ Output</title>
</head>");
  out.println("<body>");
  out.println("<h2>" ); out.println( msg );
  out.println("</h2><br>");
  if( ex != null ){
  ex.printStackTrace( out );
  }
  out.println("<br>");
  footer( out );
  }
  }

Служебная библиотека документа XML

Существует альтернативный подход к тому, чтобы обеспечить каждому сервлету доступ к соответствующему документу XML. Этот подход заключается в использовании служебных библиотек. В этом случае сервлет просто запрашивает документ из библиотеки независимо от того, находится ли файл XML на диске и где именно или он уже вызван в результате какого-либо другого запроса и находится в памяти.

Ниже перечислены характеристики созданного нами класса DOMlibrary.

  • Построен по шаблону единичного класса (singleton), который допускает создание только одного экземпляра класса. В такой схеме отсутствует открытый (public) конструктор, вместо него имеется статический метод, контролирующий создание единичного экземпляра класса и доступ к этому экземпляру.

  • Когда поступает запрос на документ XML, экземпляр DOMlibrary проверяет время создания файла, содержащего документ. Это гарантирует, что в ответ на запрос не будет выдан устаревший документ.

  • Реализует интерфейс Runnable, поэтому в нем может содержаться объект Thread, который периодически выполняет некоторые вспомогательные функции. Типичной вспомогательной функцией является исключение из памяти объектов Document, которые давно не использовались. Таким образом, постоянно задействованные документы, как правило, окажутся в памяти, в то время как редко используемые документы не будут занимать память, истощая ее ресурсы.

Вместо того чтобы использовать единичный класс, можно было бы реализовать все только через статические методы. Однако благодаря шаблону единичного класса мы выигрываем в отношении гибкости, получая, в частности, возможность реализовать интерфейс Runnabl e и использовать метод run для управления жизненным циклом документа в памяти. Шаблон единичного класса очень часто можно встретить в стандартной библиотеке Java.

В листинге 7.37 показан статический метод getLibrary, который при необходимости создает новый объект DOMlibrary. Все сервлеты, которым требуется доступ к документу XML, вызывают метод getLibrary для получения ссылки на единственный экземпляр библиотеки, а затем с помощью этой ссылки запрашивают нужный документ. Переменная maxAge используется в методе run для того, чтобы определить, когда документ следует убрать из памяти.

Листинг 7.37. Инструкции импорта и статические методы класса DOMlibrary (DOMIibrary.java)

 package com.XmlEcomBook  ;
import java.io.* ;
  import java.util.* ;
  import com.sun.xml.tree.* ;
  import com.sun.xml.parser.Resolver ;
  import org.xml.sax.* ;
  import org.w3c.dom.* ;
public class DOMlibrary implements java.lang.Runnable
  {
  private static DOMlibrary theLib ;
  private static int maxAge = 6000 ; // age in seconds
  public synchronized static DOMlibrary getLibrary(){
  if( theLib == null ) theLib = new DOMlibrary();
  return theLib ;
  }
  public static void setMaxAge(int t) { maxAge = t ;}

Как показано в листинге 7.38, единственный конструктор является закрытым (private), чтобы гарантировать, что только лишь статический метод getLibrary может создать новый объект. Резидентные объекты XML document хранятся в хэш- таблице domHash; ключом является путь к соответствующему файлу. Хэш-табли- ца с именем trackerHash, используя тот же ключ, сохраняет объект DomTracker для каждого объекта XML document. Класс DOMTracker — внутренний класс в DOMlibrary; его код приведен в листинге 7.43. Обратите внимание на то, что объекту Thread (потоку, выполняющему метод run), присвоен самый низкий приоритет.

Листинг 7.38. Конструктор и переменные экземпляра класса DOMlibrary (DOMIibrary.java)

 private Hashtable  domHash, trackerHash ;
  boolean running ;
  private String lastErr = "none" ;
  // private constructor to ensure singleton
  private DOMlibrary(){
  domHash = new Hashtable();
  trackerHash = new Hashtable();
  Thread upkeep = new Thread(this,"DOMlibrary upkeep");
  upkeep.setPriority( Thread.MIN_PRIORITY );
  running = true ;
  upkeep.start();
  }

Анализ документа XML в DOMlibrary осуществляется в методе loadXML, как показано в листинге 7.39. Чтобы избежать многократных попыток загрузить документ с неверно указанным атрибутом scr (путь к файлу) или документ, загрузка которого вызывает синтаксическую ошибку, этот метод помещает в таблицу domHash строку, содержащую сообщение об ошибке, если таковая встречается. Если анализ документа проходит успешно, в таблицу trackerHash записывается соответствующий объект DomTracker. Это единственный метод, в котором вызываются специфические для анализа документов методы; если бы вы вместо анализатора Sun использовали для анализа что-либо другое, вам потребовалось бы несколько модифицировать этот метод.

Листинг 7.39. Метод loadXML осуществляет анализ документа XML (DOMIibrary.java)

 private synchronized  void loadXML(File xmlFile, String src ) {
  //File xmlFile = new File( src ) ;
  try {
  long timestamp = xmlFile.lastModified();
  InputSource input = Resolver.createInputSource( xmlFile );
  // ... the "false" flag says not to validate (faster)
  // XmlDocument is in the com.sun.xml.tree package
  Document doc =
XmlDocument.createXmlDocument (input, false);
  domHash.put( src, doc );
  trackerHash.put( src, new DomTracker( timestamp ) );
  }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("loadXML threw " + lastErr );
  domHash.put( src, lastErr );
  se.printStackTrace( System.out );
  }catch( IOException ie ){
  lastErr = ie.toString();
  System.out.println("loadXML threw " + lastErr +
  " trying to read " + src );
  domHash.put( src, lastErr );
  }
  } // end loadXML

Когда сервлетам требуется получить документ, они вызывают метод getDOM, показанный в листинге 7.40. Если при создании документа возникают какие-либо проблемы, то вместо ссылки на документ этот метод возвращает null. Каждый раз, когда в хэш-таблице обнаруживается требуемый документ, в ассоциированном объекте DomTracker обновляется значение времени последнего использования, которое заменяется текущим временем. Заметим, что в нескольких местах создаются выходные сообщения, которые записываются в объект System.out и служат для отладки. Мы советует закомментировать их после того, как система заработает.

Листинг 7.40. Метод getDOM (DOMIibrary.java)

 // either  return the doc or null if a problem
  public synchronized Document getDOM( String src ){
  Object doc = domHash.get( src );
  DomTracker dt = (DomTracker) trackerHash.get( src );
  boolean newflag = false ;
  File f = null ;
  if( doc == null ){
  System.out.println("DOMlibrary.getDOM new " + src );
  f = new File( src );
  loadXML( f, src ); // sets trackerHash
  doc = domHash.get( src );
  dt = (DomTracker) trackerHash.get( src );
  newflag = true ;
  System.out.println("DOMlibrary load OK");
  }
  else { // found a document - is it up to date?
  f = new File( src );
  if( dt.changed( f )){
  System.out.println("DOMlibrary reloads " + src );
  loadXML( f, src ); // sets trackerHash
  newflag = true ;
  doc = domHash.get( src );
  dt = (DomTracker)trackerHash.get( src );
  }
  }
  // if not a document, must be a string due to error
  if( ! (doc instanceof Document )){
  System.out.println("DOMlibrary: " + doc );
  // could try for re-read here
  }
  if( doc instanceof Document ) {
  if( ! newflag ){
  dt = (DomTracker)trackerHash.get( src );
  dt.setLastUse( System.currentTimeMillis());
  }
  return (Document) doc ;
  }
  return null ;
  }

В листинге 7.41 представлена пара служебных методов, которые используются для удаления документа из памяти или для его перезагрузки.

Листинг 7.41. Некоторые служебные методы (DOMIibrary.java)

 // use this  to force removal of a dom. it
  // returns last copy of dom or null if dom not in hash
  public synchronized Document removeDOM( String src ){
  Document dom = (Document)domHash.get( src );
  if( dom != null ){
  domHash.remove( src );
  trackerHash.remove( src );
  // System.out.println("Removed " + src );
  }
  return dom ;
  }
 // call this to force a reload after src is modified
  public synchronized Document reloadDOM( String src ){
  if( domHash.get( src ) != null ){
  domHash.remove( src );
  trackerHash.remove( src );
  }
  return getDOM( src );
  }

Класс DOMlibrary должен реализовывать интерфейс Runnable, чтобы можно было использовать в фоновом режиме поток, имеющий минимальный приоритет и выполняющий служебные функции. Пример, приведенный в листинге 7.42, очень прост: поток удаляет все документы, которые давно не используются. Также в листинге 7.42 показаны служебные методы toString и getLastErr.

Листинг 7.42. Метод run и другие служебные методы (DOMIibrary.java)

 // run is  used for upkeep, not reading XML
  public void run()
  { while( running ){
  try{ Thread.sleep( 60000 );
  // example management code
  Enumeration keys = trackerHash.keys();
  long time = System.currentTimeMillis();
  while( keys.hasMoreElements() ){
  String key = (String) keys.nextElement();
  if(((DomTracker)trackerHash.get(key)).getAge(time) >
maxAge ){
  removeDOM( key );
  }
  }
  }catch(InterruptedException e){
  }
  }// end while
  }
 public String getLastErr(){ return lastErr ; }
 public String toString()
  { StringBuffer sb = new StringBuffer("DOMlibrary contains ");
  int ct = domHash.size();
  if( ct > 0 ){
  sb.append(Integer.toString( ct ) );
  sb.append( " DOM objects ");
  Enumeration e = domHash.keys();
  while( e.hasMoreElements() ){
  String key = (String)e.nextElement();
  sb.append( key );
  sb.append(" " );
  }
  }
  else { sb.append("no DOM objects");
  }
  sb.append(" Last error: " );
  sb.append( lastErr );
  return sb.toString();
  }

Экземпляр внутреннего класса DOMTracker создается всякий раз, когда загружается документ XML. Этот экземпляр подвергается обработке параллельно с самим объектом document. В нынешней версии нас интересуют только два параметра: время, когда документ XML был создан, и время, когда последний раз поступал запрос на этот документ. Как показано в листинге 7.43, метод getAge возвращает время в секундах, прошедшее с момента последнего использования документа, а метод changed проверяет время создания или последней модификации исходного файла.

Листинг 7.43. Определение класса DOMTracker как члена класса DOMIibrary (DOMIibrary.java)

 // utility  class to aid in tracking memory resident DOM
  class DomTracker {
  private long lastMod ;
  private long lastUse ;
  DomTracker( long timestamp ){
  lastMod = timestamp ; // from File.lastModified();
  lastUse = System.currentTimeMillis();
  }
  void setLastUse( long ts ){ lastUse = ts ; }
  int getAge( long now ){ // return value in seconds
  return (int)(( now - lastUse)/ 1000) ;
  }
  boolean changed( File f ){
  long n = f.lastModified();
  return !( n == lastMod );
  }
  }
  }

Используя инструментальные средства, описанные в этой главе, вы можете создать гибкую систему получения информации о ваших покупателях — как потенциальных, так и уже покупающих товары в вашем магазине. Поскольку эта система использует сценарий, основанный на XML и допускающий возможность изменения без перезапуска сервера, вы можете направлять опрос в то или иное русло в зависимости от мнений, которые высказывает клиент по ходу опроса.


  • Глава 8. Новости на сайте
    • Разработка системы показа новостей
      • Гибкость отображения
        • Элементы текстов сообщений
        • Расположение сообщений в зависимости от их новизны
        • Информация для управления сообщениями
        • Корневой элемент документа
      • Простота ввода данных
      • Минимальная нагрузка на сервер
    • Система показа новостей
    • Внешний вид web-страницы
      • Класс NewsFormatter
      • Использование класса NewsFormatter
        • Код для сервлета TheNewsServ
        • Пример JSP-страницы
    • Добавление свежих новостей
      • Сервлет CompanyNewsServ
      • Класс NewsUpKeep

Глава 8.

Новости на сайте

Вы, наверное, встречали web-сайты небольших организаций, в которых самые свежие новости относятся к событиям полугодовой давности. Эти организации теряют прекрасный шанс привлечь внимание случайных посетителей. Может быть, так происходит потому, что люди, находящиеся в курсе происходящих в фирме событий, не общаются с web-мастером своей организации или просто страницы с новостями слишком неудобно редактировать. Мы, естественно, собираемся предложить вам решение, основанное на технологии XML, которое мы подробно обсуждаем в этой главе.

Разработка системы показа новостей

Мы хотим предложить посетителям нашего web-сайта последние новости фирмы в компактном и удобном для чтения формате. Помните, что обычно в вашем распоряжении имеется всего лишь несколько секунд, чтобы привлечь внимание к вашему сайту случайного посетителя, который просто «прогуливается» по сети. Мы хотим, чтобы посетителю было понятно, как найти на сайте подробную информацию, если его что-то заинтересовало, как войти в систему каталога и, будем надеяться, что-нибудь приобрести.

Если вы подумаете, какими характеристиками должна обладать система показа новостей, в ваш список, вероятно, войдут следующие пункты:

  • гибкий способ отображения;

  • удобный ввод данных;

  • минимальная нагрузка на сервер.

Наряду с обеспечением этих свойств при разработке системы вы, в первую очередь, должны будете решить, какие элементы и атрибуты XML использовать для представления данных, затем сделать выбор между моделями DOM и SAX (механизмами отображения данных для пользователя) и, наконец, определить механизм добавления новых сообщений.

Гибкость отображения

Поскольку мы решили остановиться на XML как на исходной среде хранения данных, мы можем обеспечить гибкость отображения посредством хранения данных, необходимых для всех доступных способов представления информации. Мы имеем в виду следующее: вероятно, вы замечали, что на разных коммерческих web-сайтах новости компании представлены обычно одним из трех способов:

  • заголовки новостей со ссылкой на полный текст;

  • несколько наиболее интригующих строк со ссылкой на полный текст;

  • полный текст сообщения.

Существуют и другие форматы сообщений: электронный информационный бюллетень, печатная копия информационного бюллетеня, корпоративные сообщения. Для этих форматов также применимы описанные выше три способа (заголовок, краткое сообщение и полный текст). Первая наша задача при разработке системы отображения новостей — создать такой документ XML, который поддерживал бы все три формата.

Элементы текстов сообщений

Хотя в области искусственного интеллекта и понимания компьютером естественных языков сделаны большие успехи, никто не рассчитывает, что компьютер напишет хороший заголовок, проанализировав текст сообщения. Поэтому приходится согласиться с тем, что нужен человек, который для каждого способа представления новостей создаст отдельный заголовок. В сообщении обычно указывается дата, а иногда рядом еще и место, где произошло то событие, о котором идет речь, например: «Остин, Техас, 1 января 2000». Эта задача тоже должна выполняться человеком.

Некоторые сообщения очень выигрывают, когда сопровождаются графикой, звуковыми клипами или ссылками на другие сайты, поэтому продумайте, как сконструировать документ XML, чтобы его можно было дополнить различными элементами, способствующими увеличению привлекательности страницы новостей. Мы решили, что было бы слишком сложно и неудобно снабжать систему показа новостей в нашем примере всеми возможными «украшениями». Поэтому мы будем хранить тексты сообщений для второго и третьего способов (краткое и полное сообщение), используя тег XML <[[CDATA...]]>.

Поскольку анализаторы XML не пытаются анализировать текст, содержащийся внутри раздела СВАТА, в этот раздел вы можете поместить любую разметку HTML, не сбивая с толку анализатор.

Элементы, содержащие дату, заголовок, краткое сообщение и полный текст, показаны в листинге 8.1.

Листинг 8.1. Дата, заголовок, короткое и полное сообщения (thenews.xml)

 <date>Austin, TX, Jun 14 2000</date>
 <head>Best Seller at a Great Price</head>
 <short>
 <![CDATA[Due to a special deal with the publisher,
 we can now offer
 <i>Dryer Lint Art</i>
 at 50% off the retail price.]]>
 </short>
 <long>
 <![CDATA[This books starts with simple Dryer Lint projects

suitable for the novice and advances through easy

stages tothe  (literally)
 <b>monumental</b>
 recreation of famous monuments in that most flexible of craft

materials, dryer lint.  Even though you may never attempt major

constructions like the Statue of Liberty project documented in

the final chapter,  your projects will benefit by a study of this

famous creation.  Includes DHL diagrams.]]>
 </long>

Другим аспектом гибкости является способность выборочно представлять сообщения в соответствии с темой, интересующей посетителя. Предполагая, что спектр возможных интересов посетителей сайта XMLGifts.com очень широк, мы хотим показать каждому посетителю те новости, которые связаны с его излюбленной темой, и в том месте сайта, куда он с наибольшей вероятностью заглянет. В такой структуре неизбежны перекрывающиеся области; например, книга о музыкальной группе может оказаться интересной как для покупателей книг, так и для покупателей музыкальных компакт-дисков. Следовательно, каждое сообщение должно быть снабжено одной или несколькими пометками, которые указывают, к каким тематическим категориям можно его отнести; а формат представления новостей должен допускать переключения между различными темами сообщений.

Для того чтобы пометить сообщение и отнести его тем самым к определенной категории, мы можем использовать элемент или атрибут. Следуя советам, приведенным в разделе «Элементы или атрибуты?» главы 2, можно заключить, что в данном случае лучше использовать атрибуты, так как тема сообщения — это данные о содержимом элемента, и мы предполагаем, что количество тем сообщений будет ограниченным.

Расположение сообщений в зависимости от их новизны

Самые свежие новости должны располагаться первыми. Так как документ XML автоматически сохраняет порядок следования элементов, новые элементы должны добавляться к началу документа. Более того, было бы неплохо предусмотреть возможность отображения только самых свежих новостей. Следовательно, нам нужен способ представления «возраста» сообщений.

После долгих колебаний между многочисленными способами представления даты, которые позволили бы нам отображать только недавние сообщения, мы остановились на использовании простого целочисленного представления количества дней, прошедших с 1 января 1970 года. Для этого значение типа long, возвращаемое методом System.currentTimeMillisO, делится на количество миллисекунд в сутках, и полученное число становится значением атрибута timestamp тега News item. Альтернативные варианты — использование классов Java DateFormat или Calendar — были отвергнуты, так как они подразумевают создание большого количества объектов, а мы хотим, чтобы показ новостей создавал минимальную нагрузку на сервер.

Информация для управления сообщениями

Поскольку вы или ваши служащие будут обновлять страницу новостей в режиме подключения к сети, было бы полезно отслеживать, кто какое сообщение написал. (Например, чтобы знать, кто должен получать нагоняй за допущенную ошибку.) Для этого используется атрибут <author> элемента <Newsitem>. Сервлет обновления сообщений, описанный в разделе «Добавление свежих новостей» этой главы, обладает простым механизмом контроля доступа, в котором используются имя автора и пароль; этот то самое имя автора, которое становится значением атрибута author.

Чтобы создать документ HTML, в котором заголовок содержит ссылку на полную версию текста, нужно использовать уникальный идентификатор. Мы, например, выбрали простейший вариант: при создании каждого сообщения <NewsItem> ему присваивается серийный номер, который и становится значением его атрибута id.

Корневой элемент документа

Мы отслеживаем некоторые параметры, используемые во всем файле, с помощью атрибутов, которые задаются в корневом элементе документа, Newsfile. Очередной атрибут id — это просто next id. Присваивание нового значения каждому следующему атрибуту nextid является обязанностью программы, добавляющей новые сообщения или, в случае редактирования в автономном режиме, автора элемента Newsitem.

Корневой элемент также является подходящим местом для хранения атрибутов, связанных с различными заданными по умолчанию параметрами отображения. В приведенном ниже примере имеется только один такой атрибут, longtemplate, который идентифицирует заданный по умолчанию файл — шаблон HTML, используемый для форматирования сообщений.

В листинге 8.2 показано, как используются все перечисленные теги.

Листинг 8.2. Элемент <Newsfile> с одним элементом <Newsitem> (thenews.xml)

 <?xral version="l.О"  standalone="yes"  ?>

 <!-выходные данные NewsllpKeep -->
 <Newsfile longtemplate="tmlong.html" nextid="1010">

 <Newsitem timestamp="11045" topic="CDs"
                         author="wbrogden" id="1008" >

 <head>Your Favorite Music Now Available</head>

 <date>Austin, Feb 1, 2000</date>

 <short>
 <![CDATA[XMLGifts proudly announces the availability of the

CD that has  all the geeks singing,

<i>It's Dot Com Enough for He.</i>]]>

 </short>

 <long>
 <![CDATA[<p>
 <i>It's Dot Com Enough For He.</i>
 now in stock!</p>
 <p>All those great songs created during breaks in all-night
coding  sessions - now recorded by top Silicon Valley garage
bands on our private  label. <i>It's Dot Com Enough for Me</i>
will have you singing along - or  maybe laughing till the Jolt cola
spurts out your nose. Seventeen songs  from geeks at Sun.
Microsoft, Apple. Cisco, and other top tech outfits.
</P>]]>
 </long>
 </Newsitem>
 </Newsfile>

Простота ввода данных

Поскольку мы выбрали простой формат новостей, показанный в листинге 8.2, ввод данных осуществляется тоже достаточно просто. Система, основанная на формах HTML и сервлетах, описанная в разделе «Добавление свежих новостей» этой главы, позволяет добавлять новые сообщения через Интернет.

Тем не менее легкость ввода данных никак не связана с контролем за качеством самого текста. Для достижения лучших результатов следует убедиться, что темы, согласно которым классифицируются сообщения, и стиль текста всегда согласуются между собой. Также необходимо составить список требований и рекомендаций по составлению текстов сообщений и убедиться в том, что эти списки доступны всем служащим фирмы, уполномоченным размещать сообщения на сайте.

Минимальная нагрузка на сервер

Поскольку мы надеемся, что посещаемость нашего сайта будет достаточно велика, мы хотим, чтобы отображение главной страницы выполнялось по возможности просто, то есть чтобы страница быстро загружалась, а ее генерирование не требовало больших затрат ресурсов со стороны сервера. Рассмотрим следующие альтернативные варианты отображения новостей.

  • Статические страницы новостей (Static News Pages). Статические страницы быстро загружаются, и главная страница благодаря XML может формироваться заново при появлении нового сообщения. Однако использование статических страниц исключает возможность их индивидуальной настройки для постоянных посетителей.

  • Страницы новостей, генерируемые сервлетами (Servlet-Generated News Pages). Сервлеты Java могут генерировать все, что потребуется; при этом предполагается, что они используют файлы с шаблонами HTML для регулировки многочисленных атрибутов внешнего вида сайта.

  • JSP-страницы (JavaServer Pages). Преимущество JSP-страниц перед сервлетами заключается в том, что для изменения внешнего вида страницы web-дизайнеру не нужно уметь программировать на Java.

При выборе методов обработки следует сделать выбор между моделями SAX и DOM, а также между анализаторами XML, проверяющими либо не проверяющими допустимость документа. Модель SAX подразумевает работу анализатора для доступа к каждой странице. При использовании модели DOM доступ к страницам осуществляется быстрее, но зато все приходится хранить в памяти. Предполагая, что количество новостей будет не больше нескольких сотен, затраты ресурсов на хранение всей модели DOM в памяти не окажутся слишком значительными, поэтому мы считаем, что в данном случае модель DOM является безусловно оптимальным вариантом.

Мы не видим никаких причин в использовании анализатора, проверяющего допустимость документа. Так как новые сообщения будут создаваться автоматически, вероятность ошибки форматирования весьма невелика. Кроме того, вряд ли ошибки, связанные с недопустимостью документа, могут смутить пользователя.

Система показа новостей

Окончательный вариант устройства системы показа новостей изображается блок-схемой, приведенной на рис. 8.1. Обработка информации, происходящая на сервере, представлена в правой части блок-схемы, а обработка в автономном режиме — в левой части. Исходный файл XML может редактироваться как в режиме подключения к сети, так и автономно, но предпочтительным является режим подключения, так как он позволяет автоматически задавать атрибуты id и timestamp.

Рис. 8.1. Обработка сообщений

Объектная модель документа поддерживается в памяти с помощью класса DOMlibrary, который был описан в главе 7. Обновление исходного файла XML в режиме подключения выполняется сервлетом CompanyNewsServ и классом NewsUpKeep, которые обсуждаются далее в этой главе. Формирование web-страниц новостей на основе текстов сообщений осуществляется сервлетами или JSP-страницами, использующими класс NewsFormatter, который обсуждается в следующем разделе. Для создания информационных бюллетеней и печатной версии требуются другие форматы, которые легко написать, основываясь на приведенных ниже примерах.

Внешний вид web-страницы

Прежде чем мы углубимся в рассмотрение кода нашей системы показа новостей, давайте посмотрим, как может выглядеть разрабатываемая страница. На рис. 8.2 показана web-страница, посвященная новостям фирмы, в контексте более крупного сайта. Мы не включили в нее навигационный интерфейс и другие элементы, которые должны присутствовать в реальном сайте.

Рис. 8.2. Web-страница новостей, генерируемая с помощью JSP-страницы

JSP-страница, используя класс NewsFormatter, который подробно описывается в следующем разделе, сгенерировала показанную на рисунке страницу на основе файла XML. В левой части экрана приводятся заголовки последних новостей по всем темам. Самое свежее сообщение приводится полностью в центре страницы. Справа показаны новости в кратком изложении, но первая из них (полная версия которой расположена по центру) пропущена. При щелчке мышью на заголовке или кратком изложении сообщения появляется полная версия этого сообщения.

Класс NewsFormatter

Ключевым классом Java для формирования новостных сообщений является класс NewsFormatter. Как показано в листинге 8.3 и следующих листингах, класс NewsFormatter включает в себя объект Fi I e, который указывает на исходный файл XML. Класс NewsFormatter использует класс DOMlibrary, описанный в главе 7, чтобы получить объект Document, содержащий все данные из файла XML. Конструктор получает список узлов NodeList, содержащий все узлы Newsitem, и задействует его для создания массива с названием itemNodes. Этот массив требуется для решения различных задач форматирования.

Листинг 8.3. Начало кода класса NewsFormatter

package com.XmlEcomBook.Chap08;
import com.XmlEcomBook.DOMlibrary ;
  import java.io.*;
  import java.util.* ;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import org.w3c.dom.* ;
public class NewsFormatter
  {
  static String handler ; // the servlet for single item presentation
  public static void setHandler(String s){handler=s; }
 // instance variables
  File newsFile ;
  String newsFileName ;
  String newsFilePath ;
  String headStr, footStr ;
 Node[] itemNodes ;
  Element docRoot ;
  Hashtable nodeHash ; // <Newsitem Elements keyed
by tag name
 int maxNitems, skipNitems;
  int itemsCount = 0 ;
 public NewsFormatter( File f ) throws IOException {
  newsFile = f ;
  newsFileName = f.getAbsolutePath() ;
  int p = newsFileName.lastIndexOf( File.separatorChar );
  if( p > 0 ){ newsFilePath = newsFileName.substring(0,p);
  }
  else { System.out.println("NewsFormatter path problem");
  }
  DOMlibrary library = DOMlibrary.getLibrary();
  Document doc = library.getDOM( newsFileName );
  if( doc == null ){
  throw new FileNotFoundException( newsFileName );
  }
  docRoot = doc.getDocumentElement();
  NodeList newsItemNodes =
doc.getElementsByTagName("Newsitem");
  int ct = newsItemNodes.getLength();
  itemNodes = new Node[ ct ];
  for( int i = 0 ; i < ct ; i++ ){
  itemNodes[i] = newsItemNodes.item( i );
  }
  }

Вы, должно быть, помните из главы 7, что класс DOMIibrary перезагружал файл XML, если время его последней модификации изменялось. Поскольку в нашем случае объект Document не меняется в результате действия класса NewsFormatter, он может использоваться совместно любым количеством сервлетов и доступ к нему нужно синхронизировать.

У нас имеются две версии метода doNews. Версия, приведенная в листинге 8.4, используется для вывода нескольких сообщений в виде заголовков новостей, краткого и полного форматов изложения. Эта версия метода обеспечивает следующие возможности: выбор сообщений по их тематике и времени появления, пропуск указанного количества сообщений и ограничение общего количества отображаемых сообщений. Строки hs и fs — необязательные параметры, которые обеспечивают некоторые небольшие дополнительные возможности форматирования.

Метод doNews проверяет наличие параметров типа Srting, которые ограничивают выбор сообщений определенными тематическими или временными рамками. Если параметр topstr отличен от null и не пуст, вызывается метод selectNodes, который ограничивает полный список сообщений набором новостей, соответствующим заданной тематике. Аналогично, если указана строка age, вызывается метод limitAge. Если какой-либо из этих методов сокращает список сообщений до нуля, метод doNews сразу же прекращает свое выполнение. Другие параметры контролируют максимальное количество новостей на странице и относительный номер сообщения, с которого начинается их просмотр.

Листинг 8.4. Метод doNews выбирает способ представления сообщений (NewsFormatter.java)

 // hs and  fs are head and foot used in short and long display
  // you can also specify templates in the <Newsfile element
  // PrintWriter, hs, fs, topics, H,S or L, age, mx#
  // skpN is used to skip the first N items that qualify
  // presumably printed elsewhere on the page, use 0 to see all
  // returns number of news items printed
  public int doNews( PrintWriter out, String hs, String fs,
  String topstr, String sz, String age, int skpN, int mxN ){
  headStr = hs ; footStr = fs ;
  skipNitems = skpN ; maxNitems = mxN ;
  itemsCount = 0 ;
  if( topstr != null && topstr.length() > 0 ){
  if( selectNodes(topstr, out )== 0 ) return 0 ;
  }
  if( age != null && age.length() > 0 ){
  if( limitAge( age, out ) == 0 ) return 0 ;
  }
  char szch ;
  if( sz == null || sz.length() == 0 ) szch = 'L' ;
// default to long form
  else szch = sz.toUpperCase().charAt(0);
  switch( szch ) {
  case 'H' :
  doHeadlineNews( out ); break ;
  case 'S' :
  doShortNews( out ); break ;
  case 'L' :
  default :
  doLongNews(out );
  }
  return itemsCount ;
  }

Метод doNews, показанный в листинге 8.5, отыскивает сообщение по указанному атрибуту id и форматирует полную версию сообщения. Оставшийся метод класса NewsFormatter предназначен для поддержки двух методов doNews.

Листинг 8.5. Версия doNews для одного выбранного сообщения (NewsFormatter.java)

 // version  to do a single item by id - always full length
  public int doNews( PrintWriter out, String hs, String fs,
 String id ){
  headStr = hs ; footStr = fs ;
  itemsCount = 0 ;
  Node n = null ; //
  for( int i = 0 ; i < itemNodes.length ; i++ ){
  n = itemNodes[i]; // <Newsitem nodes
  String nid = ((Element)n).getAttribute("id");
  if( id.equals( nid )){
  break ;
  }
  } // if not located by id, will be oldest item
  findNodes((Element) n ); // locates the parts of <Newsitem
  doNewsItemLong( out ); // with the single id
  return itemsCount ;
  }

Мы решили, что заголовки сообщений всегда будут форматироваться как маркированные списки (unordered lists) HTML. Это очень упрощает метод doHeadli - neNews, показанный в листинге 8.6.

Листинг 8.6. Метод, форматирующий список заголовков новостей (NewsFormatter.java)

 // Headline  always formatted as <UL> with link
  public void doHeadlineNews(PrintWriter out){
  out.println( "<ul>" );
  for( int i = skipNitems ; i < itemNodes.length ; i++ ){
  if( i >= maxNitems ) break ;
  Node n = itemNodes[i]; // <Newsitem nodes
  String id = ((Element)n).getAttribute("id");
  findNodes((Element) n ); // locates the parts of <Newsitem
  out.print("<li><a href=" + handler + "?id=" + id  + "&size=L >" );
  out.print( nodeHash.get("head") );
  out.println("</a></li>");
  }
  out.println("</ul>");
  }

Метод doShort, показанный в листинге 8.7, проверяет наличие заданного по умолчанию шаблона форматирования короткой версии сообщения, а затем выводит эту версию на страницу. Обратите внимание на то, что из каждого элемента (сообщения) извлекается его атрибут id, прежде чем будет вызван метод doNewsItemShort. Этот идентификатор впоследствии присоединяется к каждому элементу, представляющему собой краткую версию, в качестве ссылки на полный текст сообщения.

Листинг 8.7. Метод doShortNews (NewsFormatter.java)

 public void  doShortNews(PrintWriter out){
  NamedNodeMap attrib = docRoot.getAttributes();
  Node n = attrib.getNamedItem( "shorttemplate") ;
  String template = null ;
  if( n != null ) template = n.getNodeValue();
  if( headStr == null &&
  template != null &&
  template.length() > 2 ){
  try {
  setFromTemplate( template );
  System.out.println("Template set ok " + headStr + footStr );
  }catch(IOException ie ){
  System.out.println("Unable to read " + template );
  }
  }
  out.println( headStr );
  for( int i = skipNitems ; i < itemNodes.length ; i++ ){
  if( i >= maxNitems ) break ;
  n = itemNodes[i]; // <Newsitem nodes
  String id = ((Element)n).getAttribute("id");
  findNodes((Element) n ); // locates the parts of <Newsitem
  doNewsItemShort( out, id );
  }
  out.println( footStr );
  }

Как показано в листинге 8.8, метод doLongNews проверяет наличие заданного по умолчанию шаблона форматирования полной версии сообщения, после чего выполняет цикл по всем сообщениям в массиве itemNodes.

Листинг 8.8. Метод doLongNews выводит полный текст сообщения (NewsFormatter.java)

 public void  doLongNews(PrintWriter out){
  NamedNodeMap attrib = docRoot.getAttributes();
  Node n = attrib.getNamedItem( "longtemplate");
  String template = null ;
  if( n != null ) template = n.getNodeValue();
  if( headStr == null &&
  template != null &&
  template.length() > 2 ){
  try {
  setFromTemplate( template );
  System.out.println("Template set ok " + headStr + footStr );
  }catch(IOException ie ){
  System.out.println("Unable to read " + template );
  }
  }
  out.println( headStr );
  for( int i = skipNitems ; i < itemNodes.length ; i++ ){
  if( i >= maxNitems ) break ;
  n = itemNodes[i];
  findNodes((Element) n );
  doNewsItemLong( out );
  }
  out.println( footStr );
  }

В листинге 8.9 показан метод limitAge, который вызывается всегда, когда в методе doNews присутствует строка, задающая максимально допустимый «возраст» новостей. После проверки корректности целочисленного значения, содержащегося в строке age, этот метод заново компонует массив itemNodes, помещая туда только выбранные сообщения.

Листинг 8.9. Метод, выбирающий сообщения по дате их создания (NewsFormatter.java)

 // limit  to only most recent entries - return number, may be zero
  private int limitAge(String age, PrintWriter out ){
  int days = 100 ;
  try {
  days = Integer.parseInt( age );
  if( days <= 0 ) days = 1 ;
  }catch(NumberFormatException nfe){
  return itemNodes.length ; // no change
  }
  int today =(int)( System.currentTimeMillis()
 /( 24 * 60 * 60 * 1000));
  int oldest = today - days ;
  Vector v = new Vector( itemNodes.length );
  int nidate = today ; // in case of parse problem
  int i ;
  for( i = 0 ; i < itemNodes.length ; i++ ){
  Node n = itemNodes[i]; // <Newsitem nodes
  String t = ((Element)n).getAttribute("timestamp");
  try { nidate = Integer.parseInt( t );
  }catch(Exception nfe){ // number format or null pointer
  System.out.println( "NewsFormatter.limitAge " + nfe );
  }
  if( nidate >= oldest ){
  v.addElement( n );
  }
  }
  itemNodes = new Node[ v.size() ]; // may be zero
  for( i = 0 ; i < v.size(); i++ ){
  itemNodes[i] = (Node) v.elementAt(i);
  }
  return itemNodes.length ;
  }

Причина сложности метода selectNodes заключается в том, что и параметр topics этого метода, задающий выбор тем сообщений, и атрибут topic каждого сообщения могут содержать как одну, так и несколько тем, разделенных запятыми. Как показано в листинге 8.10, мы строим хэш-таблицу recognize для ускорения распознавания тем.

Листинг 8.10. Метод, который выбирает сообщения по указанным темам (NewsFormatter.java)

 // based  on String with topics separated by commas
  // example attribute topics="general,books,java"
  // output capability only used for debugging
  private int selectNodes(String topics, PrintWriter out ){
  Hashtable recognize = new Hashtable();
  StringTokenizer st = new StringTokenizer
( topics.toUpperCase(), ",");
  while( st.hasMoreTokens()){
  String tmp = st.nextToken().trim();
  recognize.put( tmp,tmp );
  }
  // hashtable can now be used to recognize selected topics
  Vector v = new Vector( itemNodes.length );
  int i ;
  for( i = 0 ; i < itemNodes.length ; i++ ){
  Node n = itemNodes[i]; // <Newsitem nodes
  String t = ((Element)n).getAttribute("topic");
  st = new StringTokenizer(t.toUpperCase(),",");
  while( st.hasMoreElements()){
  // we just use hashtable get to see if topic is present
  if( recognize.get( st.nextToken().trim() ) != null ){
  v.addElement(n);
  break;
  }
  } // end while over topic list
  } // end loop over all nodes
  // build new array from selected nodes
  itemNodes = new Node[ v.size() ];
  for( i = 0 ; i < v.size(); i++ ){
  itemNodes[i] = (Node) v.elementAt(i);
  }
  return itemNodes.length ;
  }

Метод findNodes, показанный в листинге 8.11, вызывается для каждого сообщения, которое должно быть помещено на страницу. Входной элемент Element — это узел Newsltem документа XML. Метод findNodes создает переменную nodeHash, которая позволяет другим методам извлекать дочерние элементы Newsltem, например <short>, из коллекции nodeHash. Ключами элементов в этой хэш-таблице являются имена узлов.

Листинг 8.11. Метод findNodes класса NewsFormatter (NewsFormatter.java)

 // locate  the nodes that are Elements for text data
  private void findNodes( Element ne ){
  NodeList nl = ne.getChildNodes(); // all nodes
  int ct = nl.getLength();
  nodeHash = new Hashtable( 2 * ct );
  for( int i = 0 ; i < ct ; i++ ){
  Node n = nl.item(i);
  if( n instanceof Element ){
  nodeHash.put( n.getNodeName(), n );
  }
  }
  }

Заголовки и краткая версия сообщения всегда снабжаются ссылкой на полную версию. Эта ссылка встраивается в HTML-страницу с помощью методов doNewsItemHead и doNewsItemShort, как показано в листинге 8.12.

Листинг 8.12. Методы doNewsItemHead и doNewsItemShort (NewsFormatter.java)

 // <Newsitem  has been hashed, id is attribute
  private void doNewsItemHead( PrintWriter out, String id ){
  out.print("<a href=" + handler + "?id=" + id + "&size=L  >" );
  out.print("<h3>"); out.print( nodeHash.get("head")  );
  out.println("</h3></a>");
  out.println();
  }
 // <Newsitem has been hashed, id is attribute
  // output with <p>..</p> formatting
  private void doNewsItemShort( PrintWriter out, String id ){
  // note anchor to full item display
  out.print("<a href=" + handler + "?id=" + id + "&size=L  >" );
  out.print("<h3>"); out.print( nodeHash.get("head")  );
  out.println("</h3></a>");
  Element de = (Element)nodeHash.get("date");
  out.print( de.getFirstChild() );
  out.println("</p>");
  Element ne = (Element)nodeHash.get("short");
  String wrk = ne.getFirstChild().getNodeValue().trim() ;
  if( !(wrk.startsWith("<P") || wrk.startsWith("<p"))  ){
  out.print("<p>");
  }
  out.print( wrk );
  if( !(wrk.endsWith("/p>") || wrk.endsWith("/P>"))){
  out.print("</p>");
  }
  itemsCount++ ;
  out.println();
  }

Как показано в листинге 8.13, метод doNewsItemLong форматирует текст заголовка с помощью тега <h3>. Было бы неплохо усовершенствовать этот метод так, чтобы он допускал возможность изменять указанный формат по мере надобности. Основной текст сообщения форматируется как абзац с помощью тега <р>. Внутри самого текста могут содержаться любые форматирующие теги HTML, но теги <р> всегда будут использоваться для полного текста сообщения.

Листинг 8.13. Метод doNewsItemLong выводит полную версию сообщения (NewsFormatter.java)

 // <Newsitem  elements have been hashed
  // output long form with <p>...</p> formatting
  private void doNewsItemLong( PrintWriter out ){
  out.print("<h3>"); out.print( nodeHash.get("head")  );
  out.println("</h3>");
  Element de = (Element)nodeHash.get("date");
  out.print( de.getFirstChild() );
  out.println("</p>");
  Element ne = (Element)nodeHash.get("long");
  String wrk = ne.getFirstChild().getNodeValue().trim() ;
  if( !(wrk.startsWith("<P") || wrk.startsWith("<p"))  ){
  out.print("<p>");
  }
  out.print( wrk );
  if( !(wrk.endsWith("/p>") || wrk.endsWith("/P>"))){
  out.print("</p>");
  }
  itemsCount++ ;
  out.println();
  }

Наконец, в листинге 8.14 представлены два служебных метода. Метод setFor- matTempl ate отыскивает файл и считывает его строка за строкой. Предполагается, что в файле имеется строка, начинающаяся с текста "<!-INSERT". Она разделяет разметку HTML на два раздела, которые становятся переменными headStr и footStr. Метод toString предназначен для помощи в отладке.

Листинг 8.14. Конец исходного кода класса NewsFormatter (NewsFormatter.java)

 private void  setFromTemplate(String template )
throws IOException {
  File f = new File( newsFilePath, template );
  FileReader fr = new FileReader( f );
  BufferedReader br = new BufferedReader( fr );
  StringBuffer hsb = new StringBuffer( 100 );
  StringBuffer fsb = new StringBuffer( 100 );
  String tmp = br.readLine(); // strips line terminators
  while( !tmp.startsWith("<!--INSERT" )){
  hsb.append( tmp ); fsb.append("\r\n");
  tmp = br.readLine();
  }
  tmp = br.readLine();
  while( tmp != null ){
  fsb.append( tmp ); fsb.append("\r\n");
  tmp = br.readLine();
  }
  headStr = hsb.toString();
  footStr = fsb.toString();
  }
 public String toString()
  { StringBuffer sb = new StringBuffer("NewsFormatter item ct= ");
  sb.append( Integer.toString( itemNodes.length ));
  return sb.toString() ;
  }
}

Использование класса NewsFormatter

В этом разделе рассматриваются два способа использования класса NewsFormatter: с сервлетом общего назначения TheNewsServ и с JSP-страницами.

Код для сервлета TheNewsServ

Сервлет TheNewsServ можно использовать для отображения одного сообщения с указанным параметром id или для отображения нескольких сообщений с заданными параметрами topic и аде. В листинге 8.15 показаны инструкции импорта и статические переменные. Мы установили значения статических переменных равными заданным по умолчанию, но, разумеется, вам нужно будет заменить их на значения, отражающие ваши фактические настройки.

Листинг 8.15. Начало исходного кода сервлета TheNewsServ (TheNewsServ)

package com.XmlEcomBook.Chap08 ;

import java.io.*;
  import java.util.* ;
  import javax.servlet.*;
  import javax.servlet.http.*;
public class TheNewsServ extends HttpServlet
  {
  static String workDir = "E:\\scripts\\CompanyNews" ;
  static String newsFile = "thenews.xml" ;
  static String handler = "http://localhost/servlet/thenews" ;
  static String propfile = "conewserv.properties";
  static String version = "v1.0";
  static String pversion = "" ;
  static Properties cnProp ;
  static String brcrlf = "<br />\r\n" ;
  static String defaultHead = "<html>\r\n" +
  "<head><title>Company News Servlet</title></head>\r\n"  +
  "<body>\r\n" +
  "<h2>Here is the news</h2>\r\n" ;
  static String defaultFoot = "</body></html>\r\n";

Метод init, показанный в листинге 8.16, считывает файл свойств, значения которых могут быть использованы для замены установленных по умолчанию значений статических переменных.

Листинг 8.16. Метод init класса TheNewsServ (TheNewsServ.java)

 public void  init(ServletConfig config) throws ServletException
  {
  super.init(config);
  String tmp = config.getInitParameter("workdir");
  if( tmp != null ) workDir = tmp ;
  tmp = config.getInitParameter("propfile");
  if( tmp != null ) propfile = tmp;
  System.out.println("Start TheNewsServ using " + workDir );
  File f = new File( workDir, propfile );
  try { cnProp = new Properties();
  cnProp.load( new FileInputStream(f) );
  tmp = cnProp.getProperty("thenewshandler");
  if( tmp != null ) handler = tmp ;
  pversion = cnProp.getProperty("version");
  if( pversion != null ){
  defaultFoot = "<hr><br>News Servlet " + version +
  " properties: " + pversion + "<br>\r\n" +
  "</body>\r\n</html>\r\n" ;
  }
  NewsFormatter.setHandler( handler );
  System.out.println( new Date().toString() +
  " Loaded properties for TheNewsServ: "
  + handler );
  }catch(IOException e){
  System.out.println("Error loading " + e );
  }
 }

Функциональность сервлета сконцентрирована в методе doGet, как видно из листинга 8.17. В запросе можно передать значения параметров, определяющих тему сообщений, максимальный «возраст» сообщений, требуемый способ представления и идентификатор сообщения. Заметим, что создается объект Fi I e, соответствующий файлу XML с сообщениями, и передается конструктору NewsFormatter. Использование объекта File гарантирует, что соблюдаются соглашения относительно разделителей для компонентов пути; NewsFormatter не открывает этот файл, но использует его имя при получении объектной модели документа для этого файла из DOMlibrary.

Листинг 8.17. Метод doGet (TheNewsServ.java)

 public void  doGet(HttpServletRequest req,
HttpServletResponse resp)
  throws ServletException, IOException
  {
  resp.setContentType("text/html");
  PrintWriter out = new PrintWriter(resp.getOutputStream());
  String topics = req.getParameter("topic");
  String ageStr = req.getParameter("days");
  String len = req.getParameter("size" ); // "S","H"  or "L"
  String id = req.getParameter("id"); // a single item is requested
  try {
  File f = new File( workDir, newsFile );
  NewsFormatter nf = new NewsFormatter( f );
  if( id != null ){
  nf.doNews( out, defaultHead,defaultFoot, id );
  }
  else {
  // PrintWriter, head, foot, topics, H,S or L, age, skip#, mx#
  nf.doNews( out, defaultHead, defaultFoot, topics,
len, ageStr,0, 10 );
  }
  out.close();
  }catch(Exception e){
  System.err.println("TheNewsServ.doGet " + e );
  errorMsg( out, "TheNewsServ.doGet", e );
  }
  }

Обратите внимание, что конструкция try-catch в методе doGet направляет все исключения методу errorMsg, показанному в листинге 8.18. Разумеется, вам следует вставить свой адрес электронной почты в текст сообщения либо текст этого сообщения может состоять из специальной строки, которая задается в файле свойств. Методы header и footer просто выписывают стандартные теги HTML.

Листинг 8.18. Методы errorMsg, Header и Footer (TheNewsServ.java)

 // assumes  response has been set to text/html
  private void errorMsg( PrintWriter out, String msg,
Exception ex ){
  header( out );
  out.println("<h2>Error: " ); out.println( msg );
  out.println("</h2><br>");
  if( ex != null ){
  ex.printStackTrace( out );
  }
  out.println("<br>");
  out.println("<a href=\"mailto:wbrogden@bga.com\">Please
mail me the error message.</a><br>");
  footer( out );
  }
 private void header(PrintWriter out ){
  out.println("<html>");
  out.println("<head><title>Company News Servlet</title>
</head>");
  out.println("<body>");
  }
 private void footer(PrintWriter out ){
  out.println("<hr><br>Company News Servlet " + version
+ " properties: <br>" );
  out.println("</body>");
  out.println("</html>");
  out.close();
  }
}

Пример JSP-страницы

Для рассматриваемого нами примера JSP-страницы новостей ее основной формой является таблица с тремя столбцами. Чтобы уменьшить размер листинга, мы предельно сократили эту страницу; на реальной странице, разумеется, содержится гораздо больше сообщений, связанных с фирмой.

Привлекательность JSP-страниц объясняется как раз простотой включения выходных данных Java в разметку HTML. В листинге 8.19 показано начало JSP- страницы, где создается первая строка таблицы.

Листинг 8.19. Первая часть упрощенной JSP-страницы для отображения новостей (mockup.jsp)

 <!DOCTYPE  HTML PUBLIC "-//W3C//DTD HTML 4.0
Transitional//EN">
  <html>
  <head>
  <title>The XMLGifts News </title>
  </head>
<body bgcolor="#FFFFFF">
  <%@ page language="java"
  import=
"com.XmlEcomBook.Chap08.NewsFormatter,java.io.*" %>
  <%!
  String newsFilePath = "e:\\scripts\\CompanyNews" ;
  String newsFileName = "thenews.xml" ;
  String newsHandler =
"http://localhost:8080/XMLbook/Chap08/thenews.jsp"  ;
  File newsFile = new File( newsFilePath, newsFileName );
  public void jspInit(){
  super.jspInit();
  NewsFormatter.setHandler( newsHandler );
  }
  %>
<table width="89%" border="0" align="left"  cellpadding="8">
  <tr align="center" bgcolor="cyan">
  <td colspan="3"><font size="4">
  Various Corporate Navigation Links Go Here</font>
  </td>
  </tr>

Чтобы не усложнять пример, мы жестко запрограммировали тему сообщений — музыкальные компакт-диски (листинг 8 20) Первый раз объект NewsFormatter используется для создания левого столбца таблицы, где расположены заголовки сообщений. Это делается в первую очередь, так как, когда тематика сообщений задана, объект NewsFormatter будет содержать только данные по сообщениям, соответствующим выбранной тематике

Листинг 8.20. Продолжение JSP-страницы с выходными данными NewsFormatter (mockup.java)

 <!-- the  nf and pw objects will be used for all three td -->
  <tr valign="TOP" ><font size="3">
  <td><b>News Headlines</b><br>
  <%
  // topic could be set from customer records or the previous form
  String topic = "CDs" ;
  NewsFormatter nf = new NewsFormatter( newsFile );
  PrintWriter pw = new PrintWriter( out );
/* Note the doNews signature
  doNews( PrintWriter out, String hs, String fs,
  String topstr, String sz, String age, int skpN, int mxN ) */
  // headlines - all topics
  nf.doNews( pw, "","", "", "H", null,  0, 8 );
  %>
 </td>
  <td width="50%">
  <%
  nf.doNews( pw, "","", topic, "L", null, 0, 1 );
  %>
 </td>
 <!-- the short form column -->
  <td width="23%">
<%= "<b>Recent news items about " + topic + "</b><br>"  %>
<%
/* Note the doNews signature
  doNews( PrintWriter out, String hs, String fs,
  String topstr, String sz, String age, int skpN, int mxN ) */
 nf.doNews( pw, "","", topic, "S", null, 1, 8  );
  %>
 </td>
  </font>
  </tr>
  <tr align="center" bgcolor="cyan">
  <td colspan="3"><font size="4" >
  Repeat the Navigation links here for convenience<br></font>
  </td>
  </tr>
  <tr>
  <td colspan="3" align='center'>
<font face='arial, helvetica'  size='3'>
  &copy;2000 XMLGifts.com<sup>SM</sup>
  <br /></font>
  </td>
  </tr>
  </table>
  </body>
  </html>

Добавление свежих новостей

Важной особенностью этого приложения является возможность добавления новых сообщений без нарушения нормальной работы web-сайта Эту функцию иллюстрирует верхний правый угол рис. 8 1. Вместо того чтобы модифицировать DOM в памяти сервера, сервлет CompanyNewsServ записывает модифицированную версию исходного файла XML на диск. Этот обновленный файл сообщений будет автоматически загружен в очередной раз при вызове DOM из библиотеки DOMlibrary.

Сервлет CompanyNewsServ

Форма HTML для обновления страницы новостей создается и управляется сер- влетом CompanyNewsServl et. Начальный вход в сервлет осуществляется с помощью HTML-страницы, в которой имеется обычная форма HTML для ввода имени автора и пароля. Пример такой страницы представлен в файле CoNewsUpdate.html, который находится на прилагаемом к книге компакт-диске. Сервлет отыскивает имя автора в файле свойств, проверяя таким образом, что этот человек имеет право на добавление новых сообщений.

В листинге 8.21 показан файл свойств для работы на сервере local host. Заметим, что имя автора является именем свойства, а пароль — его значением.

Листинг 8.21. Файл свойств, используемый сервлетом CompanyNewsServ (conewserv. properties)

# properties for CompanyNewsServ
handler=http://localhost/servlet/conewserv
thenewshandler=http://localhost/servlet/thenews
newsfile=thenews.xml
version=June 15, 2000
wbrogden=xmlrules

В листинге 8.22 показаны инструкции импорта, статические переменные и метод init для сервлета CompanyNewsServ.

Листинг 8.22. Начало кода метода CompanyNewsServ (ComanyNewsServ.java)

package com.XmlEcomBook.Chap08 ;
import com.XmlEcomBook.DOMlibrary ;
  import java.io.*;
  import java.util.* ;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import org.w3c.dom.* ;
public class CompanyNewsServ extends HttpServlet
  {
  static String workDir = "E:\\scripts\\CompanyNews" ;
  static String propfile = "conewserv.properties" ;
  static String newsFile = "thenews.xml" ;
  static String handler = "http://localhost/servlet/conewserv" ;
  static String version = "v0.12";
  static String pversion = "" ;
  static Properties cnProp ;
  static String brcrlf = "<br />\r\n" ;
 public void init(ServletConfig config) throws ServletException
  {
  super.init(config);
  String tmp = config.getInitParameter("workdir");
  if( tmp != null ) workDir = tmp ;
  tmp = config.getInitParameter("propfile");
  if( tmp != null ) propfile = tmp ;
  System.out.println("Start CompanyNewsServ using " + workDir );
  File f = new File( workDir, propfile );
  try { cnProp = new Properties();
  cnProp.load( new FileInputStream(f) );
  tmp = cnProp.getProperty("handler");
  if( tmp != null ) handler = tmp ;
  tmp = cnProp.getProperty("newsfile");
  if( tmp != null ) newsFile = tmp ;
  pversion = cnProp.getProperty("version");
  System.out.println("Loaded properties for CompanyNewsServ: "
  + handler + " file:" + newsFile );
  }catch(IOException e){
  System.out.println("Error loading " + e );
  }
 }

Метод doGet, как показано в листинге 8.23, проверяет введенные пользователем имя и пароль, сравнивая их с данными в файле свойств, загруженном при инициализации сервлета. Если обнаруживается, что имя соответствует паролю, вызывается метод generateForm, создающий форму HTML для ввода текста нового сообщения.

Листинг 8.23. Метод doGet создает форму для ввода нового сообщения (CompanyNewsServ.java)

 public void  doGet(HttpServletRequest req,
HttpServletResponse resp)
  throws ServletException, IOException
  {
  resp.setContentType("text/html");
  PrintWriter out = new PrintWriter(resp.getOutputStream());
  String username = req.getParameter("username");
  String password = req.getParameter("password");
  String action = req.getParameter("action");
  String tmp = cnProp.getProperty(username);
  boolean userok = false ;
  if( tmp != null ){
  userok = tmp.equals( password );
  }
  header( out );
  if( userok ){
  generateForm( out, username, password );
  }
  else {
  out.println("<p>User: " + username + " password: "  + password +
  " not found.</p>" );
  }
  footer( out );
  }

Заполненная форма посылается методу doPost. Как показано в листинге 8.24, различные текстовые элементы извлекаются и передаются объекту NewsllpKeep с помощью метода addltem.

Листинг 8.24. Метод doPost собирает данные из формы (CompanyNewsServ.java)

 public void  doPost(HttpServletRequest req,
HttpServletResponse resp)
  throws ServletException, IOException
  {
  resp.setContentType("text/html");
  PrintWriter out = new PrintWriter(resp.getOutputStream());
  String username = req.getParameter("username");
  String password = req.getParameter("password");
  String action = req.getParameter("action");
  String head = req.getParameter("head");
  String date = req.getParameter("date");
  String topics = req.getParameter("topics");
  String shrtStr = req.getParameter("short").trim();
  String longStr = req.getParameter("long").trim();
  File f = new File( workDir, newsFile );
  try {
  NewsUpkeep nup = new NewsUpkeep( f );
  nup.addItem( head, date, topics, username, shrtStr, longStr );
  header( out );
  out.println("NewsUpkeep is " + nup + "<br />");
  footer( out );
  } catch( Exception e){
  errorMsg( out, "CompanyNewsServ.doPost ", e );
  }
  }

Форма HTML для ввода новых сообщений создается методом generateForm, как показано в листинге 8.25. Заметим, что имя пользователя и пароль вставлены в форму в виде скрытых значений.

Листинг 8.25. Метод generateForm создает форму для ввода (Com.panyNewsServ.java)

 private void  generateForm( PrintWriter out, String name,
String pw ){
  out.println("<h2>Enter Company News Item Data</h2>");
  out.println("<form method=\"POST\" action=\"" +
handler + "\" >");
  out.println("Headline - 80 char max<br />");
  out.println("<input type=\"text\" maxlength=\"80\"  size=\"60\"" +
  " name=\"head\" ><br />" );
  out.println("Dated <br />");
  out.println("<input type=\"text\" maxlength=\"50\"  size=\"40\"" +
  " name=\"date\" value=\"" + new Date().toString() +  "\" ><br />" );
  out.println("Topics separated by commas - please stick to the
official  list.<br />");
  out.println("<input type=\"text\" maxlength=\"80\"  size=\"60\"" +
  " name=\"topics\" ><br />" );
  out.println("Short version <br />");
  out.println("<textarea cols=\"60\" rows=\"3\" name=\"short\"  >");
  out.println("</textarea><br />");
  out.println("Long version <br />");
  out.println("<textarea cols=\"60\" rows=\"10\" name=\"long\"  >");
  out.println("</textarea><br />");
  out.println("<input type=\"hidden\" name=\"username\"  value=\""
  + name + "\"><br>" );
  out.println("<input type=\"hidden\" name=\"password\"  value=\""
  + pw + "\" ><br>");
  out.println("<input type=\"submit\" name=\"action\"
value=\"Submit\" ><br />" );
  out.println("</form></center>");
  }

Наконец, в сервлете имеются обычные вспомогательные методы, показанные в листинге 8.26. Естественно, вам нужно будет заменить адрес в тексте сообщения на свой собственный либо предоставить переменную типа String, которая инициализируется в файле свойств.

Листинг 8.26. Служебные методы в сервлете CompanyNewsServ (CompanyNewsServ.java)

 // assumes  response has been set to text/html
  private void errorMsg( PrintWriter out, String msg, Exception ex ){
  header( out );
  out.println("<h2>Error: " ); out.println( msg );
  out.println("</h2><br>");
  if( ex != null ){
  ex.printStackTrace( out );
  }
  out.println("<br>");
  out.println("<a href=\"mailto:wbrogden@bga.com\">
Please  mail me the error message.</a><br>");
  footer( out );
  }
 private void header(PrintWriter out ){
  out.println("<html>");
  out.println("<head><title>Company News Servlet</title>
</head>");
  out.println("<body>");
  }
 private void footer(PrintWriter out ){
  out.println("<hr><br>Company News Servlet " + version  +
" properties: <br>" );
  out.println("</body>");
  out.println("</html>");
  out.close();
  }
}

Класс NewsUpKeep

Классу NewsUpKeep передаются существующий объект DOM <Newsfile> и различные текстовые строки, которые составляют новый элемент <Newsitem>; он переписывает файл XML с сообщениями. Применить этот метод проще, чем создать элемент <Newsitem> и вставить его модель DOM, которая постоянно находится в памяти; кроме того, указанный метод гарантирует, что файл новостей будет обновлен правильным образом. Также при использовании этого метода исключается возможность того, что один из отображающих методов получит частично измененную модель DOM.

В листинге 8.27 показано начало кода класса NewsUpKeep. Конструктор класса использует полное имя файла для получения DOM из DOMIibrary. Поскольку эта модель DOM не модифицируется при переписывании файла XML, вам не нужно беспокоиться по поводу возможности одновременного доступа к этому объекту класса NewsFormatter.

Заметим, что в классе NewsUpKeep создается переменная rootNNM типа NamedNodeMap, которая содержит имена и значения атрибутов корневого элемента документа XML, <Newsfile>. Также конструктор помещает все узлы <Newsitem> в массив itemNodes.

Листинг 8.27. Начало класса NewsUpKeep (NewsUpKeep.java)

package com.XmlEcomBook.Chap08;
import com.XmlEcomBook.DOMlibrary ;
  import java.io.*;
  import java.util.* ;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import org.w3c.dom.* ;
public class NewsUpkeep
  {
  File newsFile ;
  String newsFileName ;
  Node[] itemNodes ;
  NamedNodeMap rootNNM ; // for root attributes
 public NewsUpkeep( File f) throws IOException {
  newsFile = f ;
  newsFileName = f.getAbsolutePath() ;
  DOMlibrary library = DOMlibrary.getLibrary();
  Document doc = library.getDOM( newsFileName );
  if( doc == null ){
  throw new FileNotFoundException( newsFileName );
  }
  Element re = doc.getDocumentElement();
  rootNNM = re.getAttributes();
  System.out.println("Root has " + rootNNM.getLength() +
" attributes");
  NodeList newsItemNodes =
doc.getElementsByTagName("Newsitem");
  int ct = newsItemNodes.getLength();
  itemNodes = new Node[ ct ];
  for( int i = 0 ; i < ct ; i++ ){
  itemNodes[i] = newsItemNodes.item( i );
  }
  }

В листинге 8.28 показаны некоторые вспомогательные методы, необходимые в классе NewsUpKeep. Метод formatTopics гарантирует, что строка, которая будет записана в качестве значения атрибута topic, имеет правильный формат.

Листинг 8.28. Различные вспомогательные функции класса NewsUpKeep (NewsUpKeep.java)

 //ensure  there are no leading or trailing spaces on the
  // individual topics, comma separated, general,food , etc
  private String formatTopics(String s ){
  if( s.indexOf(',') < 0 ) return s.trim();
  // only separator is comma
  StringTokenizer st = new StringTokenizer( s, "," );
  StringBuffer sb = new StringBuffer( s.length() );
  while( st.hasMoreTokens() ){
  sb.append( st.nextToken().trim() );
  if( st.hasMoreTokens() ) sb.append(',');
  }
  return sb.toString();
  }
  // convert system millisecs to days since epoch
  private String timeInDays(){
  long t = System.currentTimeMillis() ;
  int tid = (int)(t / ( 1000 * 60 * 60 * 24 ));
  return Integer.toString( tid );
  }
 // s expected to be decimal number used in <Newsitem id=
  private String incrementID(String s ){
  try{
  int n = Integer.parseInt( s );
  return Integer.toString( n + 2 );
  }catch(NumberFormatException e){
  return s + "a" ;
  }
  }
  public String toString()
  { StringBuffer sb = new StringBuffer("NewsUpkeep ");
  sb.append(" Newsitem count: " );
  sb.append( Integer.toString( itemNodes.length ));
  return sb.toString();
  }

Теперь мы подходим к основному рабочему методу, addltem. Сначала этот метод создает новый файл с временным именем и записывает туда стандартное объявление XML и комментарии. Затем создается тег <Newsfile>, куда записываются имена атрибутов и их значения из коллекции rootNNM.

Как видно из листинга 8.29, атрибут nextid обрабатывается специальным образом. Сохраняется текущее значение, которое становится значением атрибута id нового элемента <Newsitem>, а увеличенное значение записывается в тег <Newsfile>.

Листинг 8.29. Начало метода addltem (NewsUpKeep.java)

 // items  are always added at the top of the file
  // so we have to rebuild the start of the root element
  public void addItem(
  String head, String date, String topics, String author,
  String shrtStr,
  String longStr ) throws IOException {
  String idVal = "" ;
  String tmpfile = newsFileName + "$$" ;
  File f = new File( tmpfile );
  FileWriter fw = new FileWriter(f);
  PrintWriter out = new PrintWriter( new BufferedWriter( fw ) );
  out.println("<?xml version=\"1.0\" standalone=\"yes\"  ?>");
  out.println("<!-- output by NewsUpkeep -->");
  int ct = rootNNM.getLength();
  if( ct == 0 ){
  out.println("<Newsfile>");
  }
  else { out.print("<Newsfile ");
  for( int i = 0 ; i < ct ; i++ ){
  Node an = rootNNM.item(i);
  String name = an.getNodeName();
  String val = an.getNodeValue();
  out.print( name + "=\"" );
  if( name.equals("nextid") ){
  idVal = val ;
  val = incrementID( val );
  }
  out.print( val + "\" ");
  }
  out.println(" >");
  }

Затем, как показано в листинге 8.30, пишется новый тег <NewsItem>, за которым следует заголовок сообщения, указывается дата и приводятся краткая и полная версии текста сообщения. Для того чтобы записать старые элементы <NewsItan>, вызывается метод writeNewsNode. После закрытия временного файла старый файл XML удаляется, а временный файл получает имя. Следующий раз, когда этот файл будет запрошен, класс DOMIibrary по изменившейся метке даты модификации файла (timestamp) определит, что нужно считывать новый файл.

Листинг 8.30. Метод addltem, продолжение (NewsllpKeep.java)

 out.print("<Newsitem  timestamp=\"");
  out.print( timeInDays() + "\" topic=\"");
  out.print( formatTopics( topics ) );
  out.println( "\" author=\"" + author + "\" id=\""  + idVal + "\" >");
  // end of <Newsitem .. >
  out.println("<head>" + head.trim() + "</head>"  );
  out.println("<date>" + date.trim() + "</date>"  );
  out.println("<short><![CDATA[");
  out.println( shrtStr.trim() ); out.println("]]></short>");
  out.println("<long><![CDATA[");
  out.println( longStr ); out.println("]]></long>");
  out.println("</Newsitem>");
  for( int i = 0 ; i < itemNodes.length ; i++ ){
  writeNewsNode(out, (Element)itemNodes[i] );
  }
  out.println("</Newsfile>");
  out.flush(); out.close();
  File forig = new File( newsFileName );
  DOMlibrary library = DOMlibrary.getLibrary();
  // to prevent overlapping XML file operations
  synchronized( library ){
  forig.delete();
  if( !f.renameTo( forig )){
  System.out.println("NewsUpkeep.addItem rename failed") ;
  }
  }
  }

Метод writeNewsNode, который записывает отдельный элемент <Newsitem>, показан в листинге 8.31.

Листинг 8.31. Метод, который записывает отдельный элемент из DOM (NewsllpKeep.java)

 // write  a <Newsitem Element duplicating the attributes
  public void writeNewsNode(PrintWriter out, Element e) {
  NamedNodeMap nnm = e.getAttributes();
  out.print("<Newsitem " ) ; //timestamp=\"");
  int i ;
  for( i = 0 ; i < nnm.getLength() ; i++ ){
  Attr na = (Attr) nnm.item(i); // Attr extends Node
  String atr = na.getName();
  String val = na.getValue();
  out.print( atr ); out.print("=\"");
  out.print( val ); out.print("\" ");
  }
  out.println(">");
  NodeList nl = e.getChildNodes();
  int ct = nl.getLength();
  for( i = 0 ; i < ct ; i++ ){
  Node nde = nl.item( i );
  if( nde instanceof Element ){
  Element ce = (Element)nde;
  String name = ce.getTagName();
  out.print("<" + name + ">");
  NodeList chnl = ce.getChildNodes() ;
  if( chnl.getLength() == 0 ) continue ;
  Node chn = chnl.item(0);
  if( name.equals("long") || name.equals("short") ){
  out.print("<![CDATA[");
  out.println( chn.getNodeValue().trim() );
  out.print("]]>");
  }
  else { out.print( chn.getNodeValue() );
  }
  out.println("</" + name + ">");
  }
  } // loop over <Newsitem> child nodes
  out.println("</Newsitem>");
  }
  }

На основе нашего опыта добавления новых сообщений с помощью сервлета CompanyNewsServ вы можете сначала создать полный текст сообщения в текстовом редакторе, а затем при работе с формой HTML для ввода текста просто вставить его в форму с помощью команд вырезания и вставки.


  • Глава 9. Привлечение постоянных посетителей
    • Источники новостей и стандарты
      • Формат RSS
      • NewsML и планы стандартизации
      • Формат сообщений Moreover.com
    • Получение файла XML
    • Класс NewsModel
      • Создание DOM
      • Выбор заголовков
    • Главный управляющий класс
    • Классы для отображения заголовков
      • Сервлет NetNewsServ
      • Класс NetNewsBean
    • Возможные усовершенствования

Глава 9.

Привлечение постоянных посетителей

Ваш электронный магазин предлагает великолепные товары по конкурентоспособным ценам — что еще можно сделать, чтобы посетители возвращались на ваш сайт снова и снова? В идеале, ваша домашняя страница должна быть настолько интересной, чтобы люди «приходили» на нее просто для того, чтобы посмотреть, что происходит.

Вы, вероятно, замечали, что в последние годы многие сайты включают в свои домашние страницы заголовки новостей, пытаясь заинтересовать пользователей и привлечь их к посещению сайта. На этих сайтах новости не создаются — они заимствуются у различных web-служб, предназначенных для синдицирования (syndicating) и распространения информации; в этой главе мы покажем, как вы можете воспользоваться этими службами.

Мы также рассмотрим различные типы источников новостей, доступных в Интернете. Кроме того, вы найдете здесь пример кода для загрузки форматированных с помощью XML заголовков новостей с одного из самых крупных их источников. Когда файлы XML загружены, следующим этапом является создание класса Java, с помощью которого организуются эффективный поиск и выборка новостей. Наконец, мы приводим примеры того, как генерируется код HTML для форматирования заголовков.

Источники новостей и стандарты

Вероятно, вы знакомы с идеей синдицирования в мире средств массовой информации. Корреспонденты, авторы передовиц и карикатуристы печатаются не только в своей родной газете, но и в газетах, которые заключили с ними контракт на право опубликования их работ. Синдицирование открывает перед газетой огромные возможности по публикации сообщений без вложения больших средств.

В мире печатных средств массовой информации фактическое содержание сообщения распространяется среди различных изданий; читатель, открывая газету, ожидает увидеть напечатанным полный текст сообщения, будь то хроника происшествий или комикс. Создатель сообщения получает небольшой гонорар, организацией оплаты занимается соответствующее агентство, приобретающее информацию и продающее ее различным газетам для одновременной публикации.

Однако в Интернете, где пользователь может легко переходить с одной страницы на другую, синдицированию подлежит лишь сам факт того, что где-то существует определенный ресурс. Создатель этого ресурса может синдицировать его заголовок, снабженный ссылкой на полный текст сообщения. Тогда заинтересованные в этой информации люди будут попадать на его сайт по ссылке на данное сообщение и попутно читать объявления или узнавать о товарах, которые можно заказать на этом сайте.

Формат RSS

Компания Netscape первой предложила идею создания страниц новостей, поступающих из различных источников, на сайте Netscape Netcenter (www.netscape.com) и разработала специальный формат Rich Site Summary (RSS) для упрощения этого процесса. Идея состоит в том, что новости, размещенные на сотрудничающих с Netscape Netcenter web-сайтах, записываются в специальном едином для всех сайтов формате и автоматически становятся доступными на сайте Netscape Netcenter. Такой подход сделал сайт Netscape одним из наиболее популярных порталов в Интернете.

Основанный на XML формат RSS (иногда называемый также RDF Site Summary — Resource Description Framework Site Summary, стандарт на описание ресурсов) до сих пор является одним из основных форматов для распространения новостей в Интернете. Тот сайт, где фактически создается сообщение, формирует файл ресурса (feed) в формате RSS, определяя канал новостей (news channel). Этот файл должен быть выложен на открытый web-сайт, где он автоматически становится доступным для других сайтов, с которыми заключены соответствующие соглашения. Инструкции по созданию собственного канала новостей вы можете получить по адресу: http://my.netscape.com/publish/help/quickstart. Большой web-сайт, который каждый час обновляет заголовки новостей, поступающих по различным каналам, может оказаться очень привлекательным для многих посетителей. В качестве примера вы можете попробовать заглянуть на сайт http://my.userland.com (но будьте осторожны: вы можете провести там гораздо больше времени, чем планировали!).

Примером технически ориентированного сайта, подключенного к каналам RSS, является система Meerkat (www.oriellynet.com/meerkat).

NewsML и планы стандартизации

Международный совет по медиа-телекоммуникациям (The International Press Telecommunications Council, IPTC), представленный по адресу www.iptc.org, предпринимает попытки создать на основе XML стандарт кодирования, который упростил бы процесс составления, передачи и получения информационных сообщений. Во время написания этой книги была выпущена версия 1.0 этого стандарта, названного NewsML.

Возможности этого стандарта по сравнению со стандартом RSS весьма широки. Например, он включает в себя различные мультимедийные элементы и предусматривает явные связи между сообщениями.

Поскольку в совет IPTC входит большое количество организаций, многие из которых зарабатывают на информационных сообщениях, при создании стандарта сталкивается множество различных интересов. Если NewsML утвердится в качестве стандарта, описанные в этой главе методики по-прежнему будут применимы.

Формат сообщений Moreover.com

Для задач, которые решаются в этой главе, мы используем формат импорта и экспорта сообщений, предложенный организацией Moreover.com (www.moreover.com). Мы выбрали Moreover.com для рассказа о технологиях обмена новостями по причине простоты используемого формата и большого количества тематических категорий на сайте. Разумеется, в пользу этого формата говорит и то, что он распространяется свободно.

Хотя организация Moreover.com была создана только в декабре 1999, к июлю 2000 она получала заголовки с 1500 сайтов. Программное обеспечение, занимающееся сбором заголовков новостей, автоматически разделяет их на более чем 250 категорий.

Формат, используемый Moreover.com, проще RSS и выдает только сами заголовки. Для индивидуальной настройки импорта информационных сообщений нужно зарегистрироваться в системе, сообщив свой электронный адрес и пароль, а затем выбрать нужные тематические категории и указать количество заголовков сообщений, которые вы хотели бы увидеть. На рис. 9.1 показана небольшая часть страницы, на которой происходит выбор тематических категорий.

Рис. 9.1. Выбор категорий заголовков на сайте Moreover.com

Получение файла XML

Первым этапом процесса настройки вашего web-сайта на использование материалов информационных web-синдикатов является получение исходного файла XML от поставщика. В нашем примере мы обращаемся на сайт www.moreover.com, указывая параметр поиска, который генерирует данные по нашим заранее выбранным категориям сообщений. Ниже приведен полный URL-адрес:

www.moreover.com/cgi-local/page?wbrogden@bga.com+xml

После получения доступа к этому ресурсу на сервере Moreover.com специальное приложение возвращает текстовый поток в формате XML, содержащий заголовки по выбранным темам. В листинге 9.1 показаны заголовок и первый элемент в том виде, в котором они были исходно получены при загрузке файла XML. Обратите внимание на то, что строка DOCTYPE ссылается на определение DTD, расположенное на сайте Moreover.com. Чтобы обеспечить возможность анализа файла XML во время тестирования без доступа к web-сайту Moreover.com, класс XMLgrabber модифицирует строку DOCTYPE так, чтобы она ссылалась на определение DTD как на локальный файл.

Листинг 9.1. Заголовок и первый элемент загруженного файла XML (xmldump.xml)1

 <?xml  version="1.0" encoding="iso-8859-1"?>
  <!DOCTYPE moreovernews SYSTEM "moreovernews.dtd">
  <moreovernews>
  <article id="_8510757">
  <url>http://c.moreover.com/click/here.pl?x8510756</url>
  <headline_text>Cyclone Commerce Poised to
Fulfill Promise of E-Signature  Legislation</headline_text>
  <source>Java Industry Connection</source>
  <media_type>text</media_type>
  <cluster>Java news</cluster>
  <tagline> </tagline>
  <document_url>
http://industry.java.sun.com/javanews/more/hotnews/
</document_url>
  <harvest_time>Jul 25 2000 8:34AM</harvest_time>
  <access_registration> </access_registration>
  <access_status> </access_status>
  </article>

Обратите внимание, что элемент, который в Moreover.com называется cl uster, идентифицирует основную тематическую категорию, к которой относится данное сообщение. В приведенном ниже примере сервлета мы используем только элементы url, headline_text, source и cluster. Определение DTD moreovernews, как видно из листинга 9.2, устроено относительно просто.

Листинг 9.2. Файл moreovernews.dtd (moreovernews.dtd)

 <!ELEMENT  moreovernews (article*)>
  <!ELEMENT article (url,headline_text,source,media_type,
cluster,tagline,document_url,harvest_time,
access_registration,access_status)>
  <!ATTLIST article id ID #IMPLIED>
  <!ELEMENT url (#PCDATA)>
  <!ELEMENT headline_text (#PCDATA)>
  <!ELEMENT source (#PCDATA)>
  <!ELEMENT media_type (#PCDATA)>
  <!ELEMENT cluster (#PCDATA)>
  <!ELEMENT tagline (#PCDATA)>
  <!ELEMENT document_url (#PCDATA)>
  <!ELEMENT harvest_time (#PCDATA)>
  <!ELEMENT access_registration (#PCDATA)>
  <!ELEMENT access_status (#PCDATA)>

Сервлет, который используется в рассматриваемом в этой главе приложении, включает в себя класс NetNewsSuper. Этот класс запускает поток Thread, который создает объект XMLgrabber для загрузки файла с наиболее свежими заголовками новостей, а также текущий файл DTD. В листинге 9.3 показано начало этого класса, в том числе его конструктор. Заметим, что конструктор снабжен URL- адресом искомого ресурса, а также содержит путь и имя файла, который будет использоваться для локальной копии.

Листинг 9.3. Начало класса XMLgrabber (XMLgrabber.java)

package com.XmlEcomBook.Chap09;
import java.net.* ;
  import java.io.* ;
  import java.util.*;
public class XMLgrabber
  {
  String source ; // complete URL to run std query
  // example
"http://www.moreover.com/cgi-local/
page?wbrogden@bga.com+xml";
  String saveDir ; // for both temp and final xml file
  String tfnxml ; // temp file name - see createTempXmlWriter
  String tfndtd ; // temp file name for dtd
  String saveName ; // for xml
  String dtdURL ; // complete DTD url from <!DOCTYPE line
  String dtdFname ; // for dtd
 PrintWriter pw ;
  URL theURL ;
  Thread queryT ;
 // all files from a given source will go to dest directory
  XMLgrabber( String src, String dest, String fname ){
  source = src ;
  saveDir = dest ;
  saveName = fname ;
  // System.out.println("XMLgrabber initialized for " + src );
  }

Поскольку мы не можем всегда рассчитывать на быстрое соединение с сайтом Moreover.com и так как в некоторых случаях попытка получить новые данные может провалиться, получение данных XML осуществляется отдельным от функций сервлета потоком (объектом Thread). Более того, файл со старыми данными не заменяется сразу же новым файлом; вместо этого полученные данные записываются во временный файл. Только когда мы удостоверимся, что полученный файл XML содержит все необходимые данные, мы удаляем старый файл и присваиваем имя временному файлу. Как показано в листинге 9.4, метод doQueryNow создает структуру для получения наиболее свежей информации. Помимо получения файла XML, содержащего заголовки сообщений, мы также пытаемся получить самое последнее определение DTD.

Листинг 9.4. Метод doQueryNow (XMLgrabber.java)

 // run by  external Thread to get file resident
  // return true if suceeds
  public boolean doQueryNow() throws IOException {
  theURL = new URL( source );
  createTempXmlWriter();
  grabXml();
  if( !renameTemp( tfnxml, saveName )) return false ;
  tfnxml = null ;
  // now for the dtd
  if( dtdURL == null ) return true ;
  // System.out.println("Start DTD retrieval");
  theURL = new URL( dtdURL );
  createTempDtdWriter();
  grabDtd();
  boolean ret = renameTemp( tfndtd,dtdFname );
  tfndtd = null ;
  return ret ;
  }

Существует несколько возможных ситуаций, в которых этот метод дает сбой: либо в случае, когда при попытке получить доступ к исходному файлу возникает исключение lOException, либо в случае неполадок с локальной файловой системой. Чтобы не загромождать локальный диск временными файлами, которые были созданы, но не прошли надлежащей обработки, для работы с файлами tfnxml и tfndtd разработана специальная методика.

Файл tfnxml создается при вызове метода createTempXmlWriter. Заметим, что если не возникнет исключительных ситуаций и переименование файла tfnxml в saveName пройдет успешно, переменная tfnxml устанавливается равной null. В противном случае метод finalize (листинг 9.9) попытается удалить временный файл. Файл tfndtd, предназначенный для загрузки DTD, проходит такую же обработку.

В листинге 9.5 показаны методы объекта XMLgrabber, которые применяются для создания и управления временными файлами.

Листинг 9.5. Методы для управления временными файлами (XMLgrabber.java)

 // saveDir  used for all
  private boolean renameTemp(String tmp, String saveN ) {
  File src = new File( saveDir, tmp );
  File dest = new File( saveDir, saveN);
  dest.delete();
  return src.renameTo( dest );
  }
 private void createTempXmlWriter() throws IOException {
  tfnxml = "$" + Integer.toString((int) System.currentTimeMillis())
  + ".xml";
  File tfile = new File( saveDir, tfnxml );
  while( tfile.exists() ){ // hunt for unique name
  tfnxml = tfnxml + "X" ;
  tfile = new File( saveDir, tfnxml );
  }
  // ok, unique file name in tfnxml, File tfile set up
  FileWriter fw = new FileWriter( tfile.getAbsolutePath() );
  pw = new PrintWriter( fw );
  }
 private void createTempDtdWriter() throws IOException {
  tfndtd = "$" + Integer.toString((int) System.currentTimeMillis())
  + ".dtd";
  File tfile = new File( saveDir, tfndtd );
  while( tfile.exists() ){ // hunt for unique name
  tfndtd = tfndtd + "X" ;
  tfile = new File( saveDir, tfndtd );
  }
  // ok, unique file name in tfndtd, File tfile set up
  FileWriter fw = new FileWriter( tfile.getAbsolutePath() );
  pw = new PrintWriter( fw );
  }

Как показано в листинге 9.6, метод grabXml считывает строки текста из входного потока, создав соединение по нужному URL-адресу. Строка заголовка XML, содержащая ссылку на файл DTD, переформатируется методом reformDoctype, который заменяет URL на имя локального файла. Это делается для того, чтобы гарантировать, что синтаксический анализ файла XML будет проходить независимо от соединения с Интернетом.

Листинг 9.6. Метод grabXML считывает строки файла XML из заданного с помощью URL источника (XMLgrabber.java)

 // at this  point pw is open to a temp file
  private void grabXml() throws IOException {
  URLConnection urlC = theURL.openConnection();
  urlC.setUseCaches( false );
  urlC.setAllowUserInteraction(false);
  urlC.connect();
  InputStream is = urlC.getInputStream();
  InputStreamReader isr = new InputStreamReader(is);
  BufferedReader br = new BufferedReader( isr );
  String tmp = br.readLine() ;
  while( tmp != null ){
  tmp = tmp.trim();
  if( tmp.startsWith("<!DOCTYPE") ) {
// change to use local copy
  tmp = reformDoctype( tmp );
  }
  pw.println( tmp );
  tmp = br.readLine();
  }
  pw.close(); // does a flush()
  }

В предположении, что метод ref ormDoctype корректно задает переменную dtdURL, метод grabDtd, показанный в листинге 9.7, загружает DTD в локальный временный файл. В этом листинге также показан служебный метод createURL, который устанавливает значение переменной экземпляра theURL.

Листинг 9.7. Метод grabDtd получает текущее определение moreovernews.dtd (XMLgrabber.java)

 // at this  point pw is open to a temp file for dtd
  private void grabDtd() throws IOException {
  System.out.println("grabDtd:" + dtdURL );
  URLConnection urlC = theURL.openConnection();
  urlC.setUseCaches( false );
  urlC.setAllowUserInteraction(false);
  urlC.connect();
  InputStream is = urlC.getInputStream();
  InputStreamReader isr = new InputStreamReader(is);
  BufferedReader br = new BufferedReader( isr );
  String tmp = br.readLine() ;
  while( tmp != null ){
  pw.println( tmp.trim() );
  tmp = br.readLine();
  }
  pw.close(); // does a flush()
  //System.out.println("grabDtd OK");
  }
 private boolean createURL(String str){
  try {
  theURL = new URL( str );
  return true ;
  }catch(MalformedURLException e){
  return false ;
  }
  }

Как показано в листинге 9.8, метод reformDoctype извлекает ссылку на файл DTD из строки DOCTYPE, а затем устанавливает значения переменных dtdURL и dtdFname.

Листинг 9.8. Метод reformDocType модифицирует ссылку на DTD (XMLgrabber.java)

 // string  has doctype declaration, revise to
//point to local version
  private String reformDoctype( String dts ){
  int p1 = dts.indexOf( "http:"); // points at h
  if( p1 < 0 ) return dts ;
  //
  int p2 = dts.indexOf( '"', p1 );
  int p3 = dts.lastIndexOf('/', p2);
  if( p3 < 0 ) return dts ;
  dtdURL = dts.substring( p1 , p2 );
  dtdFname = dts.substring( p3 + 1, p2 );
  // System.out.println("DTD url:" + dtdURL + "<");
  // System.out.println("DTD fname:" + dtdFname + "<");
  String tmp = dts.substring(0,p1); // includes "
  return tmp + dts.substring( p3 + 1 );
  }

Так как процесс загрузки в некоторых точках может быть прерван, существует опасность накопления пустых или частично записанных временных файлов. Метод finalize, как показано в листинге 9.9, пытается удалить эти файлы, если они существуют. Если все идет нормально, переменные tfnxml и tfndtd должны содержать null и тогда попытки удалить временные файлы не происходит.

Листинг 9.9. Метод finalize может удалять временные файлы (XMLgrabber.java)

 // last chance  to clean up temp files if something failed
  public void finalize(){
  if( tfnxml != null ){
  new File( saveDir, tfnxml ).delete();
  }
  if( tfndtd != null ){
  new File( saveDir, tfndtd ).delete();
  }
  }
  }

Класс NewsModel

Теперь, когда файл XML и соответствующий файл DTD находятся на локальном жестком диске, нам нужен класс для синтаксического анализа файла XML и создания DOM. Написанный нами для ^того класс называется NewsModel; он создает коллекции элементов в соответствии с типом содержимого, или, если использовать терминологию Moreover.com, кластеры (clusters). В классе NewsModel предусмотрены также методы для извлечения и форматирования отдельных элементов.

Создание DOM

В листинге 9.10 показано начало класса NewsModel, включая переменные экземпляра и конструктор. Обратите внимание на то, что здесь присутствуют две коллекции, Hashtable и Nodelist, которые будут «заселены» элементами article, когда завершится создание DOM. Конструктору просто передаются параметры, содержащие путь и имя файла XML. В зависимости от того, какой анализатор XML вы используете, вам может потребоваться несколько изменить инструкции импорта и метод loadXML, показанный в листинге 9.11, но остальные методы должны работать с любым анализатором, потому что они используют интерфейсы org.w3c.dom.

Листинг 9.10. Начало класса NewsModel (NewsModel.java) package com.XmlEcomBook.Chap09;

 package com.XmlEcomBook.Chap09;
import java.io.* ;
  import java.util.* ;
  import com.sun.xml.tree.* ;
  import com.sun.xml.parser.Resolver ;
  import org.xml.sax.* ;
  import org.w3c.dom.* ;
public class NewsModel
  {
  long timestamp ;
  public String dateStr ;
  Document doc ;
  String path, fname ;
  public boolean usable ;
  public String lastErr ="no error";
  // see locateCategories for creation of following
  Hashtable clusterHash ;
  NodeList articleNodeList ;
 public NewsModel( String pth, String fn ){
  path = pth ; fname = fn ;
  }

Процесс синтаксического анализа контролируется методом loadXML, как показано в листинге 9.11. Заметьте, что (для сохранения максимально полной информации по отладке) исключения SAXParseException перехватываются по отдельности и подробная информация о причинах возникновения исключения сохраняется в переменной lastErr. Хотя документы XML, содержащиеся на сайте Moveover.com, обычно правильно оформлены, у нас был один случай неправильно оформленного документа — в нем содержался недопустимый символ. В этом случае подробная информация о причинах исключительной ситуации при синтаксическом анализе оказалась очень ценной.

Листинг 9.11. Метод loadXML осуществляет синтаксический анализ (NewsModel.java)

 // return  true if sucessful - if false, see lastErr
  public synchronized boolean loadXML( ) {
  File xmlFile = new File( path, fname );
  System.out.println("NewsModel.loadXML start " +
  xmlFile.getAbsolutePath() );
  try {
  timestamp = xmlFile.lastModified();
  dateStr = new Date( timestamp ).toString();
  InputSource input = Resolver.createInputSource( xmlFile );
  // ... the "false" flag says not to validate (faster)
  // XmlDocument is in the com.sun.xml.tree package
  doc = XmlDocument.createXmlDocument (input, false);
  System.out.println("Created document");
  usable = true ;
  return true ;
  }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 );
  return false ;
  }catch( SAXException se ){
  lastErr = se.toString();
  System.out.println("loadXML threw " + lastErr );
  se.printStackTrace( System.out );
  return false ;
  }catch( IOException ie ){
  lastErr = ie.toString();
  System.out.println("loadXML threw " + lastErr +
  " trying to read " + xmlFile.getAbsolutePath() );
  return false ;
  }
  } // end loadXML

Если анализ XML завершился успешно и была создана объектная модель документа, вызывается метод locateCategories. Как показано в листинге 9.12, этот метод получает NodeList — список всех элементов article, и записывает его в переменную articleNodeList. Затем он вызывает метод processArticle для каждого элемента. Метод processArticle строит вектор элементов для каждого значения элемента cluster. Это как раз тот вектор, который выдает заголовки сообщений, если в запросе пользователя указана конкретная тематика.

Листинг 9.12. Метод locateCategories классифицирует заголовки (newsModel.java)

 public void  locateCategories(){
  Element dE = doc.getDocumentElement(); // the root element
  clusterHash = new Hashtable();
  articleNodeList = dE.getElementsByTagName("article");
  int act = articleNodeList.getLength();
  //System.out.println("Article count: " + act );
  for( int i = 0 ; i < act ; i++ ){
  Element aE = (Element) articleNodeList.item( i ) ;
  processArticle( aE );
  }
  }
 private void processArticle( Element artE ){
  NodeList clusterNL =
artE.getElementsByTagName("cluster");
  if( clusterNL.getLength() == 0 ) return ;
  Element clE = (Element)clusterNL.item(0);
  String clusterStr = clE.getFirstChild().getNodeValue().trim() ;
  // System.out.println("cluster ct " + clusterNL.getLength() +
  // " value: " + clusterStr );
  Object obj = clusterHash.get( clusterStr );
  Vector v = null ;
  if( obj == null ){
  v = new Vector();
  clusterHash.put( clusterStr, v );
  }
  else {v = (Vector)obj ;
  }
  v.addElement( artE );
  }

Выбор заголовков

Теперь мы подошли к рассмотрению методов сервлета, которые отвечают на запросы пользователя о поиске определенных заголовков сообщений. Для нашего примера мы ограничились двумя методами выбора заголовков: вы можете либо просмотреть всю коллекцию в поисках заголовков, которые содержат определенную последовательность символов, либо извлечь все заголовки, входящие в конкретную категорию.

При вызове метода articlesByKeyword, показанного в листинге 9.13, ему передается строка, содержащая одну или более последовательностей символов, разделенных запятыми. Первый шаг заключается в преобразовании этой строки keystring в массив типа String. Этот шаг выполняется методом prepKeys, который, кроме того, переводит все символы в верхний регистр. Далее метод searchArticle осуществляет поиск по всем элементам article и все совпадения возвращаются в виде массива ссылок на соответствующие элементы.

Листинг 9.13. Метод articlesByKeyWord вызывается сервлетом (NewsModel.java)

 // articles  by keyword appearance in headline
  // keys may be word or phrase, one or more, sep by comma
  // just use original order
  public Element[] articlesByKeyWord( String keystring ){
  String[] keys = prepKeys( keystring );
// upper case and separated
  Vector v = new Vector();
  int i ;
  int ct = articleNodeList.getLength();
  for( i = 0 ; i < ct ; i++ ){
  Element aE = (Element) articleNodeList.item( i ) ;
  if( searchArticle( aE, keys )){
  v.addElement(aE);
  }
  }
  Element[] ret = new Element[ v.size() ];
  for( i = 0 ; i < ret.length ; i++ ){
  ret[i] = (Element) v.elementAt(i);
  }
  return ret ;
  }
  // convert to upper case and separate at commas
  private String[] prepKeys( String s ){
  StringTokenizer st =
new StringTokenizer( s.toUpperCase(), ",");
  String[] ret = new String[ st.countTokens() ];
  int i = 0 ;
  while( st.hasMoreTokens() ){
  ret[i++] = st.nextToken().trim();
  }
  return ret ;
  }

Метод searchArticle, как показано в листинге 9.14, сложнее, чем вы, возможно, ожидали. Это объясняется некоторыми особенностями анализатора XML. Рассмотрим содержимое элемента <headline_text>, куда включена сущность (например, &атр;):

 <headline_text>Q&amp;A: Will  Sony I
 Rule the Digital World</    I headline_text>

Эта строка будет разделена анализатором на три объекта Node: два текстовых узла, разделенных узлом EntityReference. Так как нам нужен полный текст заголовка, для получения соответствующей строки вызывается метод getFullText, выдающий текст заголовка целиком.

Метод getFullText, который также показан в листинге 9.14, объединяет текст всех частей заголовка. Текст, представляющий узел EntityReference, должен быть построен как объединение с символами ; и & имени узла.

Листинг 9.14. Методы, которые поддерживают поиск заголовков по ключевым словам (NewsModel.java)

 // return  true if one of the keys appears
//in the headline_text element
  private boolean searchArticle( Element aE, String[] keys ){
  NodeList htNL =
 aE.getElementsByTagName("headline_text");
  if( htNL.getLength() == 0 ) return false ;
  // there is only one headline_text
  Element htE = (Element)htNL.item(0);
  String str = getFullText( htE ).toUpperCase() ;
  for( int i = 0 ; i < keys.length ; i++ ){
  if( str.indexOf( keys[i] ) >= 0 ) return true ;
  }
  return false ;
  }
  // this is needed to cope with headline text that has entities
  private String getFullText( Node nd ){
  NodeList nl = nd.getChildNodes();
  int ct = nl.getLength();
  if( ct == 0 ) return "";
  if( ct == 1 ) return nd.getFirstChild().getNodeValue();
  StringBuffer sb = new StringBuffer();
  for( int i = 0 ; i < ct ; i++ ){
  Node n = nl.item(i);
  if( n instanceof EntityReference ){
 // reconstruct &amp; notation
  sb.append( '&' ); sb.append( n.getNodeName());
  sb.append( ';' );
  }
  else {
  sb.append( n.getNodeValue() );
  }
  }
  return sb.toString();
  }

Метод locateCategories (см. листинг 9.12) создал коллекцию clusterHash, в которой элементы (заголовки сообщений) хранятся в соответствии со своими тегами <cluster>. Мы не совсем понимаем, почему в Moreover.com этот элемент называется cluster (кластер) — нам кажется, что более подходящим названием для этого тега было бы topic (тема); поэтому метод, приведенный в листинге 9.15, называется articlesByTopic (статьи по темам). В этом листинге также показан метод getAllTopics, который просто превращает весь список узлов articleNodeList в массив типа Element.

Листинг 9.15. Этот метод возвращает все элементы с заданным значением элемента cluster (NewsModel.java)

 // return  array of Element for this topic
  // or null if none available
  public Element[] articlesByTopic( String topic ){
  Vector v = (Vector) clusterHash.get( topic );
  if( v == null ) return null ;
  Element[] ret = new Element[ v.size() ];
  for( int i = 0 ; i < ret.length ; i++ ){
  ret[i] = (Element) v.elementAt( i );
  }
  return ret ;
  }
 public Element[] getAllTopics(){
  int ct = articleNodeList.getLength();
  Element[] ret = new Element[ ct ];
  for( int i = 0 ; i < ct ; i++ ){
  ret[i] = (Element)articleNodeList.item( i );
  }
  return ret ;
  }

Чтобы пользователь мог выбирать темы сообщений, нам нужно иметь список всех тем, имеющихся в текущей модели DOM. Для создания списка тематических категорий применяется класс NetNewsBean, который мы и будем рассматривать ближе к концу этой главы. В этом классе используется массив типа String, который создается в методе getTopics, приведенном в листинге 9.16. Обратите внимание на то, что для сортировки объектов Srting в данном массиве необходим метод shell Sort, потому что порядок следования ключей в хэш-таблице непредсказуем.

Листинг 9.16. Метод getTopics (NewsModel.java)

 // return  exact names of all topics available
  public String[] getTopics(){
  Enumeration keys = clusterHash.keys();
  String[] ret = new String[ clusterHash.size() ];
  int i = 0;
  while( keys.hasMoreElements() ){
  ret[i++] = (String)keys.nextElement();
  }
  shellSort( ret );
  return ret ;
  }

Метод formatElement, показанный в листинге 9.17, предлагает простой способ вставить текст из объекта art (который соответствует некоторому заголовку) в строку, обычно содержащую информацию относительно разметки HTML. Ниже приводится пример такой форматирующей строки, в которой содержатся теги, показывающие, куда нужно вставить элементы url, headline_text и source:

 <tr><td><a href="<%url>"x%headline_text>
</a> "&nbsp;  from <%source></ tdx/tr>

Работа этого метода заключается в отыскании символов <%, выделении имени элемента и вызове метода getContent для извлечения текста элемента из соответствующего объекта Element. Заметим, что метод getContent вызывает метод getFullText для получения полного текста выбранного элемента.

Листинг 9.17. Метод formatElement (NewsModel.java)

 // Element  known to be an article, formatting string
  public String formatElement( Element art, String fmt ){
  StringBuffer sb = new StringBuffer( 3 * fmt.length() );
  int p0 = 0 ;
  int p1 = fmt.indexOf("<%");
  int p2 = fmt.indexOf('>', p1);
  while( p1 > p0 && p2 > p1 ){
  sb.append( fmt.substring( p0, p1 ));
  sb.append( getContent( art, fmt.substring(p1 + 2, p2) ));
  p0 = p2 + 1 ;
  p1 = fmt.indexOf("<%", p0);
  if( p1 > p0 ){
  p2 = fmt.indexOf('>', p1);
  }
  }
  sb.append( fmt.substring( p0 ));
  return sb.toString();
  }
  // element known to be an article
  private String getContent( Element art, String key ){
  NodeList nl = art.getElementsByTagName( key );
  if( nl.getLength() == 0 ) return "";
  Element kE = (Element)nl.item(0);
  return getFullText( kE ) ;
  }

Последняя часть кода класса NewsModel, приведенная в листинге 9.18, содержит метод she! 1 Sort и некоторые другие служебные методы.

Листинг 9.18. Метод для сортировки и другие служебные методы (NewsModel.java)

 public void  shellSort (String[] srted ) {
  // h is the separation between items we compare.
  int h = 1;
  while ( h < srted.length ) {
  h = 3 * h + 1;
  }
  // now h is optimum
  while ( h > 0 ) {
  h = (h - 1)/3;
  for ( int i = h; i < srted.length; ++i ) {
  String item = srted[i];
  int j=0;
  for ( j = i - h;
  j >= 0 && compare( srted[j], item ) < 0;
  j -= h ) {
  srted[j+h] = srted[j];
  } // end inner for
  srted[j+h] = item;
  } // end outer for
  } // end while
  } // end sort
 // return -1 if a < b , 0 if equal, +1 if a > b
  int compare(String a, String b ){
  String aa = a.toUpperCase() ;
  String bb = b.toUpperCase() ;
  return bb.compareTo( aa ) ;
  }
 public String toString() {
  StringBuffer sb = new StringBuffer( "NewsModel " );
  if( !usable ){
  sb.append("is not usable due to ");
  sb.append( lastErr );
  return sb.toString();
  }
  sb.append("count of articles ");
  sb.append( Integer.toString( articleNodeList.getLength()) );
  sb.append("Unique clusters " + clusterHash.size() );
  sb.append("\n");
  Enumeration keys = clusterHash.keys();
  while( keys.hasMoreElements() ){
  String key = (String)keys.nextElement();
  Vector v = (Vector)clusterHash.get( key );
  sb.append(" Topic: " ); sb.append( key ) ;
  sb.append(" has " ) ; sb.append(Integer.toString( v.size()));
  sb.append("\n");
  }
  return sb.toString();
  }
  }

Главный управляющий класс

Теперь мы переходим к рассмотрению класса, который управляет процессом получения заголовков через определенные промежутки времени и обеспечивает доступ к ним сервлетов. Класс NetNewsSuper устроен согласно обычной схеме единичного класса (singleton). В нем имеются статические переменные и методы, которые гарантируют, что для каждого источника XML (URL-адрес вместе с именами папки и файла) создается только один экземпляр NetNewsSuper. Как показано в листинге 9.19, этот URL-адрес используется в качестве ключа для получения экземпляра NetNewsSuper из хэш-таблицы nnsHash или для его создания, если он еще не существует.

Листинг 9.19. Начало класса NetNewsSuper (NetNewsSuper.java) package com.XmlEcomBook.Chap09;

 package com.XmlEcomBook.Chap09;
import java.util.*;
  import java.io.* ;
public class NetNewsSuper extends java.lang.Thread
  {
  static Hashtable nnsHash = new Hashtable() ;
  static long classLoaded = System.currentTimeMillis();
  static long longTime = 1000 * 60 * 60 * 2 ;// two hours
  static int maxErrCt = 10 ;
  // source is URL + query, dest is abs
  //file path, destFname = name
  // hash stored by complete source string as key
  static synchronized NetNewsSuper
getNetNewsSuper(String source,
  String destPth, String destFname ){
  NetNewsSuper nns = (NetNewsSuper)nnsHash.get( source );
  if( nns == null ){
  nns = new NetNewsSuper( source, destPth, destFname ) ;
  nnsHash.put( source, nns );
  }
  return nns ;
  }
 static synchronized
void removeNetNewsSuper( String sourceURL ){
  Object obj = nnsHash.remove( sourceURL );
  if( obj == null ){
  System.out.println("removeNetNewsSuper of " +
sourceURL + " failed."  );
  }
  else { // allow Thread to die
  ((NetNewsSuper)obj).running = false ;
  }
  }

Причина, по который мы сделали класс NetNewsSuper расширением класса Thread, вместо того чтобы реализовать интерфейс Runnable, связана с удобством отладки и управления набором сервлетов. Самое простое — это написать сервлет, который перечисляет все работающие потоки (объекты Thread) в виртуальной машине процессора сервлетов. Вы можете отобразить и имя экземпляра, и результат вызова метода toString.

Как видно из кода конструктора класса, приведенного в листинге 9.20, имя потока устроено таким образом, что включает в себя имя извлекаемого файла. Когда вызывается .конструктор, ему передаются URL-адрес источника, имя папки и имя файла, которые используются для записи данных XML. Поскольку предполагается, что получение нового набора заголовков будет происходить в фоновом режиме, то данный поток получает минимальный приоритет.

Листинг 9.20. Переменные экземпляра и конструктор класса NetNewsSuper (NetNewsSuper.java)

 // instance  variables and methods follow
  String sourceURL ;
  String destPath, destFname ;
  public String errStr ;
  public boolean usable ;
  int errCt = 0 ;
  boolean running ;
 NewsModel newsM ;
  private NetNewsSuper
(String source, String dest, String fname ){
  sourceURL = source ; destPath = dest ; destFname = fname ;
  setName("NetNewsSuper " + fname );
  setPriority( Thread.MIN_PRIORITY );
  start();
  System.out.println("NetNewsSuper Thread started");
  }

Как показано в листинге 9.21, первое, что делает метод run, — вызывает метод checkSrc, который проверяет, существует ли уже требуемый файл XML. Если это не так, создается объект класса XMLgrabber и выполняется его метод doQueryNow для получения исходного файла XML. Если файл XML присутствует, то выполняется метод createModel, который создает новый объект NewsModel.

Листинг 9.21. Метод run класса NetNewsSuper (NetNewsSuper.java)

 // low priority  - check for need to update xml
  public void run(){
  running = true ;
  try { // runs when first started
  if( !checkSrc() ){
  XMLgrabber grab = new XMLgrabber
( sourceURL, destPath, destFname );
  System.out.println("NetNewsSuper runs doQueryNow");
  if( !grab.doQueryNow() ){
  errCt++ ;
  System.out.println
("NetNewsSuper.run - bad return from grab");
  }
  }
  createModel();
  }catch(Exception e1){
  errCt++ ;
  }
  while( running ){
  try {
  sleep( longTime );
  XMLgrabber grab = new XMLgrabber
( sourceURL, destPath, destFname );
  //System.out.println("NetNewsSuper.run runs doQueryNow");
  if( running && grab.doQueryNow() ){
  if( errCt > 0 ) errCt-- ;
  createModel();
  }
  else {
  errCt++ ;
  System.out.println
("NetNewsSuper.run - bad return from grab");
  }
  }catch(InterruptedException ie){
  errCt++ ;
  System.err.println("NetNewsSuper.run " + ie );
  }catch(Exception ee ){
  errCt++ ;
  System.err.println("NetNewsSuper.run " + ee );
  }
  if( errCt > maxErrCt ){
  System.out.println
("NetNewsSuper.run too many errors: " + errCt +" run exiting.");
  running = false ;
  }
  }
  System.out.println("Leaving NetNewsSuper.run method");
  }
 // return true if XML source file is found
  private boolean checkSrc(){
  File f = new File( destPath, destFname );
  return (f.exists() && f.canRead());
  }

В листинге 9.22 показан метод, который создает новый экземпляр класса NewsModel и затем вызывает методы экземпляра loadXML и locateCategories. В случае ошибки переменная usabl е устанавливается равной fal se.

Листинг 9.22. Этот метод создает новый объект NewsModel (NetNewsSuper.java)

 // xml source  known to exist, go for it
  private synchronized void createModel(){
  newsM = new NewsModel( destPath, destFname );
  if( !newsM.loadXML()){ // error in getting data
  errStr = newsM.lastErr ;
  usable = false ;
  }
  else {
  newsM.locateCategories();
  usable = true ;
  }
  }

Как мы увидим при обсуждении в следующем разделе классов NetNewsBean и NetNewsServ и как показано в листинге 9.23, сервлет запрашивает текущую модель NewsModel с помощью метода getNewsModel. Если она отсутствует, что может случиться из-за сбоя в сети, который прерывает нормальную работу метода run, метод getNewsModel делает попытку получить NewsModel заново.

Листинг 9.23. Метод getNewsModel возвращает NewsModel (NetNewsSuper.java)

 // Note that  there are two steps to getting a news
//model resident:
  // 1. grabbing the current XML to local file if not there already
  // 2. creating the NewsModel from the local XML
  public synchronized NewsModel getNewsModel() throws
Exception {
  if( newsM != null ) return newsM ;
  // must be newly created NetNewsSuper
  if( !checkSrc() ){
  XMLgrabber grab = new XMLgrabber( sourceURL, destPath,
destFname );
  //System.out.println("getNewsModel runs doQueryNow");
  if( !grab.doQueryNow() ){
  // System.out.println(" bad return from grab");
  return null ;
  }
  }
  // source exists, create model
  createModel();
  return newsM ; // may or may not be usable
  }

Метод toString, как показано в листинге 9.24, предоставляет краткую сводку о текущем состоянии объекта NetNewSuper.

Листинг 9.24. Метод toString (NetNewsSuper.java)

 public String toString()
  {
  StringBuffer sb = new StringBuffer( "NetNewsSuper for ");
  sb.append( sourceURL );
  if( newsM == null ){
  sb.append(" No NewsModel resident ");
  }
  else {
  sb.append(" NewsModel resident, status: " + usable );
  }
  sb.append(" class loaded: " );
  sb.append( new Date( classLoaded ).toString() );
  return sb.toString() ;
  }
}

Классы для отображения заголовков

Для отображения заголовков мы используем два класса — сервлет с именем NetNewsServ и класс для форматирования NetNewsBean. Существует множество способов задействовать на web-сайте все то, чему мы к этому моменту уже научились. Например, вы можете записывать в базу данных предпочтения каждого посетителя в отношении тематики сообщений и автоматически формировать начальную страницу для данного посетителя с учетом его интересов. Но в нашем случае мы используем более простой подход.

Сервлет NetNewsServ

Этот сервлет выполняет две существенные функции: метод doGet создает форму, которая позволяет пользователю выбирать интересующие его темы сообщений и/или задавать ключевые слова, а метод doPost осуществляет сам процесс отображения подходящих заголовков. В листинге 9.25 показано начало кода серв- лета. Чтобы не усложнять наш пример, мы жестко запрограммировали значение переменной queryStr, которая содержит URL для поиска соответствующего ресурса, путь и имя файла XML, а также значение переменной alias с URL-адре- сом сервлета. В реально работающей системе эти переменные будут считываться из файла свойств в методе init.

Листинг 9.25. Начало исходного кода NetNewsServ (NetNewsServ.java)

package com.XmlEcomBook.Chap09;
import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
public class NetNewsServ extends HttpServlet
  {
  static String version = "1.02 July 26, 2000";
  static String queryStr =
"http://www.moreover.com/cgi-local/page"  +
  "?wbrogden@bga.com+xml";
  static String destDir = "e:\\scripts\\netnews" ;
  static String queryFile = "xmldump.xml" ;
  static String alias = "http://www.lanw.com/servlet/netnews" ;
 String keywords = "Amazon,Dell,Microsoft";
  String fmt = "<tr><td><a href=\"<%url>\"  >
<%headline_text></a>" +
  " &nbsp; from <%source></td></tr>" ;
 public void init(ServletConfig config) throws ServletException
  {
  super.init(config);
  }

Метод doGet генерирует простую форму, которая позволяет выбрать одну или несколько тематических категорий и/или ввести ключевые слова. Как показано в листинге 9.26, он получает объект NetNewsBean для определенного источника новостей. Метод getTopicsAsSelect объекта NetNewsBean создает код для отображения списка возможных категорий. Получившаяся в результате страница HTML показана на рис. 9.2.

Листинг 9.26. Метод doGet создает простую форму (NetNewsServ.java)

 public void  doGet(HttpServletRequest req,
HttpServletResponse resp)
  throws ServletException, IOException
  {
  resp.setContentType("text/html");
  PrintWriter out = new PrintWriter(resp.getOutputStream());
 out.println("<HTML>");
  out.println("<HEAD><TITLE>NetNewsServ Output</TITLE>
</HEAD>");
  out.println("<BODY>");
  try {
  NetNewsBean nnb = new NetNewsBean( queryStr, destDir,
queryFile );
 out.println("<h1>The News</h1>");
  out.println("<p>Select the general categories you would like
to see.  "+ "You can also enter a list of key words or phrases
separated by "  + "commas and the system will locate any
headlines containing them.</p>"
  );
  out.println("<center><form method=\"POST\" action=\""  +
alias + "\" >" );
  out.println("Key Words: <input type=\"TEXT\" size=\"60\""  +
  " maxlength=\"120\" name=\"keywords\" ><br>");
  out.println("Select one or more topics (use &lt;ctrl&gt;click.)
<br>");
  out.println( nnb.getTopicsAsSelect() );
  out.println("<br>
<input type=\"SUBMIT\" value=\"Continue\"  >");
  out.println("</form></center><br>");
  footer( out );
  }catch(Exception e){
  errorMsg( out, "NetNewsServ.doGet ", e );
  }
  }

Рис. 9.2. Форма для выбора темы заголовков

Когда пользователь щелкает на кнопке Continue (Продолжить), методу doPost, приведенному в листинге 9.27, отправляется запрос. Если пользователь не ввел никаких ключевых слов и не выбрал никаких категорий в списке, вызывается метод doGet для обновления содержимого формы. В противном случае генерируется страница, содержащая таблицу HTML, строки которой создаются при помощи метода doNetNews. Разумеется, это сильно упрощенный вариант функционирования реального коммерческого сайта.

Листинг 9.27. Метод doPost (NetNewsServ.java)

 // assumes  response has been set to text/html
  private void errorMsg
( PrintWriter out, String msg, Exception ex ){
  header( out );
  out.println("<h2>Error: " ); out.println( msg );
  out.println("</h2><br>");
  if( ex != null ){
  ex.printStackTrace( out );
  }
  out.println("<br>");
  out.println("<a href=\"mailto:wbrogden@bga.com\">
Please  mail me the error message.</a><br>");
  footer( out );
  }
  private void header(PrintWriter out ){
  out.println("<html>");
  out.println("<head><title>Network News Servlet</title>
</head>");
  out.println("<body>");
  }
 private void footer(PrintWriter out ){
  out.println("<hr><br>Network News Servlet " +
version  + " <br>" );
  out.println("</body>");
  out.println("</html>");
  out.close();
  }
}

Как показано в листинге 9.28, метод doNetNews получает объект NetNewsBean и выводит полученные заголовки в виде строк таблицы.

Листинг 9.28. Этот метод форматирует полученные заголовки (NetNewsServ.java)

 // assumes  a table has been started
  // topics[] are tags from list, ie 0, 1 etc.
  private void doNetNews
( PrintWriter out,String keywords, String[] topics ){
  int i =0 ;
  try {
  NetNewsBean nnb = new NetNewsBean
( queryStr, destDir, queryFile );
  out.println("Update " + nnb.getDocDate());
  String[] tstr = nnb.getTopicsAsArray();
  if( keywords.length() > 0 ) {
  out.println("<tr><td>
Selected by keywords: " + keywords  + "</td></tr>");
  out.println( nnb.getContentByKeyWord( keywords, fmt )) ;
  }
  out.println("<hr>") ;
  if( topics == null ){ // none selected
  for( i = 0 ; i < tstr.length ; i++ ){
  out.println("<tr><td>topic: " + tstr[i] + "</td></tr>"  );
  out.println( nnb.getContentByTopic( tstr[i], fmt ) );
  }
  }
  else {
  for( i = 0 ; i < topics.length ; i++ ){
  int tn = Integer.parseInt( topics[i] );
  out.println("<tr><td>topic: " + tstr[tn] + "</td></tr>"  );
  out.println(nnb.getContentByTopic(tstr[ tn ], fmt));
  }
  }
  }catch(Exception e){
  out.println( "<tr><td>" );
  e.printStackTrace(out );
  out.println("</td></tr>");
  }
  }

Все остальные методы в классе NetNewsServ являются служебными; некоторые из них показаны в листинге 9.29.

Листинг 9.29. Некоторые служебные методы (NetNewsServ.java)

 // assumes  response has been set to text/html
  private void errorMsg
( PrintWriter out, String msg, Exception ex ){
  header( out );
  out.println("<h2>Error: " ); out.println( msg );
  out.println("</h2><br>");
  if( ex != null ){
  ex.printStackTrace( out );
  }
  out.println("<br>");
  out.println("<a href=\"mailto:wbrogden@bga.com\">
Please  mail me the error message.</a><br>");
  footer( out );
  }
  private void header(PrintWriter out ){
  out.println("<html>");
  out.println("<head><title>Network News Servlet</title>
</head>");
  out.println("<body>");
  }
 private void footer(PrintWriter out ){
  out.println("<hr>
<br>Network News Servlet " + version  + " <br>" );
  out.println("</body>");
  out.println("</html>");
  out.close();
  }
}

Класс NetNewsBean

Этот класс выполняет роль интерфейса между сервлетом и хранящимся в памяти объектом NewsModel, который соответствует конкретному источнику сообщений. Как показано в листинге 9.30, конструктор использует класс NetNewsSuper для получения текущего объекта NewsModel для заданного источника.

Листинг 9.30. Начало класса NetNewsBean (NetNewsBean.java) package com.XmlEcomBook.Chap09;

 package com.XmlEcomBook.Chap09;
import java.util.* ;
  import org.w3c.dom.* ;
public class NetNewsBean
  {
  static String noDataStr ="No Data is available";
  static String dataSourceErr = "Error when loading data " ;
  NewsModel newsM ; // has public boolean usable and errStr
 // create with source url string, dest file path, dest fname
  NetNewsBean( String source, String pth, String fn )
  throws Exception {
  NetNewsSuper nns =
NetNewsSuper.getNetNewsSuper( source,pth,fn );
  newsM = nns.getNewsModel() ; // throws exception
  }
 public String getDocDate(){
  if( newsM == null ) return noDataStr ;
  if( newsM.usable ) return newsM.dateStr ;
  return dataSourceErr ;
  }

В листинге 9.31 показаны методы, которые обеспечивают доступ к списку тематических категорий в конкретном объекте NewsModel. Метод getTopicsAsArray просто возвращает массив типа String, в то время как getTopicsAsSelect возвращает список в формате HTML.

Листинг 9.31. Методы, которые возвращают тематические категории в виде массива и в виде списка в формате HTML (NetNewsBean.java)

 public String[]  getTopicsAsArray(){
  if( newsM == null || !newsM.usable ) return null;
  return newsM.getTopics();
  }
  // return available topics as a Select control with values
  // matching the index of the topics array
  public String getTopicsAsSelect(){
  if( newsM == null ) return noDataStr ;
  StringBuffer sb = new StringBuffer(1000);
  if( newsM.usable ){
  String[] topics = newsM.getTopics();
  sb.append("
<select name=\"topics\" MULTIPLE size=\"3\">\r\n");
  for( int i = 0 ; i < topics.length ; i++ ){
  sb.append("<option value=\"");
  sb.append( Integer.toString( i ));
  sb.append("\" > ");
  sb.append( topics[i] );
  }
  sb.append("</select>\r\n");
  }
  else {
  sb.append( dataSourceErr );
  sb.append( newsM.lastErr );
  }
  return sb.toString();
  }

Метод getContentByKeyWord, показанный в листинге 9.32, контролирует выбор и формат заголовков, содержащих одно или несколько ключевых слов, введенных пользователем.

Листинг 9.32. Метод, контролирующий поиск заголовков по ключевым словам (NetNewsBean.java)

 public String  getContentByKeyWord( String kwds, String fmt ){
  if( newsM == null ) return noDataStr ;
  StringBuffer sb = new StringBuffer(1000);
  if( newsM.usable ){
  Element[] art = newsM.articlesByKeyWord( kwds );
  for( int i = 0 ; i < art.length ; i++ ){
  sb.append( newsM.formatElement( art[i], fmt ));
  sb.append("\n");
  }
  }
  else {
  sb.append( dataSourceErr );
  sb.append( newsM.lastErr );
  }
  return sb.toString();
  }

Альтернативным вариантом является представление всех заголовков новостей, которое осуществляется методом getAllTopics. Как показано в листинге 9.33, для каждого заголовка этот метод создает строку, содержащую форматированный текст заголовка.

Листинг 9.33. Метод getAHTopics форматирует все имеющиеся заголовки (NetNewsBean.java)

 public String  getAllTopics( String fmt ){
  if( newsM == null ) return noDataStr ;
  StringBuffer sb = new StringBuffer(1000);
  if( newsM.usable ){
  Element[] art = newsM.getAllTopics();
  for( int i = 0 ; i < art.length ; i++ ){
  sb.append( newsM.formatElement( art[i], fmt ));
  sb.append("\n");
  }
  }
  else {
  sb.append( dataSourceErr );
  sb.append( newsM.lastErr );
  }
  return sb.toString();
  }

На рис 9 3 показана страница с заголовками свежих новостей, выбранных в соответствии с указанными ключевыми словами и форматированных методом getContentByTopic, который приведен в листинге 934

Рис. 9.3. Представление заголовков сообщений

Листинг 9.34. Метод getContentByTopic (NetNewsBean.java)

 public String  getContentByTopic( String content, String fmt ){
  if( newsM == null ) return noDataStr ;
  StringBuffer sb = new StringBuffer(1000);
  if( newsM.usable ){
  Element[] art = newsM.articlesByTopic( content );
  if( art == null ) return dataSourceErr ;
  for( int i = 0 ; i < art.length ; i++ ){
  sb.append( newsM.formatElement( art[i], fmt ));
  sb.append("\n");
  }
  }
  else {
  sb.append("getContentByTopic " + dataSourceErr );
  sb.append("getContentByTopic " + newsM.lastErr );
  }
  return sb.toString();
  }
 public String toString()
  {
  StringBuffer sb = new StringBuffer("NetNewsBean ");
  return sb.toString() ;
  }
}

Возможные усовершенствования

Хотя основанный на использовании DOM подход является вполне приемлемым для систем с небольшой нагрузкой на сервер, он не слишком эффективен, так как для доступа к каждой странице нужно выполнять одни и те же вычисления Хуже всего, когда в тексте заголовков встречаются ссылки на сущности

Разумной альтернативой хранению DOM в памяти является представление отдельного заголовка через класс Java и создание коллекции таких классов путем перехвата событий анализатором SAX Если у этого класса также имеется возможность применять требуемый формат HTML к заголовкам сообщений, то конструктор может заранее форматировать весь элемент и хранить только строку, представляющую данный элемент, плюс строку, содержащую текст заголовка для поиска

Создание пользовательской библиотеки тегов JSP для заголовков не требует написания длинных программ, но такая библиотека упростила бы работу web- дизайнера по наделению любой страницы средствами представления заголовков новостей, причем без необходимости модифицировать код Java.


LANWrights

Experiment With Catalog Presentation

XMLgifts goodies


Notes on files for Chapter 3

Many of these files are further refined in Chapter 4. This
directory contains the versions that appear in Chapter 3
listings.

The CatTest.html file has buttons to activate the full
catalog and keyword select functions. Note that this version
is set to address a Tomcat server on port 8080. This may
have to be changed depending on your server.

The demo will run without the catalog.css file since
it just influences the HTML presentation style.
If you want to use it, it needs to be where the server
can find it. For example:

http://localhost:8080/XmlBook/catalog/catalog.css


The images.zip zipped file has the product images that go with
the products listed in the catalog.xml file.
On the CDROM there needs to be the following directory
structure

 -- catalog <- catalog.css file goes here typically
     |
     +-- images   <- most of the image files go here
          |
          +- covers  <- 5 image files go here - see the zip.

--
WBB - wbrogden@bga.com  Chief Scientist, LANWrights, Inc.
Java Programmer Certification information and mock exam
at  http://www.lanw.com/java/javacert/
 - images.zip


Customer Info

First Name: Last Name:

Address line 1:
Address line 2:
City: State: Zip:

Email Address:

Phone Number:


Catalog Upkeep

Add Product:

Product ID: Product Line:

Delete Product:

Product ID:

Edit Product:

Product ID:


Company News Utility Entry Page

Authorized users only!
Please enter your user name and user password


Notes on web.dtd and web.xml files - chapter 10

web.dtd is the DTD required by the Java Servlet v2.2
API for web applications. The formal description for
use in web.xml is:

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">

Note that the 2.3 API may require a different DTD.

web.xml is an example Web Application Deployment Descriptor

This is the version I have been using when working with
Tomcat 3.1 and the various servlets in the book.


  • Введение
    • XML и Java
      • Сервлеты Java и JSP-страницы
    • Кому стоит купить эту книгу
    • Что вам потребуется
    • Что содержится в этой книге
    • Что содержится на компакт-диске
    • Соглашения, используемые в этой книге

Введение

Много лет назад Билл Брогден присутствовал на конференции, посвященной обработке данных об окружающей среде. Одной из целей конференции было обсуждение серьезных проблем, возникающих при обмене данными между учеными, особенно проживающими в разных странах. При попытке согласовать данные, поступившие из различных источников, любому исследователю приходилось иметь дело с различными форматами хранения данных, всевозможными способами записи и типами компьютеров — это выглядело как строительство Вавилонской башни.

Конференцию открыл один из заведующих кафедры экологии (имевший гуманитарное образование). Он начал свое выступление с того, что описал глубокое изумление, которое он испытал, узнав, что на магнитной ленте с данными научно-технического характера отсутствует встроенное описание этих данных. Без подробной информации, полученной из отдельного источника, такая лента становится совершенно бесполезной. Остальные участники конференции согласились, что эта ситуация неудовлетворительна, но никто не знал, как исправить положение.

Теперь, с появлением языка XML, наконец реализовалась идея о том, что любые данные должны сопровождаться собственным описанием. Широкое распространение, которое получил расширяемый язык разметки (Extensible Markup Language, XML) за время, прошедшее с 1998 года, когда первый стандарт XML был опубликован консорциумом W3C (World Wide Web Consortium), служит подтверждением того, что многие отрасли науки и промышленности созрели для серьезных изменений в области описания данных.

Одна из причин быстрого распространения XML заключается в том, что Интернет способствует знакомству широких слоев пользователей с HTML, стандартным языком разметки web-страниц. HTML помог многим пользователям получить представление о том, как с помощью тегов в простой текст можно включать дополнительную информацию. Основываясь на этой идее, можно сделать следующий небольшой, хотя и нетривиальный, шаг — перейти к использованию тегов XML для описания данных.

Наиболее очевидным преимуществом XML является возможность расширения этого языка для решения специфических для каждого пользователя задач. Эта возможность делает XML столь привлекательным для тех, кому приходится работать с различными типами данных в какой-либо специальной области. Другoe, не столь очевидное преимущество заключается в том, что для любых расширений этого языка при проверке и обработке данных можно продолжать использовать инструментарий XML, если только при записи данных соблюдены правила языка.

Оказалось, что подобная гибкость — как раз то, что требуется для многих практических приложений. Компании, которые обмениваются информацией с другими компаниями через Интернет, все чаще и чаще отказываются от стандарта EDI (Electronic Data Interchange — электронный обмен данными) в пользу XML. Предполагается, что эти так называемые В2В (Business-to-business — бизнес для бизнеса) приложения содержат наибольшее количество данных в формате XML во всем Интернете.

В настоящее время имеется большое количество сайтов, которые представляют интерес для пользователей Java и XML. Прекрасным местом для начала поиска новостей и справочных материалов является сайт oasis.oasis-open.org, который принадлежит организации по продвижению стандартов структурирования информации. Эта самая большая в мире независимая некоммерческая организация, занимающаяся стандартизацией приложений XML.

XML и Java

Если вы интересуетесь Java, вам наверняка приходится часто слышать и о XML, так как с самого начала язык Java был тесно связан с XML. Джон Босак (Jon Bosak), один из первых инициаторов стандартизации XML, работает в компании Sun, и большая часть работы по созданию анализаторов XML на первом этапе была сделана с помощью Java.

Так или иначе, интерес к XML распространился всюду в компьютерной промышленности, от огромных корпораций, таких как IBM и Microsoft, до неформальных рабочих групп на всех уровнях. Силами отдельных программистов и небольших групп сделаны многие существенные усовершенствования XML. Например, неформальной группе «XML Devian», организовавшейся на основе списка рассылки в Интернете, принадлежит заслуга создания упрощенного интерфейса API для XML, известного как SAX. Разработка этого проекта исходно велась на языке Java.

Сервлеты Java и JSP-страницы

В нашей книге мы будем использовать сервлеты [Так, по-видимому, по аналогии с апплетами авторы называют программы, написанные на языке Java и выполняющиеся на сервере — Примеч ред. ] Java и JSP-страницы (JavaServer Pages) для создания web-приложений, работающих с данными XML. Сервлеты выполняются на web-сервере, осуществляя те функции, которые ранее возлагались на сценарии CGI. JSP-страницы позволяют объединять элементы разметки HTML и фрагменты кода Java. Технология сервлетов и JSP-страниц развивается примерно так же быстро, как язык XML.

Во время написания этой книги текущей версией API для сервлетов Java была версия 2.2, выпущенная в декабре 1999 года. Этот интерфейс API содержит много изменении по сравнению с предыдущей версией, особенно в области создания web-приложений и стандартизованной настройки приложения на базе XML.

Стандартизация технологии JavaServcr Pages происходила гораздо медленнее. Развитие интерфейса API надолго затормозилось на версии 0.92. Отсутствие ясных указаний со стороны Sun привело к тому, что многие компании по созданию программного обеспечения разработали свои собственные технологии для объединения пользовательских тегов в HTML с возможностью поиска по базе данных и другими аналогичными возможностями.

Когда компания Sun разработала редакцию Java, которая впоследствии стала называться Java 2 Enterprise Edition (редакция Java 2 для корпоративных программных систем на базе Java), стало ясно, что технология JSP будет играть значительную роль в общей концепции создания web-приложений. Поэтому дальнейшее развитие API для JSP-страниц пошло быстрее, появились версии 1.0 и 1.1. Когда была опубликована редакция J2EE, стало ясно, что сервлеты Java и JSP-страницы уже в значительной степени интегрированы в общую картину развития web-приложений на Java. Другой важной особенностью J2EE явилось использование XML для описания web-приложений.

Наиболее значительное в этой области событие произошло в октябре 1999 года. Компания Sun передала организации Apache весь исходный код API 2.2 для сер- влетов и API 1.1 для JSP-страниц с целью дальнейшей разработки открытых эталонных реализаций контейнеров сервлетов Java и JSP-страниц. Эта разработка не зависит от J2EE и гарантирует постоянное наличие в свободном доступе новейших реализаций интерфейсов API для сервлетов и JSP-страниц.

Кому стоит купить эту книгу

Мы предполагаем, что вы на базовом уровне знакомы с HTML, Java и web-серверами и у вас имеется доступ к компьютеру, на котором можно установить небольшой web-сервер, стандартный компилятор Java и другие служебные программы. Редактировать данные XML можно с помощью любого текстового редактора, либо вы можете использовать один из многочисленных коммерческих или находящихся в свободном доступе специализированных редакторов XML.

Если эти требования выполнены, то практически любой человек, который работает с данными в той или иной форме, потенциально является пользователем XML. Трудно представить себе такую область человеческой деятельности, для которой в Интернете не нашлось бы ни одного относящегося к ней проекта, использующего язык XML.

Что вам потребуется

Для многих проектов мы использовали находящийся в свободном доступе небольшой web-сервер —Jakarta-Tomcat (http://jakarta.apache.org), содержащий комплект необходимых разработчику инструментальных средств. Если у вас еще нет web-сервера, то для начала Tomcat является хорошим вариантом. Также мы использовали сервер JRun (www.allaire.com) — как в автономном режиме, так и в качестве дополнения к web-серверам Microsoft PWS и IIS. Еще вы можете испытать сервер Orion (www.orionserver.com), в который включено хорошее учебное пособие по библиотеке тегов JSP.

Служебные программы и комплекты инструментальных средств для работы с XML, написанные на Java, появляются как грибы после дождя. Для того чтобы придерживаться определенных стандартов, мы использовали версию JAXP 1.0 (Java API for XML parsing — интерфейс API Java для анализа документов XML). Этот пакет можно загрузить с web-сайта java.sun.com. Следующая версия пакета JAXP во время создания этой книги проходила стадию бета-тестирования, так что к моменту выхода книги она, вероятно, уже будет доступна.

Что содержится в этой книге

Цель этой книги — познакомить разработчиков с технологиями использования XML в программах на Java. Вместо того чтобы рассказывать о теоретической стороне дела, мы сразу переходим к практическому применению этих технологий на примере построения коммерческого web-сайта.

  • Глава 1. В этой главе вы найдете введение в XML с объяснениями всех каверзных моментов этого языка и типичных способов его использования. Также в этой главе приводится обзор двух моделей обработки данных XML с помощью Java — DOM и SAX.

  • Глава 2. Эта глава посвящена решению вопросов, возникающих при разработке каталога товаров на XML. Этот язык является настолько гибким, что иногда бывает непросто выбрать один из многих способов представления данных. Выбранная структура каталога иллюстрирует многие важные задачи, которые вам придется решать при разработке собственного проекта подобного рода. Построенный нами каталог, который дорабатывается в следующих главах, включает описания более сотни наименований товаров.

  • Глава 3. Эта глава начинается с обзора интерфейсов API для сервлетов Java и JSP-страниц, используемых для создания динамических web-страниц Затем мы рассматриваем стандартные интерфейсы API Java для извлечения данных из документов XML, после чего мы объединяем все эти функции в сервлете Java, предназначенном для получения информации из каталога и отображения ее в формате HTML. В числе прочих этот сервлет реализует возможность поиска товаров по ключевым словам.

  • Глава 4. В этой главе мы расширяем классы для представления каталога из главы 3 и создаем действующую корзину покупателя, которая позволяет покупателю заказывать товары через Интернет. В процессе создания этого приложения мы рассказываем о том, как концепция мониторинга сеанса реализована в сервлетах Java.

  • Глава 5. Теперь, когда у нас имеется корзина покупателя, нам нужно обеспечить возможность получения денег от клиента и оформления заказа (разумеется, с использованием XML). В нашем примере для представления соответствующей информации клиенту используется технология JavaServer Pages.

  • Глава 6. В этой главе мы исследуем задачу обновления каталога, отформатированного средствами XML, через специальный интерфейс в режиме подключения к сети.

  • Глава 7. Насущной потребностью для любого коммерческого сайта является способность собирать информацию о своих клиентах. В этой главе приводится основанная на XML система опроса, в которой предусмотрена возможность ветвления (возможность задавать различные вопросы различным пользователям в зависимости от их ответов на определенные специальные вопросы). Возникающая задача анализа собранных результатов дает нам возможность продемонстрировать, как с помощью интерфейса SAX обрабатывать большие документы XML.

  • Глава 8. Каждому коммерческому сайту необходимо как-то информировать клиентов о новостях компании. В этой главе на основе XML разрабатываются гибкая система для отображения новостей компании, а также сервлеты и JSP-страницы для ее поддержки.

  • Глава 9. В наши дни многие коммерческие сайты включают в свои домашние страницы заголовки новостей, пытаясь тем самым заинтересовать пользователей и стимулировать их к частому посещению сайта. Как показано в этой главе, вы можете обеспечить присутствие на вашем сайте текущих новостей буквально по сотням тематических категорий, и для этого не нужен целый штат репортеров — всего лишь XML и Java.

  • Глава 10. В предыдущих главах рассказывается о деталях организации ресурсов для web-приложения на Java. В этой главе мы приводим обзор спецификации Sun для версии API 2.2 сервлетов Java и обсуждаем интерфейсы для Java и XML следующего поколения.

Что содержится на компакт-диске

Естественно, книга по технологиям Java и XML должна сопровождаться работающими примерами применения этих технологий. Для глав 3-6 мы создали на XML виртуальный каталог с описанием более сотни забавных товаров для воображаемого коммерческого web-сайта. Другие главы также содержат некоторые примеры файлов XML для данного приложения.

Исходный код для всех этих программ вы найдете на компакт-диске, в папках, названия которых соответствуют нумерации глав. Поскольку подготовка компакт-диска относится к тем видам работы над книгой, которые заканчиваются в последнюю очередь, файлы на компакт-диске могут содержать исправления или добавления, отсутствующие в листингах, включенных в текст книги.

Соглашения, используемые в этой книге

Мы старались придерживаться максимально простого способа организации материала. Соглашения об обозначениях в этой книге знакомы всем читателям. Для фрагментов кода и текстов программ используется моноширинный шрифт, пример которого вы видите в следующем объявлении метода:

public void _jspService(HttpServletRequest request,
HttpServletResponse response)

ПРИМЕЧАНИЕ

Мы иногда будем использовать эту врезку для того, чтобы вставить какое-либо замечание, выходящее за рамки рассматриваемого в основном тексте вопроса.

ВНИМАНИЕ

Эту врезку мы используем для того, чтобы обратить ваше внимание на возможные сложности, например отклонение от стандарта.

СОВЕТ

Здесь вы найдете советы тех, кто действительно знает решение.


  • Приложение А. Интерфейсы API для сервлетов и JSP-страниц
    • Параметры приложений
      • Создание сервлета
      • Методы класса HttpServlet
      • Интерфейс ServletContext
    • Получение информации о запросе
      • Методы, добавляемые интерфейсом HttpServletRequest
        • Методы, связанные с безопасностью
        • Методы, связанные с сеансами и cookie
      • Другие методы интерфейса ServletRequest
        • Класс HttpSession и классы, связанные с cookie
        • Методы класса HttpSession
        • Методы интерфейса HttpSessionListener
        • Методы класса Cookie
        • Специальные объекты, связанные с запросом
    • Формирование ответа пользователю
      • Методы интерфейса ServletResponse
      • Методы, добавляемые интерфейсом HttpServletResponse
      • Выходные данные JSP-страниц
    • Ошибки и исключения
      • Ошибки и исключения JSP-страниц
    • Коды состояний и ошибок HTTP
    • Интерфейс API для JSP-страниц
    • Класс PageContext
      • Доступ к стандартным переменным
      • Класс JspWriter
    • Пакет javax.servlrtjsp.tagext
      • Класс BodyContent

Приложение А.

Интерфейсы API для сервлетов и JSP-страниц

Работая с интерфейсами прикладных программ (API) сервлетов и JSP-страниц, мы обнаружили, что документация JDK, очень основательная в отношении описания классов, содержит мало полезной функциональной информации. Формальные описания API 2.2 для сервлетов и API 1.1 для JSP-страниц также, на наш взгляд, трудно применить на практике. Цель этого приложения — представить наиболее часто используемые интерфейсы API с функциональной точки зрения, сопровождая их описание рекомендациями по применению.

Мы будем заниматься следующими пакетами:

  • javax.servlet — базовый обобщенный пакет сервлетов;

  • javax.servlet.http — специализированные расширения для web-страниц;

  • javax.sevlet.jsp — классы для создания JSP-страниц;

  • javax.sevlet. jsp.tagext — специализированные расширения классов JSP- страниц, которые позволяют создавать пользовательские библиотеки тегов.

Параметры приложений

В интерфейсах API для сервлетов и JSP-страниц параметры рассматриваются как объекты, обладающие определенной областью видимости. Эта область состоит из тех частей системы, в которых применение данного параметра является корректным. Перечислим области видимости в порядке убывания: сервер, приложение, сеанс, запрос и страница.

  • Сервер. Параметры из этой области могут использоваться во всех приложениях.

  • Приложение. Sun термином «web-приложение» называет совокупность сервлетов, JSP-страниц и других ресурсов, установленных в определенном адресном пространстве сервера. Класс Servl etContext предназначен для обеспечения возможности совместного использования информации всеми ресурсами, входящими в приложение. Это осуществляется с помощью объектов, которые доступны всем ресурсам приложения и относятся к области видимости приложения. Данная область видимости влияет на интерпретацию относительных URL-адресов.

  • Сеанс. Как было сказано в главе 3, сервлеты и JSP-страницы могут хранить специфическую для данного пользователя информацию в объекте HttpSession, которым управляет процессор сервлетов. Объекты, хранящиеся таким образом, называются объектами области видимости сеанса.

  • Запрос. Запрос на самом деле может обрабатываться несколькими страницами с помощью механизма переадресации (метода forward). Запрос (объект Servl etRequest или HttpServl etRequest) может включать в себя дополнительные объекты.

  • Страница. В сервлетах, созданных процессором JSP-страниц, может иметь место область видимости отдельной страницы. Если запрос обрабатывается с помощью метода forward или include, переменная, имеющая данную область видимости, доступна только на той странице, где она создана.

Создание сервлета

Поскольку методы сервлета определяются в интерфейсе, а интерфейсы не могут определять конструкторы, инициализация сервлета полностью осуществляется в методе init. Процессор сервлетов непременно вызывает метод init после того, как сервлет разработан, и прежде, чем обработан первый запрос. Методу init, согласно определению интерфейса Servlet, передается объект ServletConfig, который может быть использован для получения параметров инициализации. Между интерфейсами SevletConfig и ServletContext имеют место отличия, которые нужно учитывать.

  • SevletConfig — объекты, использующие этот интерфейс, содержат информацию, которая требуется во время инициализации сервлета. Причина частых таинственных ошибок времени выполнения в сервлете заключается в том, что перед вызовом метода initO не было вызова метода super.init().

  • Servl etContext — объекты, использующие этот интерфейс, позволяют сервлету находить информацию о процессоре сервлетов, в котором данный сервлет выполняется, и о его окружении. Вы можете получить интерфейс Servl etContext из объекта Servl etConfig.

Все процессоры сервлетов предусматривают определение параметров инициализации, которые должны быть переданы данному сервлету. Соглашения о том, каким образом администратор сервера устанавливает эти параметры, могут быть различными для разных процессоров, но основная идея остается неизменной. Будем надеяться, что количество производителей, использующих стандартный подход Sun с применением XML, будет увеличиваться.

Имя параметра связывается с текстовым значением. Программисты, знакомые с апплетами Java, могут заметить здесь аналогию с тем, как контейнер апплетов (браузер) передает именованные параметры апплету. Три наиболее распространенных метода объекта Servl etConfig приведены в табл. А1.

Таблица А.1. Методы объекта ServletConfig

Тип возвращаемого значения | Метод | Описание
String | getlnitParameter (String name) | Этот метод возвращает строку String, соответствующую имени параметра (Stri ng name), либо null , если такого параметра не обнаружено
Enumeration | getInitParameterNames() | Этот метод возвращает перечисление Enumeration имен всех параметров
ServletContext | getServletContext() | Этот метод получает интерфейс ServletContext, в котором выполняется сервлет

В табл. А.2 перечислены методы, которые должны быть предусмотрены в вашем пользовательском сервлете, когда он реализует интерфейс Servlet. Пакет javax.servlet также включает класс GenericServlet, который реализует интерфейс Servlet. Этот класс содержит некоторые вспомогательные методы, например вариант метода init без аргументов. Вы можете переопределить его и использовать вместо метода init, которому передается аргумент ServletConfig.

Таблица А.2. Методы интерфейса Servlet

Тип возвращаемого значения | Метод | Описание
void | init (ServletConfig config) | Вызывается контейнером сервлетов до того, как начнется обработка запроса пользователя. Не забудьте о вызове метода super.init(config)
void | destroy( ) | Вызывается контейнером сервлетов для указания сервлету, что он должен быть удален
ServletConfig | getServletConfig() | Возвращает объект ServletConfig, который содержит параметры для инициализации и запуска сервлета. Обратите внимание: этот объект буден равен null , если вы забудете вызвать метод super, init (config) в методе init
Srting | getServletlnfo() | Необязательный метод, который возвращает информацию о сервлете, например данные о его авторе, номер версии и сведения об авторских правах
void | service(ServletRequestreq, ServletResponseres) | Вызывается контейнером сервлетов для того, чтобы сервлет получил возможность ответить на запрос

Методы класса HttpServlet

Класс HttpServl et добавляет методы, которые поддерживают HTTP-запросы. Ваш пользовательский класс сервлета должен переопределить хотя бы один из этих методов, как показано в табл. А.З.

Таблица А.3. Методы класса HttpServlet, которые обычно переопределяются

Переопределяемый метод | Поддерживаемый запрос
doGet(ServletRequest req. ServletResponse resp) doPost(ServletRequest req, ServletResponse resp) doPutCServletRequest req, ServletResponse resp) doDelete(ServletRequest req, ServletResponse resp) | HTTP GET HTTP POST HTTP PUT НTTР DELETE

Интерфейс ServletContext

Процессор сервлетов создает объект, этот интерфейс ServletContext, и делает его доступным для сервлетов. Это исходный способ связи между сервлетом и его контейнером. В каждом web-приложении может содержаться множество сервлетов и JSP-страниц. Все они будут совместно использовать интерфейс ServletContext. В табл. А.4 приведены методы, с помощью которых объекты и параметры становятся доступными для совместного использования через интерфейс Servl etContext. Остатьные методы перечислены в табл. А.5.

Таблица А.4. Методы интерфейса ServletContext, относящиеся к параметрам и атрибутам

Тип возвращаемого значения | Метод | Описание
Object | getAttribute(Stnng name) | Возвращает атрибут контейнера сервлета с заданным именем или null , если атрибут с таким именем не обнаружен
Enumeration | getAttn buteNames ( ) | Перечисление Enumeration содержит перечень имен атрибутов (строк String) объекта класса ServletContext (контекста сервлетов)
void | removeAttribute (String name) | Удаляет атрибут с заданным именем из объекта класса ServletContext (контекста сервлетов)
void | setAttribute(String name, Object object) | Связывает объект с заданным именем атрибута в данном контексте сервлетов
Srting | getlnitParameter (String name) | Возвращает строку String, содержащую значение указанного именованного параметра инициализации в данном контексте, или null, если параметр не существует
Enumeration | get InitParameterNames ( ) | Возвращает имена параметров инициализации объекта ServletContext в виде перечисления (объекта Enumeration) объектов типа String либо пустой объект Enumeration, если параметры инициализации отсутствуют

Таблица А.5. Остальные методы интерфейса ServletContext

Тип возвращаемого значения | Метод | Описание
Servl etContext | getContext(String uripath) | Возвращает объект ServletContext, который соответствует указанному URL-адресу на данном сервере
String | getMimeTypeCString file) | Возвращает тип MIME указанного файла или null , если такой тип MIME не известен
RequestDispatcher | getNamedDi spatcher (String name) | Возвращает объект RequestDispatcher, который выполняет функции оболочки для указанного сервлета
RequestDispatcher | getRequestDi spatchet (String path) | Возвращает объект RequestDispatcher, который выполняет функции оболочки для ресурса, размещенного по указанному адресу
String | getRea!Path(String path) | Возвращает строку String, содержащую фактический путь для указанного виртуального пути
URL | getResourcet String path) | Возвращает URL для ресурса, путь к которому является аргументом данного метода
InputSrteam | getResourceAsStream (String path) | Возвращает ресурс, расположенный по указанному пути, в виде объекта InputStream
String | getServerlnfoO | Возвращает имя и версию контейнера сервлетов, в котором выполняется данный сервлет
int | getMajorVersionO | Возвращает основной номер версии API сервлетов Java, которую поддерживает данный контейнер (например, 2 для версии 2.1)
int | getMinorVersionO | Возвращает дополнительный номер версии API сервлетов Java, которую поддерживает данный контейнер

Получение информации о запросе

Сервлет получает информацию о запросе пользователя в виде объекта, реализующего интерфейс javax. servlet.ServletRequest или javax. servlet.http. HttpServl etRequest, созданный процессором сервлетов. Сервлеты, которые расширяют интерфейс javax.servlet.GenericServlet или реализуют интерфейс javax.servlet.Servlet, получают объект Servl etRequest (обычно с именем req). JSP-страницы всегда получают объект класса HttpServl etRequest с именем request, поскольку JSP-страницы используются только в контексте HTTP.

Основные методы для получения информации о запросе содержатся в интерфейсе Servl etRequest (табл. А.6), в то время как HttpServl etRequest добавляет некоторые методы для получения информации о протоколе.

Таблица А.6. Получение информации о запросе пользователя из объекта ServletRequest

Тип возвращаемого значения | Метод | Описание
Enumeration | getParameterNames ( ) | Имена параметров, выделенные из запроса при его анализе, возвращаются в виде объекта Enumeration (перечисления), а не массива типа String (массива строк), так как пары имя-значение хранятся в хэш-таблице, а объекты Hashtable возвращают список всех своих ключей в виде перечисления
String | getParameterCString name) | Этот метод возвращает значение типа String, соответствующее имени параметра, или null , если это имя не было обнаружено в запросе. Всегда полезно проверять, не равно ли возвращенное значение null. Если в запросе окажется больше одного параметра с одним и тем же именем, то возвращается значение первого из них
String | getParameterValues (Srting name) | Если одному имени может быть сопоставлено более одного значения, следует использовать этот метод. В возвращаемом массиве строк сохраняется исходный порядок следования параметров. Если не существует ни одного параметра с указанным именем, возвращается null

Методы, добавляемые интерфейсом HttpServletRequest

Интерфейс HttpServletRequest расширяет интерфейс ServletRequest, добавляя некоторое количество полезных методов, как показано в табл. А.7. Большинство из этих методов позволяют получить сведения о заголовках HTTP-запроса или характеризуют способ, которым этот запрос попадает к данному сервлету. Ни один из методов get, который получает данные из заголовка запроса, не чувствителен к регистру.

Таблица А.7. Методы интерфейса HttpServletRequest, характеризующие запрос

Тип возвращаемого значения | Метод | Описание
String | getContextPath( ) | Возвращает часть URI запроса, которая идентифицирует контекст запроса
long | getDateHeader(String name) | Возвращает значение указанного заголовка (который является датой), преобразованное к типу long (количество миллисекунд, прошедших с с 00:00:00 01.01.1970)
String | getHeader(String name) | Возвращает значение указанного заголовка запроса. Имя не чувствительно к регистру
Enumeration | getHeaderNames( ) | Возвращает перечень всех имен, содержащихся в заголовке запроса
Enumeration | getHeaders(String name) | Возвращает все значения указанного заголовка запроса в виде перечисления объектов типа String
int | get!ntHeader(String name) | Вспомогательный метод, который пытается преобразовать указанный заголовок запроса в целочисленный формат типа int. Если заголовок не обнаружен, возвращает -1. Если заголовок не может быть преобразован к типу int, вызывает исключение NumberFormatExcepti on
String | getMethod( ) | Возвращает имя метода HTTP, которым выполнен запрос (например, GET, POST или PUT)
String | getPathlnfo( ) | Возвращает часть URI после адреса сервлета. Если эта часть отсутствует, возвращается null
String | getPathTranslated( ) | Преобразует часть URI, расположенную после имени сервлета, но перед строкой запроса (то есть результат выполнения метода getPathlnfo), в физический путь
String | getQueryString( ) | Возвращает строку запроса, содержащуюся в URL запроса после указания пути
String | getRequestURI( ) | Возвращает часть URL запроса, начиная от имени протокола и до строки запроса в первой строке запроса HTTP
String | getServletPath( ) | Возвращает часть URL запроса, которая соответствует данному сервлету

Методы, связанные с безопасностью

Методы обеспечения безопасности, перечисленные в табл. А.8, могут не поддерживаться более старыми ядрами сервлетов.

Таблица А.8. Методы интерфейса HttpServletRequest, связанные с безопасностью

Тип возвращаемого значения | Метод | Описание
String | getAuthTypeO | Возвращает название схемы аутентификации, которая используется для защиты сервлета (например, BASIC или SSL), или null , если сервлет не был защищен
String | getRemoteUserO | Если в сеансе используется проверка прав доступа, возвращает регистрационное имя пользователя или null, если пользователь не прошел проверку
Principal | getUserPrincipaK) | Возвращает объект Java. security. Principal, содержащий имя очередного пользователя, прошедшего проверку прав доступа
Boolean | isUserlnRoleCString role) | Возвращает true, если пользователь, прошедший проверку, включен в класс пользователей, определяемый указанным в качестве параметра «ролевым именем»

Методы, связанные с сеансами и cookie

Поскольку концепция cookie связана с протоколом HTTP, все методы для работы с cookie и сеансами, относящиеся к запросам, входят в класс HttpServletRequest, как показано в табл. А.9. Такие методы, как getRequestedSessionld, ссылаются на идентификатор id сеанса, который был присоединен к запросу либо как фрагмент cookie, либо путем перезаписи URL. Все эти методы возвращают null, если сеанс не был присоединен к запросу.

Таблица А.9. Методы интерфейса HttpServletRequest, связанные с сеансами и cookie

Тип возвращаемого значения | Метод | Описание
Cookie[ ] | getCookies( ) | Возвращает массив, содержащий все объекты cookie, которые клиент прислал с данным запросом
String | getRequestSessionld( ) | Возвращает все идентификаторы id сеанса, указанные клиентом
HttpSession | getSession( ) | Возвращает текущий сеанс, связанный с данным запросом, а если запрос не имеет сеанса, то создает его
HttpSession | getSession(boolean create) | Возвращает текущий объект HttpSession, связанный с данным запросом, а если запрос не имеет сеанса и булев параметр равен true, то создает его
boolean | isRequestedSession IdFromCookie( ) | Возвращает true, если идентификатор (id) требуемого сеанса получен в виде фрагмента cookie (а не путем перезаписи URL)
bool ean | isRequestedSession IdFromURL( ) | Возвращает true, если идентификатор (id) требуемого сеанса получен путем перезаписи URL
boolean | isRequestedSessi onldVal id( ) | Проверяет, является ли запрошенный идентификатор сеанса действительным

Другие методы интерфейса ServletRequest

В табл. А.10 мы попытались сгруппировать методы в соответствии с их функциями, а не в алфавитном порядке. Если вы предпочитаете алфавитный порядок, используйте стандартную документацию JDK, прилагаемую к Tomcat или к JSDK.

Таблица А.10. Еще несколько методов интерфейса ServletRequest

Тип возвращаемого значения | Метод | Описание
void | setAttribute (Stringname.Objectobj) | Используется для сохранения объекта в объекте ServletRequest, который будет передан другому сервлету для обработки
Object | getAttribute(Stringname) | Извлекает указанный объект или возвращает null, если такой объект не существует
Enumeration | gettttributeNames( ) | Возвращает объект Enumeration, который содержит перечень имен всех имеющихся атрибутов
void | removeAttribute (Stringname) | Удаляет атрибут с указанным именем
ServletlnputStream | getlnputStream( ) | Объект для чтения потока двоичных данных из запроса с использованием методов InputSrteam
BufferReader | getReader( ) | BufferReader используется для чтения и синтаксического анализа тела запроса строка за строкой

Класс HttpSession и классы, связанные с cookie

В этом разделе мы обсудим класс HttpSession и классы, связанные с cookie. Объект HttpSession используется для хранения информации о состоянии в промежутках между соединениями с конкретным пользователем.

  • HttpSession. Объекты, реализующие этот интерфейс, позволяют программисту хранить информацию о пользователе в промежутках между отдельными посещениями страницы или соединениями. Процессоры сервлетов обеспечивают методы для мониторинга объектов класса HttpSession, используя уникальные идентификаторы (см. примеры в главе 3).

  • HttpSessionBindingListener. Этот интерфейс аналогичен многим интерфейсам слушателей событий в GUI Java. Программисты могут реализовать этот интерфейс в объекте, которому требуется извещение, когда он присоединяется к объекту HttpSession или отсоединяется от него.

  • Cookie. Эти объекты используются для манипулирования информацией, содержащейся во фрагментах cookie, которые посылаются сервером браузеру и возвращаются при последующих запросах. Информация cookie, содержащаяся в запросе, преобразуется в объекты Cookie методами класса HttpServletRequst.

  • HttpUti I s. Статические методы этого класса используются в различных ситуациях.

  • HttpSessionBindingEvent. Объекты этого типа используются для передачи информации объектам класса HttpSessionBindingListener, когда они присоединяются к объекту HttpSession или отсоединяются от него.

Методы класса HttpSession

Обратите внимание на аналогию между методами, относящимися к атрибутам (перечисленным в табл. А. 11), и методами, которые используются в интерфейсе SevletRequest. В более ранних интерфейсах API в названиях методов использовалось значение (value) вместо атрибута (attribute), например getValue, но в целях более последовательного именования от этих методов решено было отказаться и теперь они являются устаревшими.

Таблица А. 11. Методы класса HttpSession

Тип возвращаемого значения | Метод | Описание
Object | getAttribute(String name) | Возвращает объект, присоединенный к сеансу, с именем name или null , если не найдено объекта с таким именем
void | setAttribute(Stnng name, Object obj) | Добавляет объект obj с именем name к сеансу. Если с этим именем ранее был связан другой объект, то эта связь теряется
Enumeration | getAttributeNames( ) | Возвращает перечисление объектов типа String, содержащее имена всех объектов, присоединенных к сеансу
void | removeAttribute (String name) | Удаляет объект с указанным именем из сеанса
long | getCreationTime( ) | Возвращает системное время (GMT) создания объекта, такое же как в System.currentTimeMillis()
long | getLastAccessedTime() | Возвращает системное время последнего обращения клиента к сеансу. Формат такой же, как в getCreationTime
int | getMaxInactivelnterval ( ) | Возвращает максимальный интервал времени (в секундах), в течение которого контейнер сервлета поддерживает сеанс открытым между обращениями к нему клиента
void | setMaxInactivelntarval (int interval) | Устанавливает интервал времени (в секундах) между обращениями клиента, по истечении которого контейнер сервлетов сделает данный сеанс недействительным
void | invalidate( ) | Делает данный сеанс недействительным и прекращает все связи с объектами
boolean | isNew() | Возвращает true, если клиент еще не знает о сеансе или клиент предпочел не присоединяться к сеансу. Обычно этот метод вызывается сразу после вызова метода getSession объекта HttpServlrtRequest
String | getld( ) | Возвращает уникальный идентификатор, присвоенный данному сеансу

Методы интерфейса HttpSession Listener

Если объект сохраняется в сеансе и хочет «знать», когда он был присоединен к сеансу и когда он от него будет отсоединен, этот объект должен реализовать интерфейс HttpSessionListener, то есть объект должен реализовать два показанных ниже метода.

  • valueBouncKHttpSessionBindingEvent event). Объект, который связывается с сеансом. Параметр event содержит идентификатор сеанса и имя, с которым связан объект.

  • valueUnbound(HttpSessionBindingEvent event). Объект, который отсоединяется от сеанса, возможно, по причине того, что срок сеанса истек.

Методы класса Cookie

Конструктор Cookie использует имя типа String и значение того же типа. Заметим, что в имени не могут содержаться произвольные символы, а только те, которые соответствуют требованиям спецификации RFC 2109 (Requests for Comments1). Имена не чувствительны к регистру. В табл. А.12 перечислены некоторые полезные методы класса Cookie.

Таблица А.12. Методы класса Cookie

Тип возвращаемого значения | Метод | Описание
Object | clone( ) | Переопределяет стандартный метод Object. clone и возвращает копию этого файла cookie. Обратите внимание, что вы По-прежнему должны привести ссылку к типу Cookie
String | getValue( ) | Возвращает значение cookie
void | setValue(String newValue) | Присваивает файлу cookie новое значение после создания cookie
String | getComnent() | Комментарий, описывающий назначение cookie, или null, если комментарий не был присоединен
void | setComment( String purpose) | Присоединяет комментарий, который описывает назначение cookie
String | getDomain( ) | Возвращает имя домена, установленного для этого файла cookie
void | setDomain(String pattern) | Устанавливает домен, внутри которого должен быть представлен данный файл cookie
void i | setPath(String uri) | Устанавливает путь, по которому клиент должен будет возвращать cookie
int | getMaxAge( ) | Возвращает время (в секундах), оставшееся до конца срока действия cookie. Значение, равное -1, которое устанавливается по умолчанию, означает, что cookie будет существовать, пока не отключится браузер, то есть только во время текущего сеанса
void | setMaxAgednt expiry) | Устанавливает максимально допустимый срок действия cookie. Если это значение равно нулю, cookie немедленно уничтожается
String | getName( ) | Имя cookie
String | getPath( ) | Путь на сервере, по которому браузер возвращает cookie
bool ean | getSecure( ) | Возвращает true, если браузер посылает cookie только по безопасным протоколам; возвращает f al se, если браузер может посылать cookie, используя любой протокол
void | setSecure(boolean flag) | Указывает браузеру, должен ли файл cookie посылаться только по безопасному протоколу, например HTTPS или SSL
int | getVersion( ) | Возвращает версию протокола, с которой согласуется cookie
void | setVersiondnt v | Устанавливает версию протокола для cookie, с которой согласован данный файл cookie

Специальные объекты, связанные с запросом

В интерфейсе RequestDispatcher определены два метода, которые позволяют одному сервлету или одной JSP-странице переадресовать или включить выходные данные другого сервлета или файл, находящийся на сервере. Вы получаете объект RequestDispatcher из объекта ServletRequest с конструктором, который указывает, какой ресурс должен быть присоединен. Этот объект создается процессором сер- влетов, как показано в следующем примере:

RequestDispatcher rd = req.getRequestDispatcher(String path)

Используя этот объект, вы можете вызвать любой из следующих методов:

forward(ServletRequest req,  ServletResponse resp)
include(ServletRequest req,  ServletResponse resp)

Формирование ответа пользователю

В этом разделе мы рассмотрим классы и методы, связанные с отправкой данных в ответ на запрос пользователя.

  • ServletResponse. Объект, реализующий этот интерфейс, должен быть создан процессором сервлетов и передан методу service сервлета для передачи клиенту данных MIME.

  • HttpServletResponse. Расширение интерфейса ServletResponse, в котором добавляются некоторое методы, специфические для протокола HTTP.

  • Servl etOutputStream. Класс для создания потока двоичных данных как части ответа.

Методы интерфейса ServletResponse

Процессор сервлетов создает объект, реализующий интерфейс ServletResponse, прежде чем будет вызван ваш сервлет или JSP-страница. Методы этого класса перечислены в табл. А. 13.

Таблица А.13. Методы в интерфейсе ServletResponse

Тип возвращаемого значения | Метод | Описание
void | setBufferSize(int size) | Устанавливает предпочтительный размер буфера для тела ответа
void | flushBuffer() | Содержимое буфера отсылается клиенту
void | reset () | Удаляет все данные из буфера, а также код состояния и заголовки
int | getBufferSize() | Фактический размер буфера, используемый для ответа
boolean | isComitted() | Возвращает true, если ответ был послан
String | getCharacterEncoding( ) | Возвращает название набора символов, используемого в MIME-теле ответа
Locale | getLocale() | Возвращает объект Locale (идентификатор региона), присоединенный к данному ответу
Servl etOutputStream | getOutputSrteam( ) | Возвращает выходной поток, который используется для записи двоичных данных в ответе. Обратите внимание, что вы не можете поменять тип выходного потока после того, как он выбран. Это расширение java.io.OutputStream, и его можно использовать для создания специализированных выходных потоков, таких как ObjectOutputStream или ZipOutputStream
PrintWriter | getWriter() | Возвращает выходной поток, предназначенный для записи символьного текста. Поскольку он осуществляет преобразование символов в соответствии с типом содержимого и идентификатором территории, вы должны задать эти параметры до того, как будут записаны какие-либо данные. Чтобы данные были посланы, вы можете использовать метод flush(). Когда сервлет заканчивает запись выходных данных, должен быть вызван метод close(), в противном случае клиент может никогда не получить ответ
void | setContentLength(int len) | Устанавливает размер содержимого тела ответа. В НТТР-сервлетах этот метод устанавливает заголовок ответа протокола HTTP Content-Length. Он должен быть вызван до того, как фактически будет послано какое-либо содержимое. Размер содержимого, как правило, требуется указывать не для всех типов данных, а только для двоичных
void | setContentType(Srting type) | Устанавливает тип содержимого ответа. Тип ответа должен быть одним из MIME- типов и может включать дополнительную информацию о кодировке. Например, для документа HTML, в котором используется набор символов Latin-4 для западноевропейских языков, будет указан следующий тип содержимого text/html : chatset=ISO-8859-4. Если набор символов не указан, по умолчанию будет использоваться ISO-8859-1 (ACSII)
void | setLocale ( Java.util.Locale loc) | Задает идентификатор региона и соответствующим образом устанавливает заголовки (в том числе задает набор символов для Content-Type)

Методы, добавляемые интерфейсом HttpServletResponse

Интерфейс HttpServletResponse расширяет ServletResponse и добавляет некоторые полезные методы, специфичные для протокола HTTP, которые перечислены в табл. А.14. Также в нем определен набор констант, соответствующих кодам состояния ответа, как, например, печально известное сообщение «404 — page not found». Заметим, что методы заголовков, которые начинаются с префикса add, могут добавлять значения в существующий заголовок, формируя список, разделенный запятыми, в то время как методы типа set замещают любой существующий заголовок с тем же именем.

Таблица А.14. Методы интерфейса HttpServletResponse

Тип возвращаемого значения | Метод | Описание
void | addCookie (Cookie cookie) | Добавляет cookie в ответ. Этот метод можно вызывать многократно, но заметим, что браузер, возможно, будет способен принять только 20 файлов cookie из данного источника или вообще не принимать cookie
void | addDateHeader (String name, lonf date) | Добавляет заголовок ответа с указанным именем и значением даты. Значение даты указывается в формате System. currentTimeMillisO, но значение заголовка ответа будет строкой с датой в формате GMT, например Тие, 15 Nov 1994 08:12:31 GMT
void | setDateHeader (String name, lonf date) | Устанавливает заголовок ответа с указанным именем и значением даты. Любой установленный ранее заголовок с тем же именем будет потерян, поэтому здесь нужно проявить бдительность
void | addHeader (Srting name. String value) | Добавляет заголовок запроса с указанным именем и значением. Если заголовок уже существует, то после его значения через запятую добавляется новое значение
void | setHeader (Srting name. String value) | Устанавливает заголовок ответа с указанным именем и значением. Если заголовок с этим именем уже существует, то он заменяется новым
void | addlntHeader (Srting name, String value) | Добавляет заголовок ответа с указанным именем и значением. Естественно, когда значение присоединяется к заголовку, оно преобразуется к типу String
void | setlntHeader (Srting name. String value) | Устанавливает заголовок ответа с указанным именем и значением
bool ean | containsHeader(String name) | Возвращает true, если заголовок с указанным именем уже был установлен
void | sendRedi rect (String location) | Посылает ответ клиенту, используя указанный URL-адрес нового временного местоположения документа
String | encodeRedi rectURL (String url) | Преобразует указанный URL-адрес в вид, пригодный для использования в методе sendRedi rect, или, если преобразования не требуется, возвращает тот же URL-адрес
String | encodeURUSrting url) | Перекодирует указанный URL-адрес, включая в него идентификатор сеанса, или, если перекодировка не требуется, возвращает URL без изменения
void | sendError(int sc) | Посылает клиенту код ошибки HTTP
void | sendError (int sc. Srting msg) | Посылает код ошибки с описанием
void | setStatus(int sc) | Устанавливает код состояния для ответа

Выходные данные JSP-страниц

Экземпляр класса JspWriter, называемый out, автоматически создается в методе _jspService класса PageContext. Объект JspWriter аналогично объекту Java.iо.PrintWriter записывает поток символов в специальной кодировке. Но между ними имеется значительная разница: методы класса JspWriter для вывода данных могут генерировать исключение lOException. В классе PrintWriter исключение IOЕхсерtion обрабатывается внутри, и программист должен вызывать метод checkError(), чтобы узнать, было ли сгенерировано исключение.

Способность генерировать исключение lOException является существенной для управления буферизацией, которую осуществляет JspWriter.

Ошибки и исключения

В API сервлетов предусмотрены специальные классы исключений — ServletException и UnavaliableException, описанные ниже.

  • ServletException. Исключение общего назначения, которое используется в API сервлетов. Конструкторы, представленные в табл. А.15, предлагают различные способы включения других ошибок (Error) и исключений (Exception) с объяснениями или без них. Для того чтобы извлечь из класса ServletException включенные в него объекты Error или Exception, используйте метод getRootCause().

  • UnavaliableException. Это исключение генерируется, если сервлету нужно сообщить, что он временно или постоянно недоступен. Причиной этого может быть недостаток поддержки со стороны системы — невозможность соединения с базой данных, недостаток памяти или места на диске. В табл. А. 16 объясняется, каким образом используется это исключение и почему оно полезно.

Таблица А.15. Конструкторы класса ServletException

Конструктор | Описание
Servl etExcepti on( ) | Конструирует новое исключение сервлета
ServletException (Java.Tang.String message) | Конструирует новое исключение сервлета с заданным сообщением
ServletException (java.lang.Throwable rootCause) | Конструирует новое исключение сервлета в результате возникновения исключительной ситуации в системе, и включает системное сообщение (rootCause), объясняющее причину этой исключительной ситуации, нарушившей нормальный ход выполнения сервлета
ServletException ( Java.lang.String message. java.lang.Throwable rootCause) | Конструирует новое исключение сервлета в результате возникновения исключительной ситуации в системе и включает системное сообщение (rootCause), объясняющее причину этой исключительной ситуации, нарушившей нормальный ход выполнения сервлета, а также строку с описанием нового исключения сервлета

Таблица А. 16. Конструкторы класса UnavaliableException

Конструктор | Описание
Unavaliabl eException (Java.lang.String message) | Конструирует новое исключение сервлета с сообщением, что данный сервлет постоянно недоступен
Unavaliabl eException (Java.lang.String message, int seconds) | Конструирует новое исключение сервлета с сообщением, что данный сервлет временно недоступен, и с оценкой времени, в течение которого он будет недоступен

Ошибки и исключения JSP-страниц

Как говорилось в главе 5, в API для JSP-страниц предусмотрено специальное обозначение страницы, которая обрабатывает все исключения. Эта страница задается с помощью атрибута еггогРаде директивы page, подобно тому как показано в следующем примере:

<%@ page language=
"Java" ErrorPage = "JSPbook/Chapt02/whoops.jsp" %>

Любая JSP-страница, назначенная обрабатывать ошибки, должна включать тег, аналогичный приведенному ниже:

<%@ page language= "Java"  isErrorPage = "true" %>

Этот тег устанавливает параметр isErrprPage, гарантирующий, что в данной странице имеется заданная по умолчанию переменная с именем exception, через которую страница получает доступ к фактически сгенерированному исключению или ошибке. Эта переменная ссылается на один из двух связанных с ошибками классов — JspException или JspError. В этих классах нет никаких специальных методов, кроме определенных в родительском классе, Java.lang.Exception. В табл. А. 17 перечислены конструкторы классов JspException и JspError.

Таблица А.17. Конструкторы классов JspException и JspError

Конструктор | Определение
JspException() | Конструирует объект JspException
JspException(String msg) | Исключение с сообщением
JspError() | Обратите внимание, что класс JspError происходит от класса JspException
JspError(String msg) | Добавляет сообщение об ошибке

Коды состояний и ошибок HTTP

В интерфейс HttpServl etResponse входят константы для обозначения всех кодов состояний и ошибок HTTP. Они перечислены в табл. А. 18. По существу это та же информация, которая содержится в спецификации RFC 2616, где описан стандарт HTTP 1.1. Вообще коды можно разделить на следующие категории:

  • серия 100-199 — информационные коды, процесс продолжается;

  • серия 200-299 — коды указывают, что процесс был успешным;

  • серия 300-399 — переадресация, для выполнения запроса требуются дальнейшие действия;

  • серия 400-499 — ошибка клиента, в запросе содержатся синтаксические ошибки или запрос не может быть выполнен;

  • серия 500-599 — ошибка сервера, сервер отказывается выполнять заведомо корректный запрос.

Сами коды ошибок перечислены в табл. А. 19.

Таблица А.18. Константы HTTP для кодов состояний

Имя константы | Величина | Описание
SC_CONTINUE | 100 | Клиент может продолжать работу
SC_SWITCHING_PROTOCOLS | 101 | Сервер меняет протоколы в соответствии с содержимым заголовка Upgrade HTTP-запроса
SCJJK | 200 | Запрос обработан успешно
SC_CREATED | 201 | Запрос обработан успешно, на сервере в ответ на него создан новый документ
SC_ACCEPTED | 202 | Запрос был принят, но его обработка еще не закончена
SC NON AUTHORITATIVE INFORMATION | 203 | Некоторые заголовки ответов могут быть неверными, так как используется копия документа
SC_NO_CONTENT | 204 | Запрос обработан успешно,, но отсутствует новая информация, которую можно было бы вернуть с ответом
SC_RESET_CONTENT | 205 | Браузер должен обновить документ, который вызвал отправку запроса на сервер
SC_PARTIAL_CONTENT | 206 | Сервер выполнил частичный запрос для данного ресурса методом GET
SC_MULTIPLE_CHOICES | 300 | Запрошенный ресурс представлен в нескольких документах, расположенных в разных местах
SC_MOVED_PERMANENTLY | 301 | Ресурс перемещен в новое место, где он будет находиться постоянно. Все дальнейшие ссылки на этот ресурс должны использовать новый URI-адрес
SCJWEDJEMPORARI LY | 302 | Ресурс перемещен в новое место временно. Все дальнейшие ссылки для доступа к этому ресурсу должны использовать прежний URI-адрес
SC_SEE_OTHER | 303 | Ответ на данный запрос может быть найден с использованием другого URI-адреса
SC_NOT_MODIFIED | 304 | В результате выполнения условного запроса методом GET оказалось, что данный ресурс доступен, но не был изменен
SC_USE_PROXY | 305 | Запрашиваемый ресурс должен возвращаться через прокси-сервер, заданный в поле Location

Таблица А.19. Константы для кодов ошибок HTTP

Имя константы | Величина | Описание
SC_BAD_REQUEST | 400 | Запрос, посланный клиентом, синтаксически неверен
SC_UNAUTHORIZED | 401 | В поле Authorization запроса отсутствует необходимая для аутентификации информация
SC_PAYMENT_REQUIRED | 402 | Зарезервировано для будущего использования
SC_FORBIDDEN | 403 | Сервер понимает запрос, но отказывается его выполнить
SC_NOT_FOUND | 404 | Запрошенный ресурс невозможно найти по указанному адресу
SC_METHOD_NOT_ALLOWED | 405 | Заданный метод запроса не разрешен для ресурса, идентифицированного с помощью указанного URJ- адреса
SC_NOT_ACCEPTABLE | 406 | Запрашиваемый ресурс может генерировать ответы только таких MIME-типов, которые несовместимы с типами, заданными клиентом в поле Accept запроса
SC PROXY AUTHENIFICATION REQUIRED | 407 | Клиент должен пройти аутентификацию на прокси-сервере
SC_REQUEST_TIMEOUT | 408 | Клиент не послал запрос в тот промежуток времени, когда сервер был готов принять его
SC_CONFLICT | 409 | Запрос не может быть выполнен из-за конфликта с текущим состоянием ресурса
SCJ30NE | 410 | Ресурс недоступен более на этом сервере, и не указан адрес для перенаправления запроса
SC_LENGTH_REQUIRED | 411 | Запрос не может быть выполнен в отсутствие определенного клиентом заголовка Content -Length
SC_PRECONDITION_FAILED | 412 | Входное условие, заданное в одном или нескольких полях заголовка запроса, при тестировании на сервере оказалось ложным
SC_REQUEST_ENTITYJOO_LARGE | 413 | Сервер отказывается обрабатывать запрос, так как размер запрашиваемого документа больше, чем сервер готов или способен обрабатывать
SC_REQUESTJJRI_TOO_LONG | 414 | Сервер отказывается выполнять запрос, так как приведенный в нем URI-адрес слишком длинный для того, чтобы сервер мог его обработать
SC_UNSUPPORTED_MEDIA_TYPE | 415 | Сервер отказывается выполнять запрос, так как в запросе содержится документ в таком формате, который не поддерживается запрошенным ресурсом для метода, указанного в запросе
SC REQUESTED RANGE NOT SATISFIABLE | 416 | В запросе содержится невыполнимый для сервера заголовок Range
SC_EXPECTATION_FAILED | 417 | Сервер не может выполнить то задание, которое указано в заголовке Expect запроса клиента
SC_INTERNAL_SERVER_ERROR | 500 | Запрос не может быть выполнен по причине ошибки внутри HTTP-сервера
SC_NOT_IMPLEMENTED | 501 | HTTP-сервер не поддерживает функции, необходимые для выполнения запроса
SC_BAD_GATEWAY | 502 | HTTP-сервер получил неверный ответ от удаленного сервера, по отношению к которому он выполнял роль прокси-сервера или шлюза
SC_SERVICE_INAVAILABLE | 503 | HTTP-сервер временно перегружен и не может ответить на запрос
SC_GATEWAY_TIMEOUT | 504 | Сервер, который используется в качестве прокси- сервера или шлюза, не получил своевременный ответ от удаленного сервера
SC HTTP VERSION NONSUPPORTED | 505 | Сервер не поддерживает или отказывается поддерживать версию протокола HTTP, используемую в запросе

ПРИМЕЧАНИЕ

Для диагностики кодов ошибок в Microsoft Internet Explorer имеется так называемый дружественный режим, который не позволяет пользователю увидеть фактические сообщения об ошибках в сервлетах или JSP-страницах. Для отключения этого режима выберите команду Tools > Internet Options (Сервис > Свойства обозревателя), в открывшемся окне перейдите на вкладку Advanced (Дополнительно) и снимите флажок Show Friendly Http Error Messages (Показывать уведомление о каждой ошибке сценария).

Интерфейс API для JSP-страниц

В табл. А.20 приведены переменные, которые, за исключением переменных session и exception, всегда создаются в JSP-странице. Переменная session недоступна, если в директиве страницы присутствует выражение session="false". Переменная exception доступна только в том случае, если атрибут isErrorPage имеет значение true, как показано в следующем примере:

<%@page language = "Java" isErrorPage = "true"

Таблица А.20. Неявные переменные в JSP-страницах

Имя переменной | Тип | Описание
request | Объект класса, являющегося подклассом класса javax.servlet.Servl etRequest | Представляет запрос пользователя
response | Объект класса, являющегося подклассом класса javax.servlet.Servl etResponse | Создает ответ на запрос
pageContext | Объект класса javax.servlet.jsp.PageContext | Содержит атрибуты страницы
session | Объект класса javax.servlet.http.HttpSession | Содержит произвольные переменные, связанные с данным сеансом
application | Объект класса javax.servlet.ServletContext | Содержит атрибуты для всего приложения и влияет на интерпретацию некоторых других тегов
out | Объект класса javax.servlet.jsp.JspWriter | Выходной поток для данного ответа
config | Объект класса javax.servlet.ServletConfig | Содержит пары имя-значение для параметров инициализации сервлета и объект Servl etContext
page | Ссылка на объект, синоним указателя this | Возвращает ссылку на сервлет
exception | Объект класса javax.lang.Throwable или одного из его подклассов | Только те страницы, которые обозначены в директиве страницы как страницы ошибок

Интерфейсы JspPage и HttpJspPage очень просты, как видно из табл. А.21. Поскольку за создание метода _jspService отвечает процессор JSP, вам нужно определить только методы jsplnit и jspDestroy.

Таблица А.21. Методы интерфейсов JspPage и HttpJspPage

Метод | Интерфейс | Описание
void jspDestroy() | JspPage | Этот метод вызывается, когда JSP-страница должна быть уничтожена. Метод используется для удаления всех ресурсов
void jspInit() | JspPage | Этот метод вызывается, когда JSP-страница инициализируется. Метод используется для установления значений параметров
void _jspService (HttpServletRequest request, HttpServletResponse response) | HttpJspPage | Этот метод создается процессором JSP для написания тела JSP-страницы

Класс PageContext

Класс PageContext — это абстрактный класс. Для конкретного сервера класс, расширяющий PageContext, создается процессором JSP. Объект PageContext управляет всеми генерируемыми процессором JSP ресурсами, которые требуются серв- лету. Важно отметить, что при управлении ресурсами используются различные области видимости.

Доступ к стандартным переменным

В классе PageContext предусмотрены методы для получения ссылок на стандартные (неявные) переменные JSP, которые перечислены в табл. А.20. Поскольку эти переменные автоматически определены в вашей JSP-странице (в методе _jspService()), методы, перечисленные в табл. А.22, используются в основном в других классах.

Таблица А.22. Методы класса PageContext, предназначенные для получения неявных переменных

Неявная переменная | Тип | Метод PageContext
exception out page request response config application session | Exception JspWriter Object ServletRequest ServletResponse ServletConfig ServletContext HttpSession | getException() getOut() getPage() getRequest() getResponse() getServletConfig() getServletContext() getSession()

В табл. А.23 перечислены методы класса PageContext, которые используются для извлечения различных объектов. Обратите внимание на то, что переменные области видимости являются ссылками на константы класса PageContext (см. табл. А.25).

Таблица А.23. Методы класса PageContext, связанные с хранением и извлечением атрибутов

Тип возвращаемого значения | Метод | Описание
Object | getAttribute(String name) | Возвращает объект, связанный с указанным именем, областью видимости которого является страница. Если такой объект на странице не обнаружено, возвращается null
void | removeAttribute (String name) | Объект с указанным именем, относящийся к любой области видимости, удаляется
void | setAttribute (String name, Object attribute) | Возвращает объект, который становится атрибутом страницы с именем name и значением attribute
Object | getAttribute (String name, int scope) | Возвращает объект, связанный с указанным именем, в заданной области видимости. Если в этой области объект с таким именем не обнаружен, возвращается null
void | removeAttri bute (String name, int scope) | Удаляет объект, связанный с данным именем
void | setAttribute(String name. Object obj. int scope) | Возвращает объект, который становится атрибутом с именем name и значением obj. Область видимости задается параметром scope
Enumeration | getAttri buteNames I nScope (int scope) | Возвращает перечисление объектов типа String, которые являются атрибутами с указанной областью видимости
int | getAttri buteScope (String name) | Возвращает область видимости атрибута с указанным именем
Object | findAttribute(String name) | Ищет атрибут с заданным именем последовательно в странице, запросе, сеансе (если он существует) и приложении. Возвращает значение атрибута или mil 1 , если такой атрибут не обнаружен

В табл. А.24 перечислены методы класса PageContext, которые не вошли ни в одну из приведенных выше категорий.

Таблица А.24. Остальные методы класса PageContext

Тип возвращаемого значения | Метод | Описание
void | forward (String relativeUrlPath) | Переадресует текущие объекты классов ServletRequest и Servl etResponse другому активному компоненту приложения
void | hand! ePageExcepti on (Exception e) | Этот метод вызывается из раздела try- catch, который включает в себя код вашей JSP-страницы. Он переадресует исключение специальной странице ошибок для данной JSP-страницы или, если она отсутствует, выполняет определенное на этот случай действие
void | include (String relativeUrlPath) | Указанный ресурс обрабатывается как часть текущих объектов классов ServletRequest и ServletResponse
void | initialize(Servlet serlet, ServletRequest request, ServletResponse response. String errorPageURL, boolean needsSession, int bufferSize, boolean authFlush) | Метод вызывается для инициализации объекта PageContext, чтобы его можно было использовать в классе реализации JSP для обслуживания входящих запросов и ответов в методе jspService. Вызов метода осуществляется процессором JSP
JspWriter | popBody() | Возвращает предыдущий объект out класса JspWriter, который был сохранен в соответствующем методе pushBody(), а затем обновляет значение атрибута out в пространстве имен атрибутов PageContext с областью видимости, равной странице
BodyContent | pushBody() | Возвращает новый объект BodyContent, сохраняет текущий объект out класса JspWriter и обновляет значение атрибута out в пространстве имен атрибутов PageContext с областью видимости, равной странице
void | release() | Восстанавливает исходное состояние объекта PageContext, чтобы он мог быть повторно использован. Вызов этого метода осуществляется процессором JSP

В классе PageContext определено некоторое количество констант, в основном относящихся к определению области видимости. Эти константы приведены в табл. А.25.

Таблица А.25. Константы, определенные в классе PageContext

Тип | Имя | Описание
String | APPLICATION | Имя, используемое для хранения объекта класса Servl etContext в таблице имен PageContext
int | APPLICATION_SCOPE | Область видимости — приложение: ссылка в классе Servl etContext остается доступной, пока приложение не будет запущено заново
String | CONFIG | Имя, используемое для хранения объекта класса Servl etContext в таблице имен PageContext
String | EXCEPTION | Имя, используемое для хранения не перехваченного исключения
Stri ng | OUT | Имя, используемое для хранения текущего объекта класса JspWriter в таблице имен PageContext
String | PAGE | Имя, используемое для хранения объекта класса Servlet в таблице имен PageContext
int | PAGE_SCOPE | Область видимости — страница (задается по умолчанию): ссылка остается доступной в данном классе PageContext до окончания выполнения метода serviceO сервлета
String | PAGECONTEXT | Имя, используемое для хранения объекта класса PageContext в его собственной таблице имен
String | REQUEST | Имя, используемое для хранения объекта класса ServletRequest в таблице имен PageContext
int | REQUEST_SCOPE | Область видимости — запрос: ссылка остается доступной в объекте ServletRequest, связанном сданным сервлетом, пока не будет выполнен текущий запрос
String | RESPONSE | Имя, используемое для хранения объекта класса ServletResponse в таблице имен PageContext
String | SESSION | Имя, используемое для хранения объекта класса HttpSession в таблице имен PageContext
int | SESSIONJCOPE | Область видимости — сеанс (только в том случае, если страница участвует в сеансе): ссылка остается доступной в объекте HttpSession (если он существует), связанном с сервлетом, пока объект HttpSession не станет недействительным

Класс JspWriter

Класс JspWriter является расширением абстрактного класса Java.io.Writer. Назначение этого класса — обеспечивать функциональность вывода данных, аналогичную функциональности классов BufferWriter и PrintWriter в JSP, как видно из табл. А.26. Важное различие между этими классами заключается в том, что методы JspWriter могут генерировать исключение lOException, в то время как методы PrintWriter не могут этого делать.

Таблица А.26. Перечень методов управления буферизацией класса JspWriter

Тип возвращаемого значения | Метод | Описание
void | clear() | Очищает содержимое буфера. Если какое-либо содержимое было сброшено в выходной поток, генерирует исключение lOException, чтобы предупредить приложение о том, что некоторые данные уже посланы
void | clearBuffer() | Очищает содержимое буфера, но не генерирует исключение, если какие-либо данные уже были отосланы
void | close() | Закрывает поток после того, как сброшено содержимое
void | flush() | Сбрасывает содержимое потока
int | getBufferSize() | Текущий размер буфера
int | getRemaining() | Неиспользованное пространство в буфере
boolean | isAutoFlush() | Состояние флага Autoflush

Методы для вывода данных в классе JspWriter, приведенные в табл. А.27, аналогичны методам класса java.io.PrintWriter, но они могут генерировать исключение lOException, если возникает какая-либо проблема с выходным потоком.

Таблица А.27. Методы для вывода данных класса JspWriter

Тип возвращаемого значения | Метод | Описание
void | newLine() | Записывает разделитель строк
void | print(boolean b) | Выводит булево значение
void | print(char c) | Выводит символ
void | print(char[] s) | Выводит массив символов
void | print(double d) | Выводит число двойной точности с плавающей точкой
void | print( float f) | Выводит число с плавающей точкой
void | print(int i) | Выводит целое число
void | print (long 1) | Выводит длинное целое число
void | print(java.lang.Object obj) | Выводит значение типа Object
void | print(java.lang.String s) | Выводит значение типа String
void | println() | Завершает текущую строку разделителем строк
void | print! n(boolean x) | Выводит булево значение, после чего завершает строку
void | println(char x) | Выводит символ, после чего завершает строку
void | println(char[] x) | Выводит массив символов, после чего завершает строку
void | println( double x) | Выводит число двойной точности с плавающей точкой, после чего завершает строку
void | println(float x) | Выводит число с плавающей точкой, после чего завершает строку
void | printlndnt x) | Выводит целое число, после чего завершает строку
void | printlndong x) | Выводит длинное целое число, после чего завершает строку
void | println(java.lang.Object x) | Выводит значение типа Object, после чего завершает строку
void | println(Java.lang.String x) | Выводит значение типа String, после чего завершает строку

Пакет javax.servlrt.jsp.tagext

При создании пользовательских тегов JSP вам понадобятся только некоторые классы из этого пакета. Пользовательский тег должен расширять либо интерфейс Tag, либо интерфейс BodyTag. Методы интерфейса Tag перечислены в табл. А.28.

Таблица А.28. Методы интерфейса Tag

Тип возвращаемого значения | Метод | Описание
int | doEndTag() | Определяет код, который вызывается, если обнаруживается закрывающий тег
int | doStartTag() | Определяет код, который вызывается, если обнаруживается открывающий тег для данного экземпляра
Tag | getParent() | Для вложенных тегов возвращает родителя данного обработчика тега
void | release() | Вызывается обработчиком тега для возвращения к исходному состоянию
void | setPageContext (PageContext pc) | Устанавливает контекст текущей страницы
void | setParentdag () | Устанавливает текущий родительский (ближайший из внешних по отношению к данному) объект Tag для данного объекта Tag

Интерфейс Tag предназначен для работы с пользовательскими тегами, для которых не требуется обработка тела тега. Для того чтобы иметь возможность обрабатывать тело тега, вам следует добавить методы интерфейса BodyTag, приведенные в табл. А.29.

Таблица А.29. Интерфейс BodyTag добавляет следующие методы

Тип возвращаемого значения | Метод | Описание
int | doAfterBody() | Выполняет действия после обработки тела тега
void | doIntBody() | Подготовка к обработке тела тега
void | setBodyContent( BodyContent b) | Устанавливает свойство bodyContent

Класс BodyContent

Этот класс является подклассом класса JspWriter и наследует его методы, приведенные в табл. А.26 и А.27. Также этот класс добавляет новые методы, представленные в табл. А.ЗО, для буферизации символьной информации и манипулирования ею. Обратите внимание, что метод flush должен быть переопределен, так как объект BodyContent присоединяется не к выходному потоку, а только к внутреннему буферу.

Таблица А.З0. Методы класса BodyContent

Тип возвращаемого значения | Метод | Описание
void | clearBody() | Очищает буфер тела тега, обычно в процессе подготовки к повторному считыванию тела тега в буфер
void | flush() | Переопределяет метод flush() класса JspWriter, поскольку объект BodyContent не может быть сброшен. Генерирует исключение lOException
JspWriter | getEnclosingWriter ( ) | Получает ближайший внешний (enclosing) объект JspWriter
Reader | getReader() | Возвращает объект класса Reader, который может получить доступ к объекту BodyContent
Srting | getString() | Возвращает значение BodyContent в виде строки типа String
void | writeOut(java.io.Writer out) | Записывает содержимое объекта BodyContent в выходной поток Writer


  • Приложение Б. Словарь терминов

      • A B C D E F G H I J L M N O P R S T U V W X

Приложение B

Словарь терминов

100 % Pure Java — обозначение для классов и приложений Java, соответствующих критериям Sun о полной независимости от конкретной операционной системы.

A

absolute positioning (абсолютное позиционирование) — размещение и масштабирование объектов класса Component с использованием точечных координат (системой координат является массив пикселов). Доступно лишь в случае, если вместо менеджера компоновки контейнера установлено значение null.

abstract — ключевое слово языка программирования Java, используемое для обозначения классов или методов, которые определяют поведение во время выполнения, но не обеспечивают полной реализации. Невозможно создать объект такого класса, но на объекты какого-либо класса, получившегося как расширение класса abstract, можно ссылаться при помощи имени класса abstract.

abstract pathname (абстрактный путь) — описание пути внутри объекта класса File, независимое от конкретной операционной системы.

Abstract Window Toolkit (AWT, абстрактный оконный инструментарий) — пакет, содержащий интерфейсы, классы, исключения и ошибки Java и образующий графический пользовательский интерфейс (GUI — graphical user interface) Java на основе компонентов GUI базовой платформы. Пакет AWT проще, чем пакет графического пользовательского интерфейса Swing.

accessibility — пакет javax.accessibility определяет интерфейсы специальных приложений Java, предназначенных для пользователей с ограниченными физическими возможностями.

Active Server Pages (ASP, активные серверные страницы) — технология компании Microsoft, предназначенная для встраивания в HTML-страницы кода для создания динамических web-страниц.

adapter (адаптер) — шаблон для преобразования (или адаптирования) интерфейса одного класса к интерфейсу другого класса. Например, в пакете Java.awt.event имеются классы, поддерживающие создание слушателей.

Adjustable — интерфейс Java (Java.awt.adjustable), содержащий методы для управления объектами, в которых имеются изменяемые в определенных пределах числовые значения (например, полоса прокрутки).

algorythm (алгоритм) — процесс решения задачи, при котором в каждый момент времени выполняется только одно определенное действие.

alpha (альфа) — символическое обозначение версии программного продукта, который проходит начальное тестирование внутри фирмы, где он был разработан; этот этап предшествует тестированию за пределами данной фирмы. См. также beta.

alpha value (альфа-значение) — термин компьютерной графики, характеризующий степень прозрачности данного пиксела. Совместно с более привычными значениями RGB (которые определяют относительную интенсивность красного, зеленого и синего цветов) альфа-значение формирует цвет пиксела в Java. Если пиксел представлен в формате int, альфа-значение занимает верхний (первый) байт, то есть первые 8 бит.

American Standard Code for Information Interchange (ASCII, американский стандартный код для обмена информацией) — повсеместно используемый в компьютерной промышленности стандарт для кодирования текстовых и управляющих символов.

anonymous (анонимный) — неименованный локальный класс Java, объявленный и созданный в отдельной инструкции.

API См. Application Programming Interface.

applet (апплет) — программа на языке Java, которая функционирует в рамках виртуальной машины Java (JVM) в браузере пользователя. Браузер в данном случае можно представлять себе как контейнер, который содержит апплеты и позволяет им выполняться на клиентской машине более или менее независимо от операционной системы.

application (приложение) — программа на языке Java, которая выполняется на клиентской машине и имеет доступ ко всем ресурсам клиентской системы. См. также applet.

Application Programming Interface (API, интерфейс прикладных программ) — набор функций, который используется приложением для доступа к операционной системе и библиотечным службам.

Application Service Provider (ASP, программный поставщик услуг) — сервер, предоставляющий помимо обычных web-страниц другие программные услуги через Интернет.

Application Services (прикладные службы) — в классификации Sun — ряд усовершенствований интерфейса, включенных в библиотеку Java Foundation Classes (JFC).

argument (аргумент) — в Java так называется элемент данных, который может обозначать примитив или объект Java.

ArithmeticException — исключительная ситуация, которая возникает, когда в процессе выполнения программы происходит целочисленное деление на ноль.

array (массив) — группа однотипных элементов данных, каждый из которых снабжен уникальным адресом, представленным 32-битным целым числом.

Arrays — класс Java (Java.util .arrays), который включает статические методы для поиска, сортировки и других вспомогательных операций над массивами примитивов и объектных ссылок.

ASCII См. American Standard Code for Information Interchange.

ASP CM. Active Server Pages, CM. Application Service Provider.

assignable (присваиваемый) — об отношении между ссылкой на объект и переменной ссылочного типа, при котором обе принадлежат одному типу или переменная является предком ссылки в иерархии классов.

assignment (присваивание) — операторы Java, которые присваивают значение какой-либо переменной; например, к операторам присваивания относятся символы = и +=.

atomic (атомарный) — шаг программы (операция), который не может быть прерван другим потоком (объектом Thread) или выполнен не полностью, называется атомарным. Все операции присваивания Java, выполняемые над 32-битными переменными, являются атомарными, но операции над 64-битными переменными (принадлежащими к типам long и integer) не относятся к атомарным.

attribute (атрибут) — в тегах XML — пара имя-значение, расположенная в открывающем теге элемента.

automatic (о локальной переменной) — переменная, объявленная внутри метода. Для таких переменных автоматически выделяется место в памяти при вызове метода.

AWT См. Abstract Window Toolkit.

B

bean См. Java bean.

beta (бета) — символическое обозначение версии программного продукта, которая находится в стадии разработки и тестируется за пределами фирмы, в которой этот продукт был разработан. Обычно эта стадия тестирования непосредственно предшествует коммерческому распространению программного продукта. См. также alpha.

bitwise (побитовая) — операция, выполняемая над отдельными битами; операция, которая манипулирует объектами целочисленных примитивных типов Java на основе взаимодействия с отдельными битами.

block (блок) — часть кода Java, которая содержится между парными фигурными скобками { и }.

break — ключевое слово языка Java, имеющее две функции: если оно используется само по себе, то служит для возобновления выполнения кода программы после данного блока инструкций; если указана метка какого-либо блока инструкций, то управление передается инструкции, следующей за указанным блоком. См. также continue.

byte (байт) — целочисленный примитив, занимающий 8 бит. В языке Java играет роль целого со знаком.

Byte — класс-оболочка Java для значений типа byte — примитивов, состоящих из 8 бит.

bytecode (байт-код) — машинно-независимый код, генерируемый виртуальной машиной Java. Используется, например, в апплетах Java.

С

CAB (cabinet) (кабинет) — формат Microsoft для сжатых файлов ресурса и других классификационных файлов. Используется также для распространения установочных файлов.

case-insensitive (без учета регистра) — свойство языка программирования, означающее, что в данном языке буквы, записанные в верхнем и нижнем регистрах, воспринимаются одинаково.

case-sensitive (чувствительный к регистру) — свойство языка программирования, означающее, что в данном языке буквы, записанные в верхнем и нижнем регистрах, воспринимаются как разные; другими словами, в таком языке слова «текст» и «Текст» воспринимаются как два разных слова. Язык Java является чувствительным к регистру.

cast — выражение Java, которое изменяет тип выражения на новый тип, указанный в скобках.

catch — ключевое слово языка Java. Применяется для объявления специального типа исключений и создания программного блока или инструкции, которые выполняются, если это исключение возникнет в блоке, содержащем инструкцию try.

CGI См. Common Gateway Interface.

char — обозначение типа целочисленных примитивных переменных языка Java, в котором символы Unicode представлены в виде 16-битных целых без знака.

Character — класс-оболочка Java для значений типа char.

character data (символьные данные) — текстовое содержание элемента или атрибута.

checked exceptions (обрабатываемые исключения) — программные исключения Java, для обработки которых требуется явный код.

child (дочерний) — в контексте объектно-ориентированного программирования дочерним называется любой объект, который наследует свойства и получает информацию от какого-либо другого объекта; класс Java, который связан с другим классом (родительским классом, или суперклассом) механизмом наследования.

class (класс) — в общем контексте объектно-ориентированного программирования метод группировки объектов, обладающих каким-либо общим свойством или свойствами; все классы Java происходят от класса Object.

Class — класс Java (Java.lang.Class), который идентифицирует тип любого объекта во время выполнения программы.

class file (файл класса) — результат компиляции класса Java.

class method (метод класса) — метод Java, объявленный статически и относящийся ко всему классу, а не к отдельным его экземплярам.

class modifiers (модификаторы класса) — ключевые слова Java (public, abstract и final), которые задают свойства или характеристики класса.

class variable (переменная класса) — статическая переменная, принадлежащая всему классу Java, а не отдельному экземпляру класса.

ClassCastException — исключение, возникающее в случае, если виртуальная машина Java замечает попытку преобразовать ссылку на объект к несовместимому типу.

clone — метод класса Object, предназначенный для создания копии объекта.

Collection — интерфейс Java (Java.util .Collection), который определяет основное поведение объектов Collections API.

Collections API (интерфейс прикладных программ для коллекций) — набор классов и интерфейсов Java 2, который предоставляет методы для манипуляций с коллекциями объектов.

Collections — класс Java (Java.util .collection), содержащий статические методы, применимые к коллекциям.

Color — класс Java (java.awt.color), который объединяет красный, зеленый и синий цвета с нужной интенсивностью для формирования цвета, видимого на экране.

Common Gateway Interface (CGI, универсальный интерфейс шлюзов) — соглашения, регулирующие взаимодействие между web-серверами и вспомогательными программами, например поисковыми машинами. Сценарии или исполняемые файлы, которые поддерживают взаимодействие между пользователями (через браузеры) и web-серверами.

Comparator — интерфейс Java (Java.util.comparator), который содержит методы, позволяющие пользовательским классам использовать методы сортировки и поиска класса Arrays.

completeness (завершенность) — термин объектно-ориентированного программирования, указывающий, что поведение класса полностью разработано и не требует дальнейшего уточнения при помощи подклассов.

Component — абстрактный класс Java (Java.awt.Component), который является родительским для всех экранных компонентов в графическом пакете AWT, кроме тех, которые относятся к меню.

constraints — объект Java, который передается диспетчеру компоновки, реализующему интерфейс LayoutManager2, и определяет способ обработки компонентов.

constructor (конструктор) — особая разновидность метода, вызываемого при создании экземпляра класса с помощью оператора new; при этом конструктор инициализирует объект. В классе Java может быть объявлен один или несколько конструкторов либо не объявлено ни одного.

constructor chaining (связывание конструкторов) — конструктор Java, который вызывает другой конструктор в соответствии со специальным набором правил, принятых в Java.

container (контейнер) — в терминологии Sun среда, в которой функционируют апплеты, сервлеты и EJB, необходимая для реализации специальных служб.

Container — класс Java (Java.awt.Container), являющийся предком по отношению ко всем объектам AWT GUI, которые содержат все компоненты интерфейса и управляют ими.

content pane (область содержимого) — в классах контейнеров Swing, таких как JFrame, данная область — тот контейнер, которому передаются все компоненты интерфейса.

continue — ключевое слово Java, которое используется в двух случаях: для возобновления работы программы после выхода из текущего цикла или, если вместе с ним указана метка, для передачи управления на помеченную инструкцию. См. также break.

controller (контроллер) — в схеме Model-View-Controller контроллер обеспечивает функции или услуги для передачи введенных пользователем данных объектам model и view (см. Model-View-Controller).

cookie — небольшой фрагмент данных в текстовом формате, содержащий информацию о предыстории обращений данного пользователя к данному web-серверу, автоматически создаваемый сервером на машине пользователя и сохраняемый web-браузером. Эти данные сервер может использовать для идентификации пользователя.

D

data binding (связывание данных) — процесс создания объектов для преобразования данных XML в объекты Java.

daemon Thread (поток-демон) — термин UNIX, обозначающий программу, которая работает в фоновом режиме и обрабатывает запросы к сетевым службам. С помощью метода setDaemon можно маркировать потоки Java как потоки- демоны, чтобы отличить их от пользовательских потоков. Как правило, эти потоки являются утилитами JVM, как, например, поток сборки мусора. Приложение Java останавливает свою работу, когда единственными функционирующими потоками остаются потоки-демоны.

deadlock (взаимная блокировка) — ситуация, в которой двум или более потокам Java требуется один и тот же ресурс, что приводит к остановке выполнения этих потоков.

decorator — схема в Java, согласно которой функции присоединяемого объекта добавляются к основному классу.

decrement — оператор (--), уменьшающий на единицу значение своего операнда, которым служит численная переменная примитивного типа.

deep copy (детальная копия) — термин программирования для обозначения такого метода клонирования объектов, при котором копируется не только сам объект, но и все те объекты, с которыми он связан. Метод clone в классе Object не создает детальную копию.

delegate — объект, который манипулирует внешним видом и поведением компонента (в пакете Swing) или объединяет функции объектов controller и view (в схеме Model-View-Controller).

delegation — модель события в Java 1.1, в которой компоненты, генерирующие событие, передают управление обработкой событий специальным слушателям событий.

deprecated (устаревший, нерекомендуемый) — термин JDK (Java Developers Kit), который присваивается классам, интерфейсам, конструкторам, методам или полям, использование которых в дальнейшем не рекомендуется, поскольку они могут быть исключены из последующих версий.

deserialize (воссоздать) — реконструировать объект Java, который был сохранен путем сериализации, обычно с использованием объекта ObjectlnputStream.

destructor (деструктор) — метод C++, уничтожающий объект пользовательского типа и восстанавливающий занимаемую им память и другие ресурсы. В Java сборка мусора преимущественно автоматическая. См. также fmalizer.

Dimension — класс Java (Java.awt.Dimension), объекты которого обычно используются для считывания и установки ширины и высоты графического компонента.

directives (директивы) — в JSP-страницах это теги, которые задают общие параметры для страницы или части страницы.

distributed computing (распределенные вычисления) — вообще говоря, это способ организации вычислений, при котором программы, выполняемые на различных компьютерах, взаимодействуют между собой через сеть для решения общей задачи.

DLL См. dynamic link library.

doclet — программа на Java, содержащаяся в пакете sun.tools.javadoc и предназначенная для настройки внешнего вида документов Java.

Document Object Model (DOM, объектная модель документа) — способ обработки документа XML, при котором весь документ хранится в памяти в виде иерархии элементов, прошедших синтаксический анализ. В web-браузерах под DOM также понимают иерархическую структуру документа HTML.

document type declaration (объявление типа документа) — структура внутри документа XML, которая содержит объявления разметки (или указывает на них), описывающие класс документов XML.

Document Type Definition (DTD, определение типа документа) — объявления разметки, которые описывают структуру и свойства класса документов XML.

DOM См. Document Object Model.

double — обозначение примитивного численного типа Java, использующего 64 бита для представления вещественных чисел с плавающей точкой.

Double — класс-оболочка Java для значений примитивов типа double.

drag&drop (перетащить и бросить) — операция графического интерфейса (из пакета java.awt.dnd), позволяющая передавать данные между приложениями Java и другими приложениями.

DTD См. Document Type Definition.

dynamic link library (DLL, библиотека динамической компоновки) — выполняемые пакеты или модули, которые программист может записывать в память и связывать их с приложениями.

dynamic method lookup (динамический поиск метода) — способ, который использует JVM (виртуальная машина Java) для нахождения и вызова соответствующего метода во время выполнения программы, основанный на фактическом типе объекта (в отличие от типа, указанного в ссылке).

E

editable — свойство компонентов TextArea и TextField. Если для данного компонента это свойство истинно, то содержимое компонента может быть изменено пользователем.

element (элемент) — структурная конструкция XML, состоящая из открывающего тега, закрывающего тега и собственно содержимого.

enableEvents — метод Java из пакета Java.awt.Component, который вызывается для того, чтобы разрешить создание событий пользовательского интерфейса. Какие конкретно события разрешается создавать, определяет маска события.

encapsulation (инкапсуляция) — термин объектно-ориентированного программирования, означающий, что структура объекта и детали его реализации скрыты от других объектов; информация об объекте находится внутри этого объекта. Инкапсуляция позволяет программистам модифицировать внутренние функции объекта, не изменяя кода, в котором этот объект задействован как единое целое.

entity (сущность) — структурная конструкция XML, которая связывает символьные данные или правильно оформленный (well-formed) документ XML с некоторым именем. К сущности можно обращаться с помощью ссылки на сущность.

entity reference (ссылка на сущность) — структурная конструкция XML, которая используется для обращения к сущности. Ссылка на сущность выделяется с помощью знака амперсанд (&) в начале и точки с запятой в конце (;).

Enumeration — интерфейс Java 1.0 (java.util.enumeration), определяющий способ, с помощью которого коллекция генерирует последовательности элементов коллекции, используя методы nextElement и HasMoreElements. Компания Sun планировала заменить Enumeration на интерфейс Iterator версии Java 2, но он попрежнему широко используется.

equals — метод Java, который сравнивает две ссылки на объекты и возвращает true, если содержимое этих объектов идентично. Встроенный метод equal s класса Object возвращает true, когда обе ссылки указывают на один и тот же объект.

Error — класс Java (Java.lang.error), который является родительским классом всех классов ошибок Java и подклассом Throwable. Под ошибками здесь обычно понимаются ситуации, из которых программа не может выйти самостоятельно, например нехватка памяти.

escape sequence (управляющая последовательность) — комбинация символов, обычно используемая для задания отсутствующих на клавиатуре символов и символов, которые могут вызвать сбой компилятора Java.

event listener (слушатель событий) — объект Java, который контролирует деятельность пользователя и отслеживает определенные события.

event mask (маска события) — свойство класса Java.awt.Component, которое позволяет определить типы событий GUI, генерируемых объектом.

Exception — класс Java java.lang.exception, который является родительским по отношению ко всем исключениям Java, а также подклассом класса Throwable. Исключение обычно означает такую ситуацию, из которой программа может выйти самостоятельно.

extends — ключевое слово Java, используемое для описания нового класса. Это слово указывает на базовый класс, свойства которого наследует новый класс.

Extensible Markup Language (XML, расширяемый язык разметки) — упрощенная форма SGML, предложенная в качестве стандарта для создания пользовательских языков разметки. Основное отличительное свойство языка XML заключается в том, что он позволяет вводить новые теги, чтобы максимально точно описывать содержание документа.

Extensible Stylesheet Language (XSL, расширяемый язык таблиц стилей) — язык, предложенный в качестве стандарта для преобразования данных XML в визуальный формат HTML.

F

field (поле) — переменная Java, которая определяет конкретную характеристику класса.

File — класс Java (Java.iо.File), который управляет полными именами файлов и папок, но не самими файлами данных.

file separator (файловый разделитель) — символ, который разделяет имя файла и путь доступа к нему, указанные в путевом имени.

filter (в системе ввода-вывода) — пакет интерфейсов (java.io), который определяет методы фильтрования для входных и выходных потоков и имена файлов.

filter (в системе графического представления) — пакет Java.awt.image, который обеспечивает так называемые классы фильтров для преобразования графической информации.

final — ключевое слово Java, означающее, что данный класс не может иметь подклассы. В применении к методу-члену класса это слово означает, что данный метод не может быть отменен в подклассе. В применении к переменной-члену класса это слово означает, что переменная является константой, то есть ее значение нельзя изменить после того, как оно было единожды задано.

finalize — объектный метод, вызывается в процессе сборки мусора, когда требуется восстановить память, которая была занята объектом, подлежащим уничтожению. Обычно этот метод используется для того, чтобы убедиться, что после уничтожения объекта системные ресурсы восстановлены.

finally — ключевое слово Java, используемое для того, чтобы к блоку try присоединить блок кода, который должен выполняться независимо от возникновения исключительной ситуации в блоке try.

float — обозначение примитивного 32-битного числового типа Java с плавающей точкой.

Float — класс-оболочка Java для значений примитивов типа float.

Font — класс Java (Java.awt.font), содержащий специфическую информацию о шрифтах.

font family (семейство шрифтов) — класс или группа шрифтов, которые обладают общими характеристиками.

FontMetrics — класс Java (Java.awt.FontMetrics), объекты которого необходимы для выполнения всех вычислений, связанных с внешним видом шрифта (например, этот класс содержит информацию о высоте и ширине отдельных литер данного шрифта).

form (форма) — структура, используемая в HTML-страницах для создания элементов, которые на основе соглашений CGI способны принимать введенную пользователем информацию и передавать ее на web-сервер.

G

garbage collection (сборка мусора) — процесс JVM (виртуальной машины Java) поиска и восстановления памяти, занятой объектами, которые больше не используются в программе и подлежат уничтожению.

GIF См. Graphics Interchange Format.

Graphical User Interface (GUI, графический пользовательский интерфейс) — интерфейс, в котором используются графические элементы, окна и указательное устройство; примерами GUI являются MacOs, Windows и Xll. JVM также поддерживает графический пользовательский интерфейс.

Graphics — класс Java (Java.awt.Graphics), в котором определены все методы и графические примитивы, необходимые для создания изображений на экране.

graphics context (графический контекст) — информация, относящаяся к аппаратной части компьютера, используемая операционной системой для создания изображений на графических устройствах, например на экране компьютера.

Graphics Interchange Format (GIF, формат графического обмена) — повсеместно распространенный формат графических файлов HTML (расширение .gif) для встроенных графических элементов. Патент на этот формат принадлежит Unisys. См. также Joint Photograpfic Expert Group (JPEG).

Graphics2D — класс Java 2 (Java.awt.Graphics2D), который является расширением класса Graphics.

GUI См. graphical user interface.

H

hashcode (хэш-код) — в контексте программирования характеристическое число, выводимое из содержимого элемента данных, которое позволяет программе или приложению быстро отыскать этот элемент.

hashCode — метод, присущий каждому объекту Java, который генерирует целочисленный хэш-код (принадлежащий примитивному типу int), характеризующий данный объект.

Hashtable — экземпляры этого класса Java (java.util .Hashtable) можно использовать для хранения произвольных объектов, причем для индексации этой коллекции также применимы любые объекты.

heavyweight components (тяжелые компоненты) — компоненты графического пользовательского интерфейса Java, которые используют операционную сиcтему пользовательской машины, в отличие от lightweight — легких компонентов пакета графического пользовательского интерфейса Swing.

hex (hexadecimal) (шестнадцатеричный) — о системе счисления с основанием 16, используемой в программировании. В эту систему входят цифровые символы от 0 до 9 и буквенные от А до F (или от а до f)

hidden variable (скрытая переменная) — в HTML-форме скрытая переменная содержит информацию, которая недоступна для прочтения или изменения пользователем, но передается на web-сервер.

hierarchical (иерархический) — способ логической организации элементов, называемый также древовидной структурой, при котором каждый элемент, за исключением корневого элемента, имеет родительский элемент и может иметь или не иметь дочерние элементы. Примерами такой организации могут служить библиотека классов Java, документы XML и система файлов и папок в компьютере.

HTML См. Hypertext Markup Language.

HttpServlet — базовый класс пакета javax.servlet.http, расширенный сервлетами, которые должны отвечать на операции GET и POST.

Hypertext Markup Language (HTML, язык гипертекстовой разметки) — язык, используемый для создания web-страниц и стандартизованный консорциумом W3C.

Hypertext Transfer Protocol (HTTP, протокол передачи гипертекстовых файлов) — набор правил (протоколов), базирующийся на TCP/IP, который служит основой для связи между web-сервером и клиентами.

I

IDE См. Integrated Development Environment.

identifier (идентификатор) — имя, присвоенное элементу в протоколе Java или в приложении.

IEEE См. Institute of Electrical and Electronics Engineers.

Image — абстрактный класс Java (Java.awt.image), который определяет, как хранится графическая информация.

implements — ключевое слово Java в объявлении класса, которое предшествует списку из одного или более интерфейсов, методы для которых содержатся в данном классе.

implicit variables (неявные переменные) — эти переменные автоматически создаются в JSP-странице.

import — инструкция исходного кода Java, которая сообщает компилятору Java, в каком пакете содержатся классы, используемые в этом коде.

increment — оператор (++), увеличивающий на единицу значение своего операнда, которым служит численная переменная примитивного типа.

IndexOutOfBoundsException — исключение Java, возникающее, когда происходит попытка обращения к несуществующему элементу массива; Array! ndexOutOf - BoundsException и StringlndexOutOfBoundsException являются подклассами класса IndexOutOfBoundsException.

inheritance (наследование) — в объектно-ориентированном программировании механизм связи между иерархически организованными объектами, характеризующийся тем, что некоторые объекты (дочерние) получают (наследуют) атрибуты другого объекта (родительского).

init (метод апплета) — по соглашению это метод, принадлежащий исходному классу апплета Java и вызываемый JVM web-браузера после создания апплета, но перед его отображением.

init (метод сервлета) — метод, принадлежащий классу сервлета Java и используемый для однократной инициализации сервлета. Он вызывается после создания сервлета, но перед тем, как сервлет начнет выполнять свои функции.

initialization (инициализация) — задание начального значения переменной.

inner class (внутренний класс) — вложенный класс или интерфейс, имеющий доступ ко всем методам и полям — членам того класса, в который вложен данный, в том числе и ко всем его членам, объявленным как private (то есть закрытым).

InputStream — абстрактный базовый класс Java (java.awt.inputStream) для различных классов Java, которые считывают данные как поток байтов.

Insets — класс Java (Java. awt. Insets), используемый в графическом интерфейсе. В этом классе содержится объект, задающий ширину границ всех сторон контейнера.

instance (экземпляр) — объект, созданный на основе данного класса, называется экземпляром этого класса.

instance fields (поля экземпляра) — набор специфических переменных данного экземпляра класса.

instance methods (методы экземпляра) — методы данного экземпляра класса, которые выполняются только при ссылке на данный экземпляр класса.

instance variable (переменная экземпляра) — переменная Java, которая является частью данного экземпляра класса, а не самого класса (в отличие от статической переменной).

instanceof — логический оператор, который используется для определения типа ссылки в выражении.

Institute of Electrical and Electronic Engineers (IEEE, Институт инженеров по электротехнике и электронике) — профессиональная организация, создающая стандарты для аппаратной части компьютеров и для программного обеспечения, а также для электронной промышленности.

int — обозначение примитивного 32-битного целочисленного типа Java, который всегда интерпретируется как целое число со знаком.

Integer — класс-оболочка Java для значений примитивного типа int.

Integrated Development Environment (IDE, интегрированная среда разработки) — среда разработки приложений, которая объединяет в одном пакете инструментальные программные средства, такие как редактор исходного кода, компилятор, загрузчик и отладчик.

interface (интерфейс) — почти то же, что и класс Java, но в интерфейсе присутствуют только объявления методов, но не их реализации. Класс Java может реализовать любое количество интерфейсов.

International Organization for Standardization (ISO, Международная организация по стандартизации) — группа, состоящая из национальных организаций по стандартизации из 89 стран, которая устанавливает стандарты в сфере телекоммуникаций и компьютерных технологий.

interrupt — метод экземпляра класса Thread; если объект Thread находится в состоянии sleep или wait, то вызов метода interrupt приводит к возбуждению потока и генерирует исключение InterruptedException. В противном случае устанавливается флаг прерывания.

interrupted — статический метод Java, который используется работающим потоком (Thread) для определения, имело ли место прерывание потока.

InterruptedException — исключение, которое генерируется, если поток (Thread), находящийся в состоянии sleep или wait, оказывается прерванным. Поток не может продолжать выполнение, но должен сначала обработать исключение.

lOException — класс Java (Java.io.exception), который является родительским по отношению ко всем исключениям, относящимся к процессам ввода-вывода, например открытие и чтение файла.

ISO См. International Organization for Standardization.

Iterator — интерфейс Java (Java.util.Iterator), который должен был заменить интерфейс Enumeration как предпочтительное средство перебора элементов коллекции.

J

J2EE См. Java 2 Enterprise Edition.

J2ME См. Java 2 Micro Edition.

J2ME См. Java 2 Standard Edition.

JAR (Java ARchive, архив Java) — формат файлов, аналогичный формату zip, используемый для объединения нескольких ресурсов (например, классификационных файлов и библиотек классов Java) в один файл.

Java 2 Enterprise Edition (редакция Java 2 для корпоративных программных систем на базе Java) — наибольшая коллекция утилит Java, разработанная компанией Sun для создания базовых приложений, удобных для подключения к Интернету.

Java 2 Micro Edition (J2ME, редакция Java 2 Микро) — уменьшенная в объеме и упрощенная коллекция утилит и библиотек Java, специально подобранных компанией Sun для решения несложных задач.

Java 2 Standard Edition (J2SE, стандартная редакция Java 2) — коллекция утилит и библиотек Java, созданная компанией Sun как стандарт, соответствующий требованиям большинства разработчиков приложений для Интернета.

Java 2D — группа классов Java, которая содержит некоторые дополнительные графические методы.

JavaBean — компонент Java, предназначенный для создания объектов, которые затем могут многократно использоваться в разрабатываемых приложениях.

Java Communications API (интерфейс прикладного программирования Java для организации связи) — группа классов Java и специфический для данной операционной системы код, который поддерживает непосредственное взаимодействие с последовательным и параллельным портами ввода-вывода.

Java Database Connectivity (JDBC, средство доступа Java-приложений к базам данных) — коллекция классов Java в пакете java.sql, которая позволяет программам Java соединяться с базами данных SQL.

Java Development Kit QDK, базовый комплект разработчика приложений на Java) — пакет Java, состоящий из инструментальных средств разработки, утилит, библиотеки классов и документации, который можно загрузить с web-сайта компании Sun java.sun.com.

Java Foundation Classes (JFC, основные классы Java) — имя, присвоенное компанией Sun коллекции из пяти инструментальных пакетов (Swing, Java 2D, Accessibility, Drag & Drop и Application Services), предназначенной для создания дополнительных графических пользовательских интерфейсов на Java 2.

Java Native Interface (JNI, собственный интерфейс Java) — интерфейс JNI является стандартным интерфейсом, который предоставляет языку Java доступ к языкам операционной системы хоста и определяет взаимодействие Java с программными модулями, написанными на других языках (на данный момент — С и C++).

Java Runtime Environment (JRE, среда исполнения Java) — коллекция программ и библиотек для конкретной операционной системы, которая позволяет выполнять программы Java. Состоит из виртуальной машины Java, основных классов Java и сопутствующих файлов. JRE не содержит компилятора, отладчика и других инструментов.

Java Virtual Machine (JVM, виртуальная машина Java) — виртуальный (в отличие от физического) компьютер, который является частью JRE и выполняет байт-коды Java, что обеспечивает мобильность приложений Java, то есть возможность их перенесения на многие платформы.

JavaBeans — стандарт Java, которому должны соответствовать компоненты JavaBean. Содержит набор стандартных спецификаций для написания компонентных приложений на языке Java. Компоненты позволяют пользователям строить приложения из многократно используемых частей и работать с ними в любой операционной системе и на любых платформах.

javac — приложение Java, которое запускает компилятор.

javadoc — утилита Java, которая позволяет автоматически создавать документацию путем обработки исходного кода и генерации HTML-страниц со справочной информацией.

JavaScript — язык сценариев, разработанный компанией Netscape (исходное его название было LiveScript) для создания web-страниц. Позволяет контролировать способ представления web-страниц в браузере. До некоторой степени обеспечивает поддержку встроенных апплетов Java.

JavaServer Pages OSP) — интерфейс прикладного программирования Java, который позволяет программисту комбинировать коды HTML и Java в одном документе для создания динамической web-страницы.

JComponent — класс Java (javax. swing. JComponent), который является базовым классом для визуальных компонентов Swing.

JDBC См. Java Database Connectivity.

JDK См. Java Development Kit.

JFC CM. Java Foundation Classes.

JIT CM. Just In Time.

join — метод экземпляра класса Thread, координирующий связь между потоками.

Joint Photographic Experts Group (JPEG, объединенная группа экспертов по машинной обработке фотоизображений) — формат для хранения графических файлов (расширение .jpg), поддерживаемый JVM и часто используемый на web- страницах. См. также Graphics Interchange Format (GIF).

JNI См. Java Native Interface.

JPEG CM. Joint Photographic Experts Group.

JRE См. Java Runtime Environment.

JSP CM. JavaServer Pages.

jspDestroy — метод, который всегда вызывается перед тем, как web-сервер удаляет код JSP-страницы из памяти.

jsplnit — метод, который всегда создается на JSP-странице и всегда вызывается перед тем, как начинает обрабатываться запрос пользователя.

_jspService — метод, который всегда создается на JSP-странице для обработки HTTP-запроса пользователя.

Just in Time (JIT, немедленный, по требованию) — технология, которая ускоряет выполнение программ на Java за счет динамической замены байт-кода Java на машинный язык по мере вызовов методов JVM. См. Java Virtual Machine.

L

label (метка) — идентификатор, который задается в инструкциях break и continue, идентифицируя точку перехода; после метки всегда ставится двоеточие.

layout manager (диспетчер компоновки) — объект, контролирующий позицию и размер компонента, находящегося внутри объекта Java.awt.Container.

lightweight components (легкие компоненты) — компоненты графического пользовательского интерфейса Java, для которых JVM выполняет всю обработку экранных событий и осуществляет создание на экране графических объектов.

List — интерфейс Java (Java.util .list), который предоставляет упорядоченную коллекцию ссылок на объекты.

listener (слушатель) — объект Java 1.1, связанный с генерирующим события компонентом и получающий сообщения о событиях конкретного класса.

local class (локальный класс) — внутренний класс Java, который определен внутри метода — члена класса (внешнего), имеющего доступ ко всем членам класса и всем локальным переменным, отмеченным ключевым словом final.

local variable См. automatic.

lock (блокировка) — эквивалент переменной, которая связывается с каждым объектом для контроля доступа потоков к этому объекту. Этими переменными может манипулировать только JVM в процессе синхронизации доступа к объекту.

long — примитивный 64-битный целочисленный тип Java, который всегда интерпретируется как целое число со знаком. См. также double (числа с двойной точностью).

Long — класс-оболочка Java для значений примитивного типа long.

low-level event (событие низкого уровня) — события Java, близкие к событиям операционной системы; например движениям мыши.

M

main — статический метод Java, необходимый для исходного класса приложения Java, который выполняется JVM после загрузки класса для того, чтобы запустить приложение.

manifest (манифест) — файл, присутствующий во всех архивных файлах JAR, который содержит дополнительную информацию об остальных файлах в архиве (например, электронную подпись или информацию о шифровании). Доступ к этой информации дает класс Java, util.jar.manifest.

Map — интерфейс Java (java.util .Map), который требует, чтобы реализующий его класс создавал пары ключ-значение, то есть каждому объекту сопоставлял уникальный ключ. К числу классов, реализующих интерфейс Map, относятся Hashtable и SortedMap.

marshalling (составление) — в технологии распределенного программирования — это процесс компоновки объектов и переменных для передачи их удаленному обработчику.

maximumSize — параметр, характеризующий объекты графического интерфейса, предком которых является Jcomponent. Значение этого параметра задается методом setmaximumSize.

MAX_PRIORITY — константа класса Thread, которая используется в методе setPriority для придания потоку (объекту Thread) наивысшего приоритета, допустимого для пользовательского потока.

member (член) — переменные, методы и внутренние классы Java, объявленные как часть класса, называются членами класса.

member class (класс-член) — внутренний класс Java, который не объявлен как статический и не объявлен внутри метода-члена класса.

MenuComponent — класс Java (java.awt.MenuComponent), родительский для всех классов Java. awt, которые используются для отображения меню на экране.

method (метод) — функция класса Java, для которой определено имя и объявлены специфические входные параметры и типы выходных данных.

method signature (сигнатура метода) — комбинация имени и параметров метода, с помощью которой данный метод можно отличить от других.

minimumSize — параметр, характеризующий компоненты Java, предком которых является Jcomponent. Значение этого параметра задается методом setMinimumSize и определяет минимальное пространство, которое менеджер компоновки отводит данному компоненту.

MIME См. Multipurpose Internet Mail Extensions.

MIN_PRIORITY — константа класса Thread, которая используется в методе setPriority для придания потоку (объекту Thread) наименьшего приоритета.

model — в схеме Model-View-Controller — объект Java, содержащий данные.

Model I (модель I) — относится к архитектуре приложений JSP, в которых код JSP отвечает и за первичное принятие решений, и за форматирование. В терминологии Model-View-Controller JSP-страница выполняет функции controller и view, в то время как объект JavaBean выполняет функции model.

Model II (модель II) — относится к архитектуре приложений JSP, в которых первичное принятие решений осуществляет сервлет, который передает изображение JSP-страницам при помощи метода RequestDispatcher. В терминологии Model-View-Controller контроллер является сервлетом, в то время как JSP-страница выполняет функции, связанные с визуальным представлением данных (функции view).

Model-View-Controller (MVC, модель-представление-контроллер) — технология создания компонентов пользовательского интерфейса, основанная на взаимодействии трех объектов: модель отвечает за логическое представление данных, представления — за визуальное представление данных, контроллер обрабатывает входные данные и передает их изменения в модель.

modulus (modulo) — оператор Java (%), применимый к объектам целочисленного типа или к вещественным числам с плавающей точкой. Данный оператор делит левый операнд на правый операнд и возвращает результат.

monitor (монитор) — механизм JVM, который использует переменную блокировки объекта (lock) для контроля доступа потоков к объекту.

multiple inheritance (множественное наследование) — в объектно-ориентированном программировании этот термин означает, что переменные и методы данного класса унаследованы более чем от одного класса. В языке Java множественное наследование не предусмотрено.

Multipurpose Internet Mail Extensions (MIME, многоцелевое расширение почты Итернета) — дополнение к протоколу STMP, содержащее, в частности, стандартные определения различных типов файлов и позволяющее включать в сообщения электронной почты мультимедийные файлы и использовать различные виды кодировок. Первоначально протокол MIME предназначался для передачи сообщений электронной почты, но теперь он широко используется в сетевых приложениях.

multitasking (многозадачность) — организация вычислительных процессов в операционной системе, при которой одновременно (или квазиодновременно) выполняется несколько задач.

multithreading (многопоточность) — характеристика среды выполнения, в которой возможно одновременное выполнение нескольких независимых потоков в рамках одной задачи, при этом каждый поток имеет полноценный доступ ко всем ресурсам основной задачи и к памяти, отведенной для нее.

N

namespace (пространство имен) — полный набор имен классов, методов и других программных элементов, который используется компилятором Java для однозначной идентификации этих элементов. Средство для разрешения конфликтов по совпадению имен между элементами из различных словарей в документе XML.

NaN См. Not a Number.

narrowing conversion (сужающее преобразование примитивов) — процесс Java, преобразующий один примитивный тип к другому примитивному типу, сопровождающийся возможной потерей информации; например, преобразование типа int к типу byte устраняет дополнительные биты.

narrowing conversion (сужающее преобразование ссылочных типов) — процесс Java, преобразующий ссылочный тип в подкласс; например, преобразование из Object в String.

NEGATIVE_INFINITY — константа Java, определенная в классах Float и Double, которая получается как результат вещественного деления отрицательного числа с плавающей точкой на ноль.

nested top-level inner class or interface (вложенный внутренний класс или интерфейс) — внутренний класс Java, который объявлен как статический и обрабатывается так же, как любой другой внешний класс Java.

new — ключевое слово Java, указывающее на создание нового объекта или массива.

NORM_PRIORITY — константа класса Thread, которая используется в методе SetPriority для придания потоку (объекту Thread) нормального приоритета приложения.

Not a Number (NaN, не число) — специальная константа Java, принадлежащая к типу вещественных чисел с плавающей точкой, которая обозначает результат некорректной арифметической операции, например извлечение квадратного корня из отрицательного числа. Эта константа определена в классах Float и Double.

notify — метод класса Object, который запускает выполнение потока, находящегося в списке очередности данного объекта. Поток не начинает выполняться, пока на это не поступит разрешение от специального механизма JVM, контролирующего очередность выполнения потоков.

notify All — метод, который действует аналогично методу notify, но вызывает выполнение всех потоков, находящихся в списке очередности данного объекта.

null — специальная литерная константа, которая используется в качестве значения неинициализированной ссылочной переменной.

O

object (объект) — экземпляр класса.

Observable — класс Java (java.util .observable), который является базовым для всех наблюдаемых объектов; в схеме Observer-Observable — это тот объект, изменение состояния которого отслеживает интерфейс Observer.

Observer — интерфейс Java (java.util .Observer) для объектов-наблюдателей (объектов Observer), которые следят за объектами класса Obsevrable.

OutStream — абстрактный класс Java (java.io.outstream), который является базовым для всех классов, которые записывают данные в виде потока байтов.

overloading (перегрузка) — ситуация, при которой- в классе Java имеются несколько методов с одинаковым именем, но различными наборами параметров. Тогда имя метода называется перегруженным.

overriding (переопределение) — методы подкласса заменяют методы суперкласса с тем же типом возвращаемого значения и той же сигнатурой.

Р

package (пакет) — коллекция связанных между собой классов и интерфейсов Java, обладающая единым пространством имен.

parent (родитель) — родителем данного класса является любой класс, предшествующий данному в иерархической системе (то есть расположенный ближе к корневому элементу).

path separator (разделитель пути) — символ, который служит разделителем в списке путей; примером может служить переменная окружения PATH в Windows. Класс File содержит строку PathSeparatorString, соответствующую данному окружению.

peer — объект пользовательского графического интерфейса операционной системы, который соответствует некоторому объекту из Java AWT.

pixel (пиксел) — наименьший видимый и адресуемый элемент изображения на мониторе или другом выходном устройстве. В Java используется как единица для определения размера и расположения объектов.

pointer (указатель) — в языке программирования С это механизм, обеспечивающий непрямой доступ к объектам и переменным; в Java отсутствует.

polymorphic (полиморфный) — характеристика объекта, указывающая на его способность принимать различные формы благодаря наследованию и перегрузке методов.

port address (адрес порта) — в компьютерных сетях, основанных на протоколе TCP/IP, это идентификатор сокета данного адреса в сети. По этому идентификатору программа или служба находит сокет.

POSITIVE_INFINITY — константа Java, определенная в классах Float и Double, которая получается как результат вещественного деления положительного числа с плавающей точкой на ноль.

preferredSize — параметр, характеризующий компоненты Java, предком которых является класс Jcomponent. Значение этого параметра задается методом setPre- ferredSize.

primary container (первичный контейнер) — объект Swing, для которого имеется соответствующий объект графического интерфейса операционной системы и который может поддерживать независимое окно.

primitive (примитив) — общее название некоторых типов Java (boolean, char, byte, short, int, long, float и double), которые хранятся непосредственно в двоичной форме.

priority (приоритет) — целочисленное значение от 1 до 10, которое присваивается потоку (Thread) и используется JVM при определении очередности выполнения потоков.

private — ключевое слово Java для маркировки переменных и методов, которые доступны только для методов, объявленных в том же классе.

promotion (повышение) — процесс, происходящий при компиляции, когда числа подвергаются расширяющему преобразованию к другому типу, если этого требует данная операция.

protected — ключевое слово Java для маркировки переменных и методов, которые доступны только для методов, принадлежащих классам из того же пакета или классам, для которых данный класс является суперклассом.

protocol (протокол) — набор правил, регламентирующих передачу данных между различными устройствами.

public — ключевое слово Java, позволяющее менять область видимости классов и их членов, делая их доступными для всех объектов независимо от границ пакетов.

R

random access (произвольный доступ) — возможность для программиста произвольно передвигать указатель позиции в файле и начинать чтение или запись с выбранной таким образом позиции.

Reader — абстрактный класс Java (java.iо.Reader), базовый для всех классов, которые считывают данные как поток 16-битных символов Unicode.

reference (ссылка) — виртуальная машина Java позволяет программисту работать не с самим объектом (то есть с физическим адресом ячейки памяти), а с указателем на этот объект, который называется ссылкой.

reference variables (ссылочные переменные) — все переменные Java, за исключением переменных примитивных типов.

Reflection API (API-отражения) — интерфейс прикладного программирования Java, состоящий из классов, которые позволяют программе выяснить, какие конструкторы, методы и переменные имеются в любом классе, а также какой интерфейс реализует этот класс.

Remote Method Invocation (RMI, интерфейс вызова удаленных методов) — стандарт передачи сообщений Java, часто используемой для организации распределенных вычислений, который позволяет программе на Java работать с объектом, расположенным в другой системе или в другой виртуальной машине Java так же, как если бы этот объект был локальным.

resume — метод экземпляра класса Thread, который позволяет продолжить выполнение приостановленного потока. Дальнейшее использование этого метода не рекомендуется, так как он может быть исключен из последующих версий Java.

RMI См. Remote Method Invocation.

root (корень) — единственный объект или элемент иерархической системы, который является предком для всех остальных элементов.

Runnable — интерфейс Java (java.lang.Runnable), который определяет метод run класса Thread.

RuntimeException — класс Java (java.lang.RuntimeException), родительский по отношению ко всем исключениям, которые не требуют объявления в инструкции throws.

S

SAX См. Simplified API for XML.

schema (схема) — формальная спецификация структуры документа XML.

scope (область видимости) — атрибут идентификатора, который контролирует права доступа этого идентификатора к другим частям программы.

semantic event (семантическое событие) — событие, в котором задействована дополнительная логика, в противоположность событиям низкого уровня.

serialization (сериализация) — преобразование объекта Java в последовательность байтов, форматированную таким образом, что возможно воссоздание объекта.

server (сервер) — компьютер, подключенный к сети, который предоставляет другим компьютерам (клиентам) доступ к имеющимся на нем ресурсам и службам.

servlet (сервлет) — программа на Java, которая работает в контейнере сервле- та на web-сервере и обрабатывает сетевые запросы (обычно HTTP-запросы).

servlet container (контейнер сервлета) — то окружение, в котором работает сервлет. API сервлета определяет, какие услуги должен обеспечивать контейнер сервлета.

session (сеанс) — в приложениях JSP и сервлетах сеанс содержит информацию о пользователе в течение того времени, пока пользователь взаимодействует с данным приложением.

Set — интерфейс Java (Java.util .set), который является расширением интерфейса Collection, содержащего ссылки на объекты, причем в Set наложено ограничение на дублирование ссылок; таким образом, каждая ссылка в Set уникальна.

SGML См. Standard Generalized Markup Language.

shallow copy (поверхностная копия) — копия, созданная методом clone класса Object, который копирует только значения ссылочных переменных.

short — примитивный 16-битный целочисленный тип Java, который всегда интерпретируется как целое число со знаком.

Short — класс-оболочка Java для значений примитивного типа short.

sign bit (знаковый бит) — наиболее существенный бит, входящий в состав таких примитивов Java, как byte, short, int и long. Если этот бит установлен, то число интерпретируется как отрицательное.

signature (сигнатура) — совокупность имени метода, списка типов аргументов и типов результатов в порядке их следования.

Simple Object Access Protocol (SOAP, простой протокол доступа к объектам) — недавно предложенный стандарт передачи запросов через Интернет с помощью документов XML.

Simplified API for XML (SAX, упрощенный интерфейс прикладного программирования для XML) — способ обработки документов XML, при котором синтаксический анализатор идентифицирует и анализирует элементы за один проход по документу. Пользователь SAX должен обеспечить методы для обработки проанализированных элементов.

singleton (единичный класс) — шаблон, который допускает создание только одного экземпляра класса; доступ к этому экземпляру контролируется статическим методом класса.

sleep — статический метод класса Thread. Его действие заключается в том, что поток, вызвавший этот метод, переходит в режим ожидания на указанное в методе число миллисекунд.

SOAP См. Simple Object Access Protocol.

socket (сокет) — в компьютерных сетях комбинация адреса компьютера и номера порта, которая образует уникальный канал связи.

Socket — класс Java (Java.net.socket), представляющий один канал передачи данных; содержит методы getlnputStreara и getOutputStream, предназначенные для создания входного и выходного потоков.

SortedSet — интерфейс Java, являющийся расширением интерфейса Set, который поддерживает ссылки в порядке, заданном методом compareTo.

SQL См. Structured Query Language.

stack trace (запись стека) — форматированный текст, отражающий предысторию выполнения метода, приведшего к исключительной ситуации или ошибке.

Standard Generalized Markup Language (SGML, стандартный язык разметки) — стандарт для форматирования документов с помощью тегов, которые определяют структуру документа и интерпретацию его содержимого. Язык SGML послужил основой для языков HTML и XML.

start (метод апплета) — метод Java, который вызывается виртуальной машиной Java после исходного отображения апплета, а также в тех случаях, когда web-страница, содержащая этот апплет, отображается повторно.

start (метод потока) — метод класса Thead, который разрешает выполнение данного потока.

static — обозначение для метода или переменной Java, указывающее, что метод или переменная относится к самому классу, а не к экземпляру класса.

static fields (статические поля) — члены класса Java, присоединенные к самому классу, а не к экземплярам класса.

static methods (статические методы) — методы-члены класса Java, которые выполняются в окружении самого класса, а не экземпляра класса.

stop — метод экземпляра класса Thread, который вызывает исключительную ситуацию ThreadDeath и резко прекращает выполнение потока, что часто приводит к непредсказуемым и нежелательным последствиям. Использование этого метода не рекомендуется, так как он может быть исключен из последующих версий.

stream (поток) — последовательность байтов, которую можно считывать только последовательно от начала до конца.

Structured Query Language (SQL, язык структурированных запросов) — международный стандартный язык для определения и доступа к реляционным базам данных с помощью текстовых инструкций.

subclass (подкласс) — класс, который является расширением (прямым или косвенным) другого класса; все классы Java (кроме Object) являются подклассами класса Object.

super — ключевое слово Java, которое указывает на то, что данные конструкторы, переменные или методы относятся к родительскому классу.

superclass (суперкласс) — в иерархической системе классов Java предок данного класса; непосредственным предком является прямой суперкласс. См. также extends, parent.

suspend — метод экземпляра класса Thread, который приостанавливает выполнение данного потока до тех пор, пока не будет вызван метод resume. Использование этого метода не рекомендуется, так как он может быть исключен из последующих версий.

Swing — новый расширенный набор средств графического пользовательского интерфейса Java, который упрощает разработку оконных компонентов по сравнению с исходным пакетом AWT; стандартное расширение Java 2.

synchronized — ключевое слово Java, которое активирует механизм монитора (программного средства синхронизации) метода или блока кода.

syntax (синтаксис) — набор явных правил создания кода, включающий порядок следования символов.

System (класс) — класс Java (Java.lang.system), состоящий из статических методов и переменных, которые инициализируют JVM при запуске программы.

T

tag (тег) — в языках разметки, таких как HTML, XML и JSP-страницы, тег представляет собой специальную последовательность символов, которая не является частью текста документа, но содержит информацию о способе отображения этого текста.

taglib — в технологии JSP программист может определить свою собственную библиотеку специальных функций Java, обозначенных с помощью тегов. Специальная директива taglib указывает JSP, что нужно использовать конкретную библиотеку.

TCP/IP См. Transmission Control Protocol/Internet Protocol.

Thread — класс Java (Java.lang.Thread), который является родительским для потоков в JVM и определяет их поведение.

ThreadDeath — исключительная ситуация, возникающая при внезапном прекращении выполнения потока.

ThreadGroup — класс Java (Java.lang.ThreadGroup), объекты которого используются JVM для определения множества объектов Thread и для регулирования операций над этим множеством.

throw — инструкция Java, которая вызывает прекращение обработки нормальных инструкций и инициирует обработку исключения; должна быть связана с объектом Throwable.

Throwable — класс Java (Java.lang.Throwable), который является родительским по отношению ко всем классам ошибок и исключений Java.

throws — ключевое слово Java, которое используется в объявлении методов для введения списка исключений, которые может вызвать этот метод.

timestamp — переменная Java типа long, в которой записывается системное время в момент, когда происходит какое-либо событие.

toString — метод, входящий во все ссылочные типы Java, который используется компилятором для обработки инструкций, содержащих объекты String и оператор +.

Transmission Control Protocol/Internet Protocol (TCP/IP, протокол управления передачей/протокол Интернета) — набор протоколов для передачи сообщений, разработанных для использования в семействе сетей Интернета и для объединения неоднородных сетей.

try — инструкция Java, конструирующая блок кода, в котором может возникнуть исключительная ситуация; за этой инструкцией должна следовать хотя бы одна инструкция catch и/или инструкция finally.

type (тип) — класс или интерфейс объекта Java. Вообще говоря, в объектно- ориентированном программировании интерфейс объекта иногда рассматривается отдельно от его реализации, что приводит к дальнейшему подразделению на класс и тип.

U

UDP См. User Datagram Protocol.

UML См. Unified Modeling Language.

unary (унарный) — характеристика оператора Java, означающая, что у него имеется только один операнд, например оператор ++.

unchecked exceptions (необрабатываемые исключения) — исключения класса RuntimeException, для которых компилятор не требует от программиста задания явного кода обработки исключений.

Unicode — международный 16-битный стандарт ANSI для представления алфавитных символов (содержит более 65 000 символов, включая графические). В Java используется версия Unicode 2.0, см. www.unicode.org.

Unified Modeling Language (UML, унифицированный язык моделирования) — стандартная нотация визуального моделирования программных систем.

Uniform Resource Identifier (URI, универсальный идентификатор ресурса) — набор имен и адресов, указывающих на ресурсы.

Uniform Resource Locator (URL, универсальный указатель ресурса) — система унифицированных адресов ресурсов, которая содержит явные указания, как найти нужный ресурс в Интернете.

UPI См. Uniform Resource Identifier.

URL См. Uniform Resource Locator.

URL — класс Java (Java.net.URL), который предназначен для работы с ресурсами, заданными своими URL-адресами, и для создания сетевых приложений.

User Datagram Protocol (протокол датаграмм пользователя) — протокол передачи пакетов без установления прямого соединения (альтернатива TCP/IP), который поддерживает простую службу передачи данных между программами; считается ненадежным, так как не предоставляет никаких гарантий доставки пакетов.

user thread (пользовательский поток) — любой поток Java, который не обозначен как демон.

V

Valid XML (допустимый документ XML) — документ XML, который соответствует правилам, заданным в DTD или в схеме документа.

variable shadowing (сокрытие переменных) — механизм, в рамках которого переменные Java, относящиеся к определенной области видимости, могут запретить непосредственный доступ к другим переменным с теми же идентификаторами, принадлежащими той же области видимости.

Vector — класс Java (Java.util.Vector), который включает в себя расширяемый массив ссылок на объекты класса Object.

view (представление) — объект Java, который отвечает за визуальное представление данных в технологии Model-View-Controller.

viewport (порт просмотра) — логическое окно, в которое видна часть просматриваемого объекта Java. С помощью полосы прокрутки можно перемещать это окно так, чтобы по частям просмотреть весь объект.

visibility (видимость) — уровень доступа к данному классу Java других классов.

W

W3C См. World Wide Web Consortium.

wait — метод класса Object, который при вызове со стороны некоторого потока блокирует его доступ к объекту с помощью переменной lock, делает этот поток неактивным и переводит в состояние ожидания. В этом состоянии поток находится до тех пор, пока не будет вызван извещающий метод notify, или notifyAll, или же пока не истечет время, указанное в параметре метода wait.

wait list (список очередности) — список потоков Java, которые относятся к определенному объекту и находятся в режиме ожидания.

wait set (набор задержек) См. wait list.

Web application (web-приложение) — набор сервлетов, JSP-страниц, файлов HTML, графических файлов и других ресурсов, которые объединены в структурированную иерархию, расположенную на web-сервере.

Web Application Resource (WAR, ресурсы web-приложения) — набор всех файлов, необходимых для создания web-приложения, сжатый в один архивный файл по алгоритму zip. Определен в спецификации сервлета 2.2.

well-formed XML (правильно оформленный документ XML) — документ XML, соответствующий требованиям W3C (W3C Recommendation) для XML 1.0.

widening conversions (расширяющие преобразования) — такие преобразования примитивных типов, при которых не происходит потери информации; для ссылочных типов — преобразование из подкласса в класс, расположенный выше в иерархической системе классов.

widget («прибамбас») — термин профессионального жаргона программистов, означающий компонент пользовательского интерфейса, например кнопку или переключатель.

World Wide Web Consortium (W3C, консорциум W3C) — организация, устанавливающая стандарты для WWW (www.w3.org).

wrapper classes (классы-оболочки) — классы Java, которые соответствуют каждому из примитивных типов и обеспечивают соответствующие служебные функции.

Writer — абстрактный класс Java (Java.io.writer), базовый для всех классов, которые записывают данные как поток 16-битных символов.

X

XML См. Extensible Markup Language. XSL См. Extensible Stylesheet Language.