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

Атрибуты

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







Тип


Атрибут


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

Строковый

CDATA



Маркерный

ID
IDREF
IDREFS
ENTITY
ENTITIES
NMTOKEN
NMTOKENS



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









Ключевое члово

Описание

#REQUIRED

Этот атрибут должен присутствовать в каждом экземпляре элемента

#IMPLIED

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

#FIXED

Этот атрибут должен присутствовать в каждом экземпляре элемента и иметь указанное значение



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

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





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




Бесси
Kopoвa
My-y


Ровер
Собака
Гав



ПРИМЕЧАНИЕ

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


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

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

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


Использование 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 в приложениях

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


Элементы

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


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

Комментарии (comments) в XML устроены и функционируют так же, как в
HTML, что видно из следующего примера:

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

ВНИМАНИЕ

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


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

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


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

Язык 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 находились в разработке, но ко времени, когда книга будет опубликована, вероятно, эти новые версии уже будут доступны.


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

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


Объявление

Синтаксис:






Тип элемента




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



EMPTY
#PCDATA
ANY
Mixed
Children











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

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

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

или

Snuggles

Хотя выбор остается за вами, имеется несколько общих принципов. Мы расскажем о них в главе 2, а сейчас вам просто следует запомнить, что этот выбор является непростой задачей для всех программирующих на XML.
Ниже приведены некоторые примеры объявлений атрибутов (несколько позже будет объяснено их значение) [Gender (male|female) — пол (мужской|женский), species — вид, "Cams familiaris" — "собака обыкновенная". — Примеч. перев.]:



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

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



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











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



rock



a rock



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



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

Ключевое слово Определение
#REQUIRED Этот атрибут должен присутствовать в каждом экземпляре элемента

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



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

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

Каждый элемент, который используется в документе XML, должен быть определен в DTD. Существует несколько правил, которым нужно следовать при именовании элементов:
имена элементов не должны содержать символа < или >;
имя элемента должно начинаться с буквы или символа подчеркивания. После первой буквы в имени элемента может содержаться любое количество букв, цифр, дефисов, точек или символов подчеркивания;
имена элементов не могут начинаться с последовательности xml (в любой комбинации верхнего или нижнего регистров),
двоеточия запрещены, они применяются только в пространствах имен


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

Объявления сущностей позволяют использовать ссылки на сущность. Ссылка на сущность (entity) — это последовательность символов, которая автоматически подставляется вместо другой последовательности символов. Обычно ссылки на сущность требуются для того, чтобы обозначить символы, которые иначе могут быть приняты за символы разметки. Если вы имели дело с HTML, то, вероятно, сталкивались со ссылками на сущность. Наиболее распространенный тип сущности — это общая сущность (general entity), то есть сущность, которую можно подставить вместо символов в документах XML Формат объявления общей сущности следующий:
lt;!ENTITY имя_сущности "заменяемые символы">
Ссылки на сущность имеют вид &имя_сущности; В XML имеется пять встроенных сущностей. Их не обязательно объявлять в DTD, хотя в спецификации XML это сделать рекомендуется, чтобы гарантировать возможность взаимодействия с HTML и SGML. Пять встроенных сущностей перечислены в приведенной ниже таблице:





Ссылка на сущность




Числовой заменитель




Символ



&
<
>
'
"


&
<
>
'
"


&
<
>
'
"


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






ПРИМЕЧАНИЕ

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



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



Super Dnnk&tm;



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

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





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





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

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





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

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


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

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

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

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


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

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


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



Объявления

Синтаксис:









Тип сущности




Пример




Описание



Общая





Может использоваться только в данных XML


Параметрическая




Может использоваться только в DTD

Внешняя





Используется для включения внешних файлов XML


Непроверенная





Используется для включения файлов, не являющихся файлами XML



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

XML можно применять как на стороне сервера, так и на стороне клиента. Следующие два раздела рассказывают о принципах применения XML в каждой из этих областей. Кроме того, 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


Вода в бутылках

Напитки высшего качества




0


0


0


0


0



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

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

Java Developer's Guide to XML

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

.

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

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



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



СОВЕТ

Если вы хотите, чтобы ваш код HTML был совместим с XML, то обратите внимание, что тег HTML переноса строки
способен причинить немало хлопот. Некоторые браузеры не воспринимают тег
, а пару тегов

воспринимают как два переноса строки. Для решения этой проблемы поместите пробел между символами br и косой чертой:
.

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



Правила 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.


Применение

Примеры:


какой-нибудь текст


Примеры:
Copyright © 2001 Sybex Inc/
while( a %lt; b) // для представления кода Java в HTML


Программирование на основе 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 можно сформулировать следующим образом.
Создание одного или нескольких пользовательских классов для обработки событий анализатора SAX.
Создание объекта, который обеспечивает прием входного потока символов.
Создание анализатора на основе одного из пакетов инструментальных средств.
Присоединение классов, обрабатывающих события, к анализатору.
Присоединение входного потока к анализатору, начало анализа.
Обработка всех событий в пользовательских классах, которая позволяет записать нужные данные, отследить ошибки и т. д.
Как видно, обработка документов XML в модели SAX подчиняется совсем другой идеологии, чем в модели DOM. Выбор одного из этих двух подходов для конкретного приложения — самый важный выбор из тех, которые вам предстоит сделать. В табл. 1.1 приведены основные критерии, которые следует учитывать в данном вопросе.

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








Фактор




DOM




SAX



Требования к ресурсам памяти


Могут оказаться довольно высокими


Пропорциональны количеству элементов, которые требуется сохранить в памяти


Скорость первой обработки


Невысокая, так как анализируются все элементы


Высокая, особенно если нужные элементы легко локализуются


Скорость повторной обработки


Сравнительно высокая, так как все элементы уже находятся в памяти


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


Допустимость модификации


Очень высокая


Ограничена необходимостью создания нового документа XML для каждого прохода анализатора




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

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

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


Super-Drink

Extra Good Beverages



]]>



Схема XML

Хотя определения DTD в настоящее время являются стандартом определений типов документов XML, у них есть несколько серьезных ограничений. Определения DTD были унаследованы от языка SGML, где они были исходно разработаны для определения языков разметки, а не для создания схем баз данных. Самое существенное ограничение определения DTD заключается в том, что оно не обеспечивает достаточного контроля над содержимым элементов. Например, с помощью DTD невозможно указать, что показанный ниже элемент является допустимым [Today's date — сегодняшняя дата — Примеч. перев. ]:
09/01/2000
Точно так же нельзя указать, что следующий элемент не является допустимым:
Яйца, тост, кофе
Кроме того, вам может потребоваться задать более точные ограничения на количество появлений элемента в документе. С помощью 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)















































BigSoft Xtreminator 3.36

20

195.99

Managing your life has never been so easy.





E-Dev ProntoWorks

35

299.99

The premier integrated rapid e-development suite for busy e-professionals.





Элементы сложного типа определяются с помощью элемента 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)



Just Singin' Along
The Happy Guys

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


I'm Really Fine

Can't Stop Grinnin'

Things Are Swell

2/23/1954





It' s Dot Com Enough for Me: Songs From Silicon Somewhere

The Nettizens

A collection of the best folk music from Internet companies.

My B2B Is B-R-0-K-E

Workin' in a Cubicle

Killer Content Strategy

She Took the Bricks. I Got the Clicks

7/12/2000





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



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





























Title Artist







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



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



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



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

...













""

,









...

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

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



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



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

В объявлении элемента вы указываете, что может появиться в качестве содержимого этого элемента Если вам нужно объявить элемент, который не должен содержать никаких данных, вы можете использовать тип EMPTY (например, ).
Хороший пример пустого элемента — элемент HTML img Чтобы этот элемент не нарушал принципа допустимости документа XML, нужно использовать синтаксис, определенный для пустого элемента XML:

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

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

ПРИМЕЧАНИЕ

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

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

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


sad_story*, funny_story*,

lesson*, conclusion)>

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


sad_story*, funny_story*,

lesson+| conclusion)>

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



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


sad_story*, funny_story*,

(lesson+ | conclusion)*)>










(lesson | conclusion)*)>









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





ПРИМЕЧАНИЕ


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



Сущности

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


Требования к правильно оформленному документу 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 — количество. — Примеч. перев. ].
В приложениях XML обычно используются следующие типы данных и вспомогательные функции:
сам файл XML, имеющий строго определенную структуру;
определение типа документа (Document Type Definition, DTD), где определяется структура файла XML (необязательный элемент);
таблицы стилей, содержащие информацию о том, как данные должны быть отформатированы при выводе (необязательный элемент);
процессор XML и различные служебные функции для манипулирования данными и переформатирования данных.


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

Для разработчиков web-приложений привычными являются рассуждения о том, что делают с текстом те или иные теги. Мы привыкли к тому, например, что тег выделяет соответствующий текст жирным шрифтом. Тем не менее фактически этот тег не имеет какого-либо собственного значения. Как будет отображен текст, помеченный тегом , полностью зависит от программы, анализирующей данные. В случае 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. — Примеч. ред. ]

































Название товapa 0писание Ценa
Фонарик

Свет, который всегда с вами! $9.95
Неоновая лампа Ничто не скажет слово "класс" так,

как неоновая лампа!
$14.75


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

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

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



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






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




Фонарик

Cвeт, который всегда с вами!



$9.95





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

Ничто не скажет слово "класс"

так, как неоновая лампа!

$14.75





Первая строка этого документа — объявление 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 для гипотетического каталога товаров:











Это 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 позволяет достичь такого уровня соответствия конкретным условиям представления данных, которого очень трудно или невозможно достичь с использованием 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.



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

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

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



Доработка DTD

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

id ID "REQUIRED
date CDATA "REQUIRED
subject CDATA #IMPLIED>







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

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


date = "8/2/2000"
subject = "ваша задача на сегодня">

Крис Минник


Bceм coтpyдникaм


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



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

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



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









Guide to Plants

Everything you've ever wanted to know about plants.

$12 99

4


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



This is the cover from the first edition





12/23/1999





Writing Fake Catalogs

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

$59.95

30

09/01/2000







Just Singin' Along

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

$10 00


100

2/23/2000





It' s Dot Com Enough for Me Songs from Silicon

Somewhere


A collection of the best folk music from

Internet companies.




src="track2.mp3">

Track 2: My B2B Is B-R-0-K-E



4/12/2000









ElectroThermal Oxidizor

This amazing gizmo uses electricity to

produce heat that can be used for oxidization

purposes.


$24.95

10





6/2/2000





Percusive Interface Unit

Communicate with your favorite electric

calculating machine through tapping!


$109.99

7

6/23/200K/onsale_date>







Достоинства 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 дает разработчикам прекрасные возможности для создания приложений.



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

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

Листинг 2.6. Исправленный файл catalog .dtd
italics | quote | link | general)*">
day_of_month?, year?,(hour, minute, seconds?)?)">











































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



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













Guide To Plants



Everything you've ever wanted

to know about plants.




$12.99

4


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



This is the cover from the first

edition.








4

4

1999









Guide To Plants, Volume 2



Everything else you've ever wanted

to know about plants.




$12.99

4



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



This is the cover from the first

edition.








4

8

2000






keywords="how-to, technology">

The Genius's Guide to the 3rd Millenium



Christoph Minwich





Learn to convert your replicator

into a transporter..and other neat tricks.




$59.95

0


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





1

1

2001









Dryer Lint Art



A new book about the new folk art

that's catching on like wildfire.




$5.95

34


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





11

3

1971




















Music for Dogs



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.




$14.99

50


src="http://www.musicfordogs.com/images/cover.gif">



3

2

1990





Track 1: Fetching the Stick



An exciting and playful melody.











Just Singin' Along



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



$10.00

100



2

23

2000










It' s Dot Com Enough For Me: Songs From Silicon Somewhere



A collection of the best folk music from Internet companies.



$12.99

4



4

12

2000





Track 2: My B2B is B-R-O-K-E

















ElectroThermal Oxidizer



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



$24.95

10





6

2

2000









Percusive Interface Unit



Communicate with your favorite

electric calculating machine--through

tapping!




$109.99

7




6

23

2001









Umbrella



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



$10.99

7



7

2

2000

















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

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



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

Авторам, пишущим на 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 — общая сумма к оплате. — Примеч. перев. ]:















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





Установка бензинового нacoca



3

0



$100

$300











Установка бензинового нacoca

$300





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





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

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



Элемент shipping

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

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



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

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

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

name CDATA #IMPLIED>
В объявлении атрибута name элемента productjine указано, что этот атрибут не является обязательным (#IMPLIED). Но информация о принадлежности товара (элемент product) к некоторой серии товаров (элементу productjine) не имеет смысла, если мы не знаем названия этой серии. Поэтому соответствующий атрибут, name, мы сделаем обязательным. Внести это изменение несложно:
name CDATA #REQUIRED>
Следующее объявление — наиболее содержательная часть этого определения DTD [Photo — фотография, clip — клип. — Примеч. перев. ]:
quantity_in_stock|photo| onsale_date|clip)* >
В таком виде это объявление подразумевает, что элемент product может содержать любое (в том числе нулевое) количество любых перечисленных здесь элементов. Это не соответствует фактической организации каталога на сайте XMLGifts.com. Также здесь не учитываются сведения об авторах книг (для элементов Books) и исполнителях (для элементов CDs). Такие промашки часто случаются, если используется недостаточно большой фрагмент исходных данных, для которых вы составляете DTD. Полностью исправленное объявление элемента product выглядит следующим образом:
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 этого элемента:


id CDATA #IMPLIED>

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


id ID #REQUIRED>

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


keywords CDATA #IMPLIED>

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





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

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

italics|quote|link)*>



attrib CDATA #IMPLIED>

href CDATA "REQUIRED
alt CDATA #IMPLIED>
Существует большая вероятность того, что такой же список элементов разметки, который используется в элементе paragraph, потребуется в каком-то другом месте DTD (например, в элементе title или footnote, то есть в заголовке или в сноске). Чтобы упростить процесс создания DTD, мы ввели параметрическую сущность, представляющую собой список элементов разметки текста:
italics | quote | link)* ">

СОВЕТ

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

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



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

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



Эта яркая новая группа из Бельгии,

звучание которой можно определить как смесь

Iggy Pop и Spice Girls,стремительно завоевывает

популярность во всем мире.




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

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




type CDATA #REQUIRED>


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



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

Бельгии,

звучание которой можно определить как смесь

Iggy Popи

Spice Girl

стремительно завоевывает популярность во всем

мире.




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

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












attrib CDATA #IMPLIED>




href CDATA #REQUIRED

alt CDATA #IMPLIED>




type CDATA #REQUIRED>

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

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

Справочник по растениям

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

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



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

Следующий объявленный в DTD элемент — onsa!e_date. Исходным назначением этого элемента было обеспечение возможности внесения товаров в каталог заранее, еще до фактического начала их продажи В будущем возможно и другое применение этого элемента. Например, мы можем предоставить пользователю возможность просматривать новые товары или учитывать значение этого элемента (дату начала продаж) при составлении отчетов для внутреннего использования
Как отмечалось в главе 1, не существует способа указать в DTD, что значением некоего элемента должна быть дата Тем не менее можно создать несколько дочерних элементов onsale_date, чтобы более точно указать, данные какого типа ожидаются, а также чтобы формат этих данных был достаточно гибким и мог включать, например, день недели, число месяца или даже часы, минуты и секунды [Day of week — день недели, month — месяц, day of month — день месяца year — год, hour — час, minute — минута, seconds — секунды — Примеч перев]
(day_of_week?.month?,day_of_month?,year?,
(hour,minute, seconds?)?)>







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


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




format CDATA #IMPLIED>


length CDATA #IMPLIED>


size CDATA #IMPLIED>


src CDATA #IMPLIED>



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

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



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



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



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





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

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

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

discount CDATA #IMPLIED>
База данных каталога не занимается определением того, как именно тот или иной клиент получает скидку (как член определенной привилегированной группы клиентов или по распродаже). Эти факторы контролируются приложением и базой данных о клиентах. База данных о клиентах содержит информацию как раз о том, какие клиенты или группы клиентов имеют право на скидки.
Все, что должно содержаться в элементе price, — это сведения о стоимости данного товара. Поскольку в стандартном определении DTD отсутствует возможность указать тип данных элемента, то за соответствие реальным ценам тех данных, которые содержатся в элементе price, отвечает приложение.
Следующий элемент DTD, quantity_in_stock, не требует больших изменений:
*>
Если убрать излишний символ оператора повторяемости (*), то элемент получится как раз таким, как надо:

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


width CDATA #IMPLIED>




src CDATA #IMPLIED>



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

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




width CDATA #IMPLIED>


height CDATA #IMPLIED>


src CDATA #REQUIRED>



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


format (gif|png|jpg) #REQUIRED>

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

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



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




width CDATA #IMPLIED

height CDATA #IMPLIED

src CDATA #REQUIRED>







Каталог товаров и 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







Контекст

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



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

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



Написание DTD

Независимо от того как вы преобразуете данные в XML, в процессе написания определения типа документа вы непременно получите представление о потенциальных проблемах, связанных с организацией данных. В DTD должно быть указано, каким образом элементы каталога связаны друг с другом, также DTD должно обеспечивать возможность последующего добавления в каталог новых данных, не нарушая требований допустимости документа XML.
Один из способов создания DTD — начать с правильно оформленного документа XML и выводить DTD из него. Для этого нужно просто последовательно "пройтись" по документу и создать объявления для каждого элемента разметки, который вы встретите. Этот процесс в той или иной степени может быть автоматизирован. Для начала этот метод неплох, но полученное таким образом описание DTD не может быть столь же логичным и столь же полезным, как DTD, созданное с нуля и основанное на подробном исследовании.
Другой способ разработки DTD заключается в том, чтобы писать DTD до того, как написан сам документ XML. Это более формальный метод, который требует тщательного планирования. Если вы начинаете с нуля, не имея каких- -либо данных, которые вам нужно преобразовывать, можно использовать этот метод.
Чаще, однако, при создании DTD используется комбинация этих двух методов. Если, к примеру, нам требуется создать DTD для служебных записок, можно начать с типичной записки и разметить ее как документ XML [Memo — служебная записка, from — от кого, to — кому. — Примеч. перев. ]:

Крис Минник
Сотрудникам
Сегодня выходной. Поезжайте на озеро отдыхать

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








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

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



Обсуждение

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



Ограничения

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



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

Таблицы представляют собой, вероятно, наиболее распространенный способ организации данных. Хранение данных в строках и столбцах обеспечивает гибкость при создании представлений данных и определения взаимосвязей между ними. Тем не менее использование единственной таблицы — это очень неэффективный способ организации данных.
Если бы в нашем примере для создания каталога электронного магазина использовалась реляционная база данных, первым шагом по улучшению организации было бы создание отдельной таблицы для каждого вида товаров. Это помогло бы избежать очевидных потерь, связанных с тем, что в единой таблице приходится отводить целый столбец, в котором, возможно, будет заполнена только одна ячейка (например, если в столбце указывается автор книги, для всех остальных видов товаров, помимо книг, этот столбец будет пустым). Далее, если для какой-то книги указывается не автор, а редактор, то ради одной такой книги придется добавлять целый столбец. Если у какой-либо другой книги два (или более) автора, то снова придется либо создавать очередной столбец, либо создать отдельную таблицу для авторов.
На рис. 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:





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

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

<Рrice>$12 99




Вильям Смит

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







Как показано в предыдущем примере, реляционную базу данных часто удается преобразовать в 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, — Примеч. ред. ]





По ком звонит колокол



Эрнест Хемингуэй







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



Вильям Брогден





Крис Mинник







Преступление и наказание



Федор Достоевский













Поддержка Unicode

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



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

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

ВНИМАНИЕ

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



Причины

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



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

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

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




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



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

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



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

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



Шаблон универсального элемента создает новый

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



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

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



Шаблоны XML

Шаблоны XML — это готовые решения распространенных проблем, возникающих в определенных ситуациях. В последнее время шаблоны все больше привлекают внимание как способ совершенствования процесса разработки приложений и обмена полезной информацией как внутри отдельных компаний, так и в сфере разработки программного обеспечения в целом.
Несмотря на большое количество появившихся в последнее время книг и статей о шаблонах, лежащая в их основе идея не представляет собой ничего нового. Это та же идея, на которой основаны книги по нетрадиционной медицине, диетическому питанию и оздоровительным процедурам, — сходные задачи имеют сходные решения. В шаблоне просто формулируется контекст задачи, сама задача и способ ее решения. Шаблоны обычно составляются некоторым стандартным образом, что облегчает их чтение и понимание.
Если при конструировании DTD вы будете использовать исключительно готовые шаблоны, вы, вероятнее всего, не сделаете грубой ошибки, но одновременно вряд ли добьетесь оптимального решения. Тем не менее очень часто оказывается, что кто-то другой уже сталкивался с такой же задачей, которую пытаетесь решить вы, и, заглянув в архив шаблонов, вы найдете искомое решение и вам не придется изобретать велосипед.
Для описания шаблонов имеется определенный стандарт, состоящий из нескольких разделов (краткое описание шаблона, контекст, пример и т. д.). Ниже описывается шаблон универсального элемента (generic element) для web-документов. Этот шаблон и многие другие можно найти на сайте www.xmlpatterns.com.



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:

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

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



Этот запрос просто передает на сервер все данные, необходимые для вызова указанного метода. Обратите внимание, что пространство имен, указанное в первом элементе внутри 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.



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

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



Создание первого чернового варианта 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


name CDATA #IMPLIED>
quanti ty_in_stock|image|onsale_date|clip)* >
id CDATA #IMPLIED>





width CDATA #IMPLIEO>
height CDATA #IMPLIED>
src CDATA #IMPLIED>



format CDATA #IMPLIED>
length CDATA #IMPLIED>
size CDATA #IMPLIED>
src CDATA #IHPLIED>




Стандартизация 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 (хотя время от времени его придется модифицировать) для обеспечения структурированности и целостности данных.

Требования

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

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

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

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

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





Упрощайте 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 для описания телефонного звонка.

(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)>

digit, digit, digit, dash, digit, digit,digit,digit)>
%valid_phone_dlgits; #REQUIRED>

value CDATA #FIXED "-">
OELEMENT to (phone_number)> (caller|answerer)*> <'ELEMENT caller (#PCDATA)>



phone_number CDATA #REQUIRED>





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



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

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



Основная задача, стоявшая перед создателями

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



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

API для JSP-страниц

Многочисленные попытки разработать системы, позволяющие включать в статический контекст HTML-страницы динамические данные с помощью специальных тегов, встроенных в код HTML, основаны на следующей идее: при передаче страницы специальный процессор распознает эти теги и использует их для того, чтобы динамически вставить данные по мере передачи страницы. Часто тип файла, содержащего такую страницу, обозначается специальным образом, чтобы указать web-серверу на необходимость специальной обработки данного файла.
В качестве очень удачных примеров реализации такой системы можно назвать сервер Cold-Fusion (www.allaire.com) и страницу ASP (Active Server Pages) компании Microsoft (http://msdn.microsoft.com/workshop/server/default.asp).
Компания Sun для динамического генерирования web-страниц использует технологию JavaServer Pages QSP). Версия JSP 1.1 (на момент написания книги) входит в J2EE и играет важную роль при написании серверов web-приложений на языке Java.
Технология JSP основана на технологии сервлетов. По сути, процессор JSP преобразует статические элементы web-страниц и динамические элементы, определенные тегами JSP, в исходный код Java для класса сервлетов. Когда на web- сервер поступает запрос, адресованный JSP-странице, для создания ответа выполняется этот класс сервлетов. До тех пор пока статические элементы страницы не изменятся, создание ответа на запрос происходит очень быстро, потому что класс остается в памяти.
Одна из ведущих компаний-производителей программного обеспечения недавно провела тест на производительность для приложений ASP и аналогичных приложений JSP. Результаты теста показали, что реализация приложения на основе Orion JSP гораздо более быстродействующая, чем ASP-реализация. Постоянно обновляемые результаты тестов публикуются по адресу www.orionservr.com/ benchmarks/benchmark.html.



API для объектной модели документа

В этой книге мы в основном будем использовать набор инструментальных средств JAXP (Java API for XML Parsing — интерфейс прикладных программ Java для анализа XML) компании Sun. Основной интерфейс API для манипулирования фрагментами документов XML согласован с формальной спецификацией DOM (Document Object Model — объектная модель документа) Консорциума W3C. Этот интерфейс API дает наиболее полный доступ ко всем элементам документа XML, с чем связана его сложность по сравнению с другими интерфейсами API. Существуют более простые интерфейсы API, поддерживающие DOM, но версия Консорциума W3C является наиболее распространенной.
Текущую версию набора инструментальных средств JAXP вы можете загрузить с web-сайта компании Sun или использовать версию, представленную на сайте Tomcat по адресу jakarta.apache.org. Этот набор состоит из пакетов Java, которые представляют WSC-версию интерфейса API, и пакетов, реализующих различные анализаторы и служебные программы. На момент написания книги этот набор не входил в стандартную библиотеку расширений Java, и его придется загрузить с сайта разработчиков по адресу http://developer.java.sun.com/developer/ products/xml.



API для сервлетов Java

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

ПРИМЕЧАНИЕ

Полноценное описание API для сервлетов Java требует отдельной книги. В этом разделе мы приводим только краткий обзор по данной тематике.
На момент написания книги текущей версией API для сервлетов Java является версия 2.2, а версия 2.3 находится в стадии тестирования. Ко времени издания этой книги версия API 2.2, вероятно, будет уже широко поддерживаться специализированными web-серверами, а на существующих web-серверах будут установлены соответствующие расширения.
Если web-сервер снабжен расширением для обработки сервлетов, то соответствующие настройки конфигурации этого сервера позволяют определить, какие запросы должны обрабатываться сервлетами. На сайте компании Sun Microsystems по адресу http://java.sun.com/products/servlet/industry.html представлен список доступных расширений для сервлетов и специализированных web- серверов.
Ниже перечислены web-серверы, соответствующие критерию 100% Pure Java.
Tomcat. Открытый проект Apache Software Foundation (http://jakarta.apache.org).
Enhydra. Недорогой коммерческий сервер приложений, написанный на Java (www.lutris.com) и поддерживающий технологии сервлетов JavaServer Pages и Enterprise JavaBeans.
Orion. Коммерческий сервер приложений (www.ononserver.com), поддерживающий все последние технологии Java, включая Е2ЕЕ и Enterprise JavaBeans.
Resin. Этот сервер (www.caucho.com/index.xtp), соответствующий критерию 100% Pure Java, задуман как сервер приложений масштаба предприятия и специализируется на использовании XML и XSL.
Согласно терминологии Sun, web-сервер, обрабатывающий сервлеты Java, выполняет роль контейнера сервлетов (servlet container), так же как браузер играет роль контейнера апплетов. Контейнер сервлетов должен загружать и инициализировать требуемые классы и выполнять основные части транзакции HTTP. Контейнер сервлетов создает объект HttpServletRequest, который содержит удобное представление запроса пользователя, и объект HttpServl etResponse, который обеспечивает методы, необходимые для того, чтобы сервлет мог отослать ответ.
Контейнер сервлетов также создает поток (класс Thread), предназначенный для выполнения кода сервлета в ответ на запрос пользователя. Каждый запрос получает собственный поток, который независимо выполняет методы сервлета, причем обычно создается только один экземпляр сервлета. Это означает, что программистам следует быть очень внимательными при использовании переменных экземпляров.
Поскольку обычно экземпляр сервлета остается загруженным в память web- сервера в течение длительного времени, ответ на запрос, обращенный к сервлету, формируется очень быстро. Этот способ работает гораздо быстрее, чем технологии, в которых приходится порождать новый процесс и загружать приложение каждый раз, когда возникает запрос.


Форматирование описаний товаров

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



Генерирование ответа сервлетом

Все ресурсы, необходимые для контроля за созданием ответа, содержатся в интерфейсах ServletResponse и HttpServletResponse. Например, приведенные ниже обращения к методу setHeader можно использовать для того, чтобы запретить браузеру кэшировать посылаемую ему страницу:
response. setHeader("Expires", "Mon, ,26 Jul 1990 05:00:00 GMT");
response.setHeader("Cache-Control" , "no-cache, must-revalidate");
response.setHeader("Pragma", "no-cache"); // для HTTP 1.0
Объект ServletResponse предоставляет программе-сервлету выходной поток, в который будет записано содержимое посылаемой страницы. Этот поток может быть типа PrintWriter, и тогда возможно преобразование содержимого в формат Unicode, или типа ServletOutputStream, то есть поток простых двоичных данных, когда преобразование не происходит.



Гибкость содержимого

Чтобы добиться гибкости внутреннего содержимого страницы, мы собираемся использовать форматирующий класс с именем ProductFormatter Этот класс выдает данные элемента XML product в формате, задаваемом с помощью списка имен полей, которому сопоставлен список стилей, применяемых к тексту каждого поля.
В качестве простого примера рассмотрим следующую ситуацию: для каждого товара требуется отобразить на странице его название в формате ch3 и цену в формате ch4. Для этого мы определяем два массива типа String:
Stnng[] elem = { "prname", "price" };
String[] shortSt = { "ch3", "ch4"
Также мы хотим, чтобы имя каждого товара было представлено в виде ссылки, щелчок на которой вызывает отображение полной информации о данном товаре. Для этого строковой переменной alink присваивается соответствующее значение, наподобие следующего:
"http://localhost/servlet/cattest?action=showproduct"
Также нам надо определить целочисленную переменную типа int с именем linkN, содержащую индекс (номер) поля, которое должно стать ссылкой. В нашем случае linkN0, так как такой ссылкой должно быть имя элемента (название товара). Когда эти параметры установлены, метод doOutput, приведенный в листинге 3.11, может форматировать данные для конкретного товара (элемента product), содержащиеся в документе XML catalog.xml. В результате выполнения метода doOutput получается строка, которую уже можно вставлять в HTML-страницу.

Листинг 3.11. Метод doOutput (productFormatter.java)
public String doOutput( Element el ){
StringBuffer sb = new StringBuffer( );
String pid = null ;
if( aLink != null ){
pid = "&id=" + el.getAttribute("id") ;
System.out.println("pid is " + pid );
}
else { System.out.println("aLink null");
}
for( int i = 0 ; i < elem.length ; i++ ){
if( i == linkN && pid != null ){
sb.append( " sb.append( style[i] );
sb.append("\" href=\"");

sb.append( aLink );

sb.append( pid );

sb.append("\">");

addText( sb, elem[i], el );

sb.append( "
");

}

else {

sb.append( "
sb.append( style[i] ); sb.append("\">");

addText( sb, elem[i], el );

sb.append( "
");

}

}

return sb.toString();

}

Например, для элемента product, данные о котором приведены в листинге 3.12, в результате выполнения метода doOutput получится следующая строка:

<а class="ch3" href= "http //localhost/servlet/cattest?action=showproduct">

Guide to Plants price ea = $12 99



Листинг 3.12. Описание отдельного товара (элемента product) из каталога catalog.xml






$12 99

4





This is the cover from the first edition







4

4

1999







До сих пор мы занимались форматированием данных для одного товара Теперь посмотрим, как создать на странице список товаров со ссылками на их полные описания В классе 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, который устанавливает заголовок страницы, затем создает теги и , за которыми следует строка, содержащая тег <link> для связывания HTML-страницы с таблицей стилей. Затем следует тег <boby> и вызывается метод completeCatalog Далее пишутся закрывающие теги и закрывается выходной поток PnntWnter <br><br> <br><br> <b>Листинг 3.14.</b> Метод doPost сервлета, отображающий весь каталог (CatalogTestServ.java) <br><br> public void doPost(HttpServletRequest req, HttpServletResponse resp)<br><br> throws ServletException, IOException<br><br> {<br><br> resp.setContentType("text/html");<br><br> PrintWriter out = new PrintWriter(resp.getOutputStream());<br><br> String action = req.getParameter("action");<br><br> out.println("<html>");<br><br> out.println("<head> <link rel="canonical" href="https://www.e-lave.ru//chapter01.htm"/><title>CatalogTestServ Output");

out.println( cssLink );

out.println("\r\n");

try {

if( "showcatalog".equals( action )){

completeCatalog( out );

}

else if( "selectkeyword".equals( action )){

doKeywordSelect( out );

}


}catch( Exception e ){

e.printStackTrace( out );

}

out.println("");

out.println("");

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("

Complete Catalog

");

out.println("");

out.println("" + "");

out.println("
BooksCDs Gadgets
");

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("
");

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("
");

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("
");

}

В листинге З 16 приводится текст первой части получившейся HTML-страницы Обратите внимание, что многие строки разбиты на несколько частей, чтобы поместиться на страницу книги Несмотря на использование таблицы стилей, экономящей память, вся страница полностью занимает 17 213 байт



Листинг 3.16. Первая часть генерируемой сервлетом HTML-страницы



CatalogTestServ Output







Complete Catalog









Рис. 3.2. Отображение каталога товаров

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

Гибкость стилей

Самым очевидным инструментом для достижения гибкости в применении различных стилей к документу являются каскадные таблицы стилей. С помощью CSS задаются параметры стиля различных компонентов web-страницы. В настоящее время CSS является наиболее широко поддерживаемым стандартом и входит в официальную часть спецификации HTML 4.
Помещая информацию о стиле в отдельный файл — так называемую таблицу стилей (style sheet), мы значительно уменьшаем объем текста, который приходится генерировать сервлету. Если для всех страниц на вашем сайте используется одна и та же таблица стилей, то web-браузер пользователя может кэшировать ее и таким образом уменьшить время ожидания ответа от сервера для всего сайта.
В листинге 3.10 приводится простая таблица стилей, задающая стиль тегов HTML , ,

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

СОВЕТ

Превосходное пособие по применению таблиц стилей вы найдете на сайте 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, помещенного внутрь тега , как показано в следующем примере:
Catalog Test Servlet Output
type="text/css" media="screen" >

Присоединив к странице такую таблицу стилей, можно очень легко задавать стиль любого элемента. Для этого нужно просто добавить к тегу атрибут, например style = "ch2". Указанный таким образом стиль замещает стиль, задаваемый браузером по умолчанию для этого элемента.
Чтобы понять, насколько использование таблиц стилей эффективнее, можно сравнить два фрагмента кода HTML, выполняющих одну и ту же функцию.
Пример использования таблицы стилей:
<а class="ch3" href= "http://localhost/servlet/cattest?action=showproduct">
Guide to Plants
price ea = $12.99
Пример непосредственного задания стилей:


Guide to Plants


price ea = $12.99


Индексация товаров

Пользуясь приведенным выше кратким обзором интерфейсов Java, дающих доступ к объектной модели документа, мы можем исследовать вопрос о создании
различных структур данных, которые ускорят процесс поиска товаров по каталогу, оставляя в то же время информацию о товарах в формате DOM. Ниже перечислены некоторые структуры данных и функциональные возможности, которые нам нужны:
перечень категорий product_line (серий товаров) каталога;
перечень всех товаров каждой серии;
быстрый поиск информации о конкретном товаре (содержимого элемента product) по указанному идентификатору товара (id);
список всех используемых ключевых слов, который можно предоставить пользователю для поиска нужных ему товаров;
быстрый поиск товаров по выбранным ключевым словам.
Метод scanCatalog, как показано в листинге 3.6, создает структуры данных, удовлетворяющие этим требованиям. Эти структуры данных — массивы типа String с именами productLineNames и keywords и объекты Hashtable с именами productLi neHT, productHT и prodByKeyHT. Мы используем классы коллекций, совместимые как с пакетом JDK Java 1.1, так и с JDK Java 1.2, поскольку (на момент написания книги) некоторые процессоры сервлетов до сих пор используют библиотеки Java 1.1.
Метод scanCatal og вызывается сразу же после того, как конструктор этого класса (листинг 3.4) завершил разбор файла XML. Заметим, что в методе scanCatalog первый метод, примененный к корневому элементу (гЕ), — это метод normalize(). Причина этого заключается в том, что у анализатора Sun имеется свойство воспринимать символы возврата каретки и дополнительные пробелы в тексте (которые авторы, пишущие на XML, часто используют для того, чтобы документ было легче просматривать) так, что этот текст разбивается указанными символами на несколько узлов типа Text. Метод normalize объединяет содержимое всех примыкающих друг к другу узлов Text в один узел Text.

Листинг 3.6. Метод scanCatalog инициализирует различные объекты Hashtable (theCatalog.java)
public void scanCatalog(){

Element rE = catDoc.getDocumentElement();

// the root

rE.normalize();

productLineNL = rE.getElementsByTagName("product_line");

productLineHT = new Hashtable();

productHT = new Hashtable();

prodByKeyHT = new Hashtable();

// note that in contrast to other get methods, getAttributes

// returns "" if the attribute does not exist

int i,j, ct = productLineNL.getLength();

productLineNames = new String[ ct ];

for( i = 0 ; i < ct ; i++ ){

Element plE = (Element)productLineNL.item(i);

productLineNames[i] = plE.getAttribute("name");

NodeList prodNL = plE.getElementsByTagName("product");

productLineHT.put( productLineNames[i], prodNL ); // node list

int pct = prodNL.getLength();

System.out.println( productLineNames[i] + " ct " + pct );

for( j = 0 ; j < pct ; j++ ){

Element prodE = (Element)prodNL.item(j) ;

String id = prodE.getAttribute("id");

if( id == null ){

System.out.println("No id - productLine " + productLineNames[i] + " product " + j );

}

else { productHT.put( id, prodE );

// product by id

String keys = prodE.getAttribute("keywords");

if( keys != null ){

addProdByKey( keys, prodE );

}

}

}

}

// end loop over product lines

ct = prodByKeyHT.size();

keywords = new String[ ct ];

i = 0 ;

Enumeration en = prodByKeyHT.keys();

while( en.hasMoreElements()){

keywords[i++] = (String)en.nextElement();

}

shellSortStr( keywords );

}

Метод addProdByKey создает объект prodByKeyHT, как показано в листинге 3.7. Этот метод должен разрешить некое затруднение, связанное с тем, что строка keywds может содержать не одно, а несколько ключевых слов (или фраз), разделенных запятыми. Для решения этой задачи используется класс StringTokenizer, но обратите внимание, что после разбора строки с помощью метода StringTokenizer нужно использовать метод trim, который убирает лишние пробелы перед строкой ключевых слов и после нее. Объект Vector, в котором хранятся ссылки на элементы, сохраняет исходный порядок расположения товаров, то есть такой же, какой был в файле XML.



Листинг 3.7. Метод addProdByKey (TheCatalog.java)

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

// затем создает вектор v или добавляет элемент рЕ к уже

// существующему вектору в prodByKeyHT

private void addProdByKey( String keywds, Element pE ){

StringTokenizer st = new StringTokenizer( keywds, ",");

while( st.hasMoreTokens() ){

String key = st.nextToken().trim();

Vector v = (Vector)prodByKeyHT.get( key );

if( v == null ){

v = new Vector();

prodByKeyHT.put( key, v );

}

v.addElement( pE );

}

}



Информация для представления каталога в сети

Вообще говоря, перед тем как каталог отображается в окне браузера пользователя, содержащаяся в нем информация проходит несколько этапов обработки. Основной элемент DOM, который фигурирует на этих этапах, — Element, представляющий отдельный товар. Этапы создания виртуального магазина на основе каталога XML следующие.
Берется объектная модель документа (каталога), представленная в виде списка узлов (NodeLi st), каждый из которых соответствует некоторому товару.
К списку узлов применяются правила отбора. Возможные правила отбора включают идентификатор товара (id), ключевое слово или название серии товаров (productjtine).
При необходимости массив элементов сортируется.
Для каждого элемента (товара) генерируется и добавляется к создаваемой странице код HTML, задающий формат представления данных об этом товаре.



Инициализация сервлета

Когда контейнер сервлета загружает код и создает экземпляр класса сервлета, API гарантирует, что первым будет вызван метод init и что он будет выполнен прежде, чем начнется обработка любых запросов пользователя. API сервлета обеспечивает передачу параметров инициализации вновь созданному экземпляру, используя объект класса ServletCongigClass.
До появления версии 2.2 API сервлетов каждый производитель использовал свой способ конфигурирования настроек инициализации. Теперь, когда компания Sun выбрала основанную на XML спецификацию, можно рассчитывать, что появится единая стандартная конфигурация.
Листинг 3.3 содержит документ XML, используемый для задания параметров инициализации сервлетов, которые мы будем обсуждать в главе 7.

Листинг 3.3. Задание параметров инициализации сервлетов (web.xml)

cattest
com.XmlEcomBook.catalog.CatalogTestServ

catalog
com.XmlEcomBook.catalog.CatalogServ

workdir
e:\\scripts\\XMLgifts


Questionnaire
com.XmlEcomBook.Chap07.QuestionnaireServ

homedir
e:\\scripts\\questionnaire


Qanalysis
com.XmlEcomBook.Chap07.QanalysisServ

homedir
e:\\scripts\\questionnaire



В методе init сервлета QuestionnaireServ параметр с именем homedir используется для того, чтобы задать значение Srting с именем homedir:
homedir = config.getlnitParameter("homedir");
Здесь config —это объект ServletConfig, переданный методу init или полученный из метода getServletConfigO. Помимо этого, метод init обычно устанавливает связь с базами данных и открывает файлы регистрации.


Интерфейс Document

Объект Java, который инкапсулирует весь документ XML, реализует расширение Document интерфейса Node. Большая часть методов этого интерфейса связана с созданием или модифицированием DOM в памяти. Тот метод, который мы будем использовать (getDocumentElement), просто возвращает ссылку на корневой элемент данного документа:
Element rootE = catDoc.getDocumentElement();
Например, чтобы получить список узлов (объект NodeLi st) всех элементов product в документе catDoc, следует использовать следующие методы:
Element rootE = catDoc.getDocumentElement();
NodeList nl = rootE.getElementsByTagName("product");



Интерфейс Node

Исходный набор методов для всего пакета org.w3c.dom обеспечивается интерфейсом Node. В этом пакете имеется 13 интерфейсов, производных от интерфейса Node, которые представляют различные части документа. Хотя все они являются расширением Node, определенные методы этого интерфейса в некоторых производных интерфейсах не имеют смысла. В табл. 3.8 перечислены методы интерфейса Node. Обратите внимание на то, что интерпретация возвращаемых значений nodeName и nodeValue зависит от типа узла [Node — узел. — Примеч. перев. ].

Таблица 3.8. Методы интерфейса Node
















Метод




Возвращаемое значение




Описание



getNodeName


NodeName


Возвращаемое значение — строка, представляющая имя Node; интерпретация зависит от типа узла


getNodeVal ue


NodeVal ue


Возвращаемое значение — строка, представляющая значение узла; интерпретация зависит от типа узла


setNodeValue


Пустое множество




getNodeType


Целочисленное значение типа short


Возвращаемое число идентифицирует тип узла согласно определению в интерфейсе Node


getParentNode


Ссылка на узел


Возвращается ссылка на узел, являющийся родительским по отношению к данному в иерархии DOM. Не для всех типов узлов существуют родительские узлы


getChildNodes


Ссылка на семейство узлов NodeList


Объекты NodeList обеспечивают доступ к упорядоченному списку ссылок на узлы


getFirstChild


Ссылка на узел


Первый дочерний узел для данного узла или null, если дочерние узлы отсутствуют


getLastChild


Ссылка на узел


Последний дочерний узел для данного или null, если дочерние узлы отсутствуют


getPrevlous Sibling


Ссылка на узел


Узел, непосредственно предшествующий данному, или null, если таковой отсутствует


getNextSibling


Ссылка на узел


Узел, непосредственно следующий за данным, или null, если таковой отсутствует


getAttributes


Ссылка на семейство NamedNodeMap


Методы NamedNodeMap обеспечивают доступ к атрибутам по имени. Возвращает null, если атрибуты отсутствуют


getOwnerDocument


Ссылка на документ


Объект Document, которому принадлежит данный узел, или null, если этот узел сам является объектом Document

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



ПРИМЕЧАНИЕ


Для упрощения терминологии мы будем называть объекты, реализующие интерфейсы Node, Element и др., объектами Node, Element (узел, элемент) и др. соответственно. Фактический тип объектов, реализующих интерфейсы, не играет роли, так как мы будем использовать только методы интерфейсов.

Поскольку за недостатком места мы не можем предоставить формальное изложение API org.w3c.dom в том виде, в котором оно приводится на сайте консорциума W3C, исследуем по крайней мере, каким образом фрагмент каталога, соответствующий какому-то товару, представляется объектами Java. В листинге 3.5 показан код XML для одного товара; объект Element для этого кода будет содержать иерархию объектов Node, представляющих XML.



Листинг 3.5. Код XML для одного товара (catalog.xml)



Guide to Plants





Everything

you've ever wanted to know about plants.





$12.99

4


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



This is the cover from the first edition.







4

4

1999





Например, если вы выполните метод getFirstChild элемента product, то получите ссылку на узел, представляющий элемент name. Элемент name содержит дочерний узел типа Text, а значением этого узла является строка Guide to Plants (справочник по растениям).

Доступ к XML-атрибутам элемента product осуществляется с помощью метода getAttribute, который по имени атрибута возвращает строку — значение атрибута, как в следующем примере:

String id = product.getAttribute("id")

String keywords = product.getAttribute("keywords");

Доступ к узлам первого по отношению к элементу product уровня иерархии осуществляется с помощью метода getChil dNodes. Этот метод возвращает объект, реализующий интерфейс NodeLi st. Объект NodeLi st отличается от других тем, что он содержит динамическое представление документа XML. Это значит, что если в иерархию узлов элемента product будет встроен какой-либо новый узел, это изменение автоматически отразится в объекте NodeLi st (списке узлов XML).



Интерфейс NodeList

В этом интерфейсе имеются только два метода:
  • int getLength() — возвращает текущее количество узлов, которое может быть равным нулю;
    Node item ( int n ) — возвращает ссылку на n-й узел в списке или null, если данная позиция списка пуста.


    Язык тегов JSP

    В приведенном ниже коде JSP-страницы теги JSP начинаются с символов <*= и заканчиваются символами %>. После компиляции в Java-класс запрос, обращенный к этой JSP-странице, выдаст обычный статический текст HTML-страницы, куда будет вставлена динамически сгенерированная строка, созданная с помощью метода toStnng, примененного к новому объекту Date:

    JRun Date Demo

    Date And Time <%= new java util Date().toString() %>






    Из-за больших различий между интерфейсами 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-страницеили статической web-странице





    Используется внутри тегов forward, include и plugin для добавления или модифицирования параметров в объекте request





    Выдает значение свойства bean-компонента по его имени





    Задает значения свойств bean-компонентов





    Создает или отыскивает bean-компонент с указанным именем и областью видимости





    Предоставляет полную информацию для загрузки подключаемых модулей Java (Java Plug-In) в web-браузер клиента




    Классы и интерфейсы для сервлетов Java

    Пакеты javax.servlet и javax.servlet.http содержат классы и интерфейсы, используемые при создании сервлетов. Пакет javax.servlet в основном содержит обобщенные классы и интерфейсы, в то время как классы пакета javax.servlet.http специализированы для работы с протоколом HTTP. В табл. 3.1 перечислены интерфейсы пакета javax.servlet.

    Таблица 3.1. Интерфейсы пакета javax.setvlet


    Интерфейс

    Описание
    Servlet Этот интерфейс определяет методы, которые должны быть реализованы в каждом сервлете. Интерфейс Servlet реализуется классом GenericServlet
    Servl etRequest Доступ ко всей информации о запросе клиента осуществляется через объект, реализующий этот интерфейс. За создание объекта ServletRequest отвечает процессор сервлетов
    Serl etResponse Объекты, реализующие этот интерфейс, создаются процессором сервлетов и передаются методу service сервлета для формирования ответа клиенту
    RequestDispatcher Этот интерфейс позволяет переадресовать запрос от текущего сервлета к другому сервлету или JSP-странице для дальнейшей обработки запроса
    SerletConfng Объекты, использующие этот интерфейс, применяются для хранения информации, которая помогает конфигурировать сервлет во время его инициализации
    Servl etContext Объекты, использующие этот интерфейс, позволяют сервлету получать информацию о процессоре сервлетов и об окружении сервлета
    SingleThreadModel В этом интерфейсе не содержится методов. Он используется для того, чтобы предотвратить одновременный доступ нескольких потоков к одному экземпляру сервлета. Процессор сервлетов выполняет это требование либо путем ограничения доступа и организации очереди запросов, либо путем создания отдельного экземпляра сервлета для каждого потока

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



    Таблица 3.2. Классы пакета javax.servlet



    Класс


    Описание
    GenericServlet Этот класс обеспечивает минимально необходимую функциональность
    ServletInputStream Класс для чтения потока двоичных данных из запроса
    ServletOutputStream Класс для записи потока двоичных данных, входящих в ответ
    В пакете javax.servlet определены только два исключения. 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




    Обработка запросов JSP

    Ниже описана последовательность событий, происходящих при обработке запроса, обращенного к JSP.
    Запрос, обращенный к JSP, направляется web-сервером к процессору JSP (JSP engine).
  • Процессор JSP отыскивает соответствующий сервлет, основываясь на имени страницы. Если сервлет существует, то запрос передается методу сервлета _jspService с помощью объектов HttpServl etRequest и HttpServl etResponse, как это происходит с обычным сервлетом.
    Если исходная страница изменилась или еще не была скомпилирована, то компилятор обрабатывает исходный код и создает эквивалентный исходный код Java для сервлета, реализующего интерфейс HttpJspPage.
    Затем код компилируется и выполняется новый сервлет. Сервлет может оставаться в памяти, что позволяет очень быстро отвечать на следующий запрос.
    Объекты request и response — это в точности те же самые объекты, которые используются в обычных сервлетах; разница заключается только в том, что метод service создается процессором JSP.


    Обработка запросов сервлетами

    В обычных коммерческих приложениях запросы пользователей обрабатываются в следующей последовательности.
    Данные, передаваемые в запросе, используются для создания объекта HttpServletRequest, который содержит информацию из заголовка запроса и всю остальную дополнительную информацию. Также создается объект HttpServletResponse в ходе подготовки к созданию ответа на запрос.
    Вызывается метод service сервлета со ссылками на указанные два объекта. На основании типа запроса принимается решение, какой из методов обработки запроса следует вызвать в данном случае. Специализированные сер- влеты обычно не отменяют метод service, но могут заменить методы doGet и/или doPost.
    Метод doGet или doPost исследует запрос и определяет, какие действия должно осуществить приложение. Во всех приложениях, за исключением самых простых, сервлеты обычно используют другие объекты для выполнения запросов к базам данных или для вычислений.


    Организация каталога

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


    Организация поиска по ключевым словам

    Поскольку мы уже проделали некоторую работу по кодированию ключевых слов для каждого товара в каталоге, мы, конечно, хотели бы, чтобы пользователю было удобно работать с этими ключевыми словами. Напомним, что, как сказано в предыдущем разделе, методы scanCatalog и addProdByKey создают массив ключевых слов (объектов типа String), а также поддерживают объект Hashtable, содержащий объект Vector, в котором хранятся ссылки на элементы Element, снабженные ключевыми словами.
    Можно было бы, конечно, организовать поиск таким образом: пользователь набирает какое-либо слово, характеризующее искомый товар, в специальной форме на HTML-странице, а потом осуществляется проверка, содержится ли оно в списке ключевых слов. Но мы организуем поиск иначе, с помощью раскрывающегося списка. Для этого используется тег HTML SELECT и список всех ключевых слов и фраз. Получившаяся страница представлена на рис. 3.1.
    Код HTML этой страницы (с небольшим количеством ключевых слов) представлен в листинге 3.8. Заметим, что помимо перечня ключевых слов мы включили в код скрытую переменную с именем action и значением keywordsearch.

    Листинг 3.8. Код HTML для создания раскрывающегося списка ключевых слов

    Select a KeyWord












    Организация поиска по ключевым словам



    Рис. З.1. Форма для выбора ключевого слова

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

    public void doKeywordSelect( PrintWriter out ){

    CatalogBean cb = new CatalogBeanO;

    cb.setHidden( "action","keywdsearch");

    out.println("

    Select a KeyWord

    ");

    out.print( cb.doKeywordSelect( alias ) );

    out.println("

    ") ; }

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



    За счет того что метод 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( "

    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("
    sb.append( hiddenNames.elementAt(i) );

    sb.append("\" value=\"");

    sb.append( hiddenVals.elementAt(i) );

    sb.append( "\" >\r\n");

    }

    }

    sb.append("\r\n");

    sb.append("\r\n" );

    sb.append("
    \r\n" );

    return sb.toString();

    }



    Ответ web-сервера

    Ответное сообщение web-сервера на запрос браузера также обязательно содержит заголовок. Заголовок начинается со строки состояния, в которой указан используемый протокол, численный код состояния и текстовая версия кода состояния. В следующих строках приводится дополнительная информация в формате ключевое слово: значение, затем следует одна пустая строка.
    В заголовке ответа обычно указываются тип и размер содержимого тела сообщения. Заголовок ответа также может содержать строки, которые устанавливают в браузере значения элементов cookie. Ниже приведен заголовок, полученный в ответ на запрос, показанный в листинге 3.1. После этого заголовка идет пустая строка, а затем — тело сообщения, содержащее код HTML:
    НТТР/1.0 200 ОК
    Server: Microsoft-PWS/2.0
    Date. Mon, 25 Sep 2000 14:15:55 GMT
    Content-Type, text/html
    Тело ответного сообщения сервера может быть каким угодно, от стандартной HTML-страницы до двоичных данных закодированного изображения (а также совокупностью фрагментов данных, представленных в любых других специализированных форматах). Заметим, что в предыдущем заголовке тип содержимого был указан как text/html.

    СОВЕТ

    Для более детального изучения HTML мы советуем обратиться на сайт www.piter.com, где вы найдете множество изданий по этой тематике.



    Пользовательские библиотеки тегов

    Удобным свойством интерфейса API для JavaServer Pages является возможность определять пользовательские библиотеки тегов. Это очень мощное средство, позволяющее задействовать специализированные инструментальные средства так же легко, как стандартные теги.
    Пользовательские теги задействуют интерфейсы и классы пакета javax.se- rlet. jsp.tagext. Пользовательские библиотеки тегов намного упрощают работу по созданию JSP-страниц.



    Простой пример сервлета

    Обычное приложение с сервлетами включает в себя класс, который расширяет класс HttpServlet и реализует методы, необходимые для обработки различных типов запросов, адресованных приложению. В простом примере, показанном в листинге 3.2, сервлет должен отвечать только на запросы методом GET, поэтому в нем реализован только метод doGET. Обратите внимание, что ответ записывается в объект PrintWriter с именем out, который получен из объекта HttpServletResponse.

    Листинг 3.2. Простой сервлет, обрабатывающий запрос GET (DateDemo.java)
    import java.io.*;
    import java.util.* ;
    import javax.servlet.*;
    import javax.servlet.http.*;
    public class DateDemo extends HttpServlet
    {
    public void doGet(HttpServletRequest req,
    HttpServletResponse resp)
    throws ServletException, IOException
    {
    resp.setContentType("text/html");
    PrintWriter out = resp.getWriter();
    String username = req.getParameter("uname");
    if( username == null ) username = "unknown person" ;
    out.println("");
    out.println(" Date Demo");
    out.println("");
    out.println("Hello " + username + "
    ");
    out.println("Date and time now: " + new Date().toString() + "
    ");
    out.println("");
    out.println("");
    out.close();
    }
    }
    В этом примере метод doGet пытается отыскать параметр с именем uname в объекте HttpServl etRequest, чтобы использовать его в ответе. Заметим также, что в сервлете задействован метод setContentType, для того чтобы установить тип содержимого ответа как text/htral.



    Роль JavaBeans

    Огромный успех языка Java обусловлен его простой архитектурой, основанной на компонентах JavaBeans. Хотя сначала планировалось использовать их как компоненты графического интерфейса, вскоре оказалось, что они весьма полезны и в неграфических приложениях. JavaBeans — это просто класс Java, который удовлетворяет следующим критериям:
    он должен быть открытым и реализовывать Seri al i zabl e;
    класс должен иметь конструктор без параметра;
    доступ к любым переменным, используемым другими классами, в классе JavaBeans осуществляется через методы setXxx и getXxx.
    Создавая классы и называя методы в соответствии с этими простыми условиями, можно частично автоматизировать конструирование различных приложений, используя готовые компоненты, особенно в технологии JavaServer Pages. Вы, вероятно, слышали название Enterprise JavaBeans в контексте разговоров о серверах web-приложений. Технология Enterprise JavaBeans значительно отличается от JavaBeans и является гораздо более сложной.


    Создание объектной модели документа для каталога товаров

    Как мы говорили в главе 1, исходное создание объектной модели документов на Java очень просто, поскольку вся работа выполняется анализатором, входящим в набор инструментальных средств. В листинге 3.4 показан фрагмент кода, который на основе документа XML строит объект org.w3c.dom.Document. Основная часть кода состоит из инструкций, перехватывающих различные синтаксические ошибки.

    Листинг 3.4. Пример синтаксического анализа документа (TheCatalog.java)
    ]
    import javax.xml.parsers.* ;
    import org.xml.sax.* ;
    import org.w3c.dom.* ;
    public class TheCatalog
    {
    org.w3c.dom.Document catDoc ;
    public TheCatalog( File f, TextArea msg, TextField status ){
    try {
    timestamp = f.lastModified();
    DocumentBuilderFactory dbf =
    DocumentBuilderFactory.newInstance ();
    // statements to configure the DocumentBuilder would go here
    DocumentBuilder db = dbf.newDocumentBuilder ();
    catDoc = db.parse( f );
    }catch(ParserConfigurationException pce){
    lastErr = pce.toString();
    System.out.println("constructor threw " + lastErr );
    }catch(SAXParseException spe ){
    StringBuffer sb = new StringBuffer( spe.toString() );
    sb.append("\n Line number: " + spe.getLineNumber());
    sb.append("\nColumn number: " + spe.getColumnNumber() );
    sb.append("\n Public ID: " + spe.getPublicId() );
    sb.append("\n System ID: " + spe.getSystemId() + "\n");
    lastErr = sb.toString();
    System.out.print( lastErr );
    }catch( SAXException se ){
    lastErr = se.toString();
    System.out.println("constructor threw " + lastErr );
    se.printStackTrace( System.out );
    }catch( IOException ie ){
    lastErr = ie.toString();
    System.out.println("constructor threw " + lastErr +
    " trying to read " + f.getAbsolutePath() );
    }
    }
    Структура данных DOM выстраивается в памяти в такую же иерархическую систему, какая была в документе XML. Объекты Java представляют различные части документа XML и связаны ссылками на соседние элементы, как показано на рис. 1.2 в главе 1. Программные интерфейсы объектов Java, представляющие различные части документа, определены в пакете org.w3c.dom. Каждая часть документа XML, включая корневой элемент, представлена в виде объекта, реализующего интерфейс, который является расширением фундаментального интерфейса Node.


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

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


    Встроенные переменные в JSP-страницах

    В табл. 3.7 перечислены встроенные переменные, которые доступны в JSP-страницах по умолчанию.

    Таблица 3.7. Встроенные переменные в JSP-страницах













    Имя переменной




    Тип




    Описание



    request


    Объект класса, являющегося подклассом javax.servlet.ServletRequest


    Представляет запрос пользователя


    response


    Объект класса, являющегося подклассом javax.servlet.ServletResponse


    Создает ответ на запрос


    pageContext


    Объект класса javax.servlet.jsp.PageContext


    Содержит атрибуты страницы


    session


    Объект класса javax.servlet.http.HttpSession


    Содержит произвольные переменные, связанные с данным сеансом


    application


    Объект класса javax.servlet.ServletContext


    Содержит атрибуты для всего приложения и влияет на интерпретацию некоторых других тегов


    out


    Объект класса javax.servlet.jsp.JspWriter


    Выходной поток для данного ответа


    config


    Объект класса javax.servlet.ServletConfig


    Содержит пары имя-значение для параметров инициализации сервлета и объект ServletContext


    page


    Ссылка на объект, синоним this


    Возвращает ссылку на сервлет


    exception


    Объект класса javax.lang.Throwable или одного из его подклассов


    Содержит только те страницы, которые обозначены в директиве страницы как ошибочные





    Взаимодействие по протоколу HTTP

    Консорциум W3C (www.w3.org) поддерживает протокол HTTP 1.1 (это модификация предыдущей версии, HTTP 1, у которой было много недостатков) в качестве текущего стандарта для web-серверов. Этот стандарт определяет требования к формату запросов браузера и ответов web-сервера.



    Запрос браузера

    Сообщение-запрос браузера, отсылаемое на сервер, начинается с заголовка, состоящего из одной или нескольких строк ASCII-символов, каждая из которых заканчивается символом crl f (carriage-return-line-feed — возврат каретки и перевод строки). Первая строка нужна для указания метода,, идентификатора URI (Uniform Resource Identifier — универсальный идентификатор ресурса) и индикатора используемой версии HTTP. Стандартными методами для протокола HTTP 1.1 являются OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE и CONNECT, но для коммерческих сайтов обычно используются методы GET и POST. После заголовка могут следовать дополнительные данные.
    В запросе на обычную HTML-страницу используется метод GET. Простые поисковые запросы также пересылаются методом GET, в то время как для отправки заполненных форм, например в приложении "корзина покупателя", обычно применяется метод POST. Практически разница между этими методами заключается в том, что в запросе по методу GET параметры присоединяются к строке заголовка, содержащей идентификатор URI, а в методе POST данные передаются в теле сообщения.
    Заголовок также содержит строки, дающие дополнительную информацию о типах данных, которые может принять данный браузер, о предпочтительном типе соединения и о типе и версии самого браузера. В листинге 3.1 показан пример запроса, который посылается методом POST после щелчка на кнопке Send (Отправить) на обычной HTML-странице, содержащей формы для заполнения их пользователем и скрытую переменную с именем action и значением showkeywords. Заметим, что строки Accept: и User-Agent: разбиты на две части, так как иначе они бы не поместились на странице.
    Листинг 3.1. Сообщение, пересылаемое браузером на сервер методом POST [Все представленные в книге тексты программ можно найти на сайте издательства по адресу www.piter.com. — Примеч. ред.]
    POST /servlet/cattest HTTP/1.1
    Accept: application/msword, application/vnd .ms-excel, image/gif, image/x-xbitmap, image/]peg, image/pjpeg, */*
    Referer: http://localhost/XmlEcommBook/CTestSnoop.html
    Accept-Language: en-us
    Content-Type: application/x-www-form-urlencoded
    Accept-Encoding: gzip, deflate
    User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)
    Host: localhost:9000
    Content-Length: 19
    Connection: Keep-Alive
    action=showkeywords


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

    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). Программисту следует очень внимательно относиться к выбору объектов, которые он собирается хранить как объекты сеанса. Помните, что вы не можете предсказать, когда пользователь вернется к данному сеансу. Поэтому не рекомендуется хранение таких объектов, как, например, объекты соединения с базами данных, которые требуют значительных системных ресурсов.





    Инициализация сервлета

    Как видно из листинга 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 = "
    \r\n" ;
    static String version = "1.03 Oct 17, 2000";
    static String cssLinkA = " // following is part of a web server URL for the style sheet
    static String cssLinkB = "XmlEcommBook/catalog/catalog.css" ;
    static String cssLinkC = "\" type=\"text/css\" media=\"screen\" >" ;
    static String resourcepath = "XmlEcommBook/catalog/" ;
    static String host = "http://localhost/";
    // these are servlet engine aliases
    static String servlet = "servlet/catalog" ;
    static String checkout = "servlet/checkout" ;
    // these are complete webserver paths
    static String cssLink = cssLinkA + host + cssLinkB + cssLinkC ;
    static String alias ; // for catalog servlet
    static String checkoutalias ;
    static String resources ; // for images, style sheets, etc
    String catPath = "e:\\scripts\\XMLgifts" ; // for xml
    String catName = "catalog.xml" ;
    Properties catProp = new Properties();
    public void init(ServletConfig config) throws ServletException
    { try {
    super.init(config);
    System.out.println("CatalogTestServ init called, version "

    + version );

    String tmp = config.getInitParameter("workdir");

    if( tmp != null ) catPath = tmp ;

    File f = new File( catPath, "catalog.properties");

    if( f.exists() && f.canRead() ){

    FileInputStream fis = new FileInputStream(f) ;

    catProp.load( fis );

    fis.close();

    tmp = catProp.getProperty("csspath");

    if( tmp != null ) cssLinkB = tmp;

    tmp = catProp.getProperty("host");

    if( tmp != null ) host = tmp ;

    tmp = catProp.getProperty("resourcepath" ) ;

    if( tmp != null ) resourcepath = tmp ;

    tmp = catProp.getProperty("catalogservlet");

    if( tmp != null ) servlet = tmp ;

    tmp = catProp.getProperty("checkoutservlet" );

    if( tmp != null ) checkout = tmp ;

    }

    else { System.out.println ("CatalogServ can't read catalog.properties");

    }

    resources = host + resourcepath ;

    alias = host + servlet ;

    checkoutalias = host + checkout ;

    System.out.println( "resources:" + resources );

    System.out.println("servlet: " + alias );

    System.out.println("checkout: " + checkoutalias );

    CatalogBean.setTheCatalog( catPath, catName );

    CatalogBean.setResourcePath( resources );

    }catch( Exception e ){

    System.out.println("CatalogTestServ init " + e );

    }

    }

    Заметим, что метод init вызывает два статических метода из класса CatalogBean. Вызов метода setTheCatal од необходим для считывания данных из файла XML, а метод setTheResoursePath устанавливает путь, который будет использоваться для нахождения таких ресурсов, как изображения товаров. Определения класса Gala- togBean вы найдете далее в этой главе в разделе "Класс CalatogBean".





    Интерфейс HttpSessionBindingListener

    Как вспомогательное средство для управления системными ресурсами, которые могут быть задействованы в сеансах, и как средство отладки в API сервлетов предусмотрены интерфейс HttpSessionBindingListener и класс HttpSessionBinding- Event. В этом интерфейсе определены два метода.
    void valueBoundCHttpSessionBindingEvent event). Когда объект, реализующий интерфейс HttpSessionBindingListener, присоединяется к сеансу HttpSession, вызывается данный метод. Параметр этого метода event передает двоякую информацию — имя, которое было использовано для присоединения объекта к сеансу, и идентификатор сеанса (объект типа String).
    void valueUnbound(HttpSessionBindingEvent event). Этот метод вызывается, когда объект должен быть удален из сеанса. Обычно это происходит, когда в результате простоя выполняется метод invalidate класса HttpSession и сеанс становится недействительным.
    В сервлете catalogServ мы демонстрируем использование этого интерфейса для выполнения простых операций по отладке.



    Использование класса carttistener

    Поскольку процессор сервлетов во многом определяет поведение объекта HttpSession, решение проблем отладки, связанных с сеансами, может оказаться непростым делом. API сервлетов предоставляет интерфейс HttpSessionBindingListener и класс HttpSessionBindingEvent для решения проблем отладки и для управления ресурсами, которые могут быть присоединены к объекту HttpSession.
    В листинге 4.13 показан простой пример использования этого интерфейса в классе CartLi stener, который является внутренним по отношению к классу Catalog- Serv. В этом примере мы просто записываем системное время присоединения объекта к объекту HttpSession, и затем, когда сеанс закрывается, печатаем время жизни объекта. Объект CartLi stener присоединяется к объекту ShoppigCart, когда он создается в первый раз методом doPost (см. листинг 4.5).

    Листинг 4.13. Внутренний класс CartListener (CatalogServ.java)
    class CartListener implements
    HttpSessionBindingListener {
    long created ;
    public void valueBound( HttpSessionBindingEvent evt ){
    created = System.currentTimeMillis();
    }
    public void valueUnbound( HttpSessionBindingEvent evt ){
    long del = System.currentTimeMillis() - created ;
    System.out.println( "Session lifetime: " + ( del / 1000 )
    + " seconds ");
    }
    }
    }



    Использование объектов Cartltem и ShoppingCart

    Как показано в листинге 4.14, CatalogBean создает объект Cartltem для некоторого товара на основе информации, содержащейся в экземпляре cat класса TheCatalog.
    Метод doCartLnst, приведенный в листинге 4 14, контролирует вывод всех объектов Cartltem, содержащихся в экземпляре класса ShoppingCart Форматирование таблицы осуществляется методом doShowCart класса CatalogServ Метод doCartList обеспечивает отображение каждого заказанного товара в отдельной строке таблицы HTML, а также отображение количества заказанных экземпляров (numeberOrdered) для каждого из них Типичная страница, показывающая содержимое корзины покупателя, представлена на рис 4 6

    Листинг 4.14. Методы CatalogBean, связанные с объектом ShoppingCart (CatalogBean.java)
    public CartItem createCartItem( String id ){
    Element pE = cat.getProductElByID( id );
    return new CartItem( pE );
    }
    // we are in a ..
    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("");
    String id = item.getId();
    out.print( pf.doListOutput( cat.getProductElByID(id)));
    out.print( "");
    out.print( "Number ordered: " + item.getNumberOrdered() );
    out.print( "\r\n");
    }
    }
    Использование объектов Cartltem и ShoppingCart

    Рис. 4.6. Отображение содержимого корзины покупателя
    Сложное форматирование при представлении полной информации о товаре (как показано на рис. 4 4 и 4 5) контролируется методом doFuTIItem, код которого приведен в листинге 4 15 Исходно мы строим таблицу с четырьмя или тремя ячейками в зависимости от наличия или отсутствия изображения товара В этой таблице содержатся сведения о товаре, его изображение (если оно есть), название и цена, а также количество заказанных экземпляров (если покупатель решил приобрести этот товар) Создается объект ProductFormatter, задающий стиль отображения всей этой информации о товаре, то есть стиль всех фрагментов текста, которые содержатся в документе XML, описывающем данный товар

    + " value=\"setcart\" >");

    if( ci.getNumberOrdered() == 0 ){

    out.print("");

    out.print("
    out.print(" Add this item to cart\" >");

    }

    else {

    out.print( "To change the number ordered,

    enter a new number here "

    + "and click the Change button.

    " );

    out.print("
    " value=\"" + ci.getNumberOrdered() + "\" >   ");

    out.print("
    out.print("Change\" >");

    }

    out.print("\r\n");

    }





    Использование объектов класса HttpSession

    Процессор сервлетов Java (который иногда называют контейнером сервлетов) управляет коллекцией объектов, реализующих интерфейс HttpSession. Наша программа может использовать один из этих объектов для хранения любой информации, необходимой при создании корзины покупателя. Каждый объект HttpSession имеет идентификатор (id), представляющий собой строку, которая создается одновременно с объектом и гарантированно является уникальной. Этот уникальный идентификатор создается одним из двух возможных методов.
    Мониторинг файлов cookie. Значение идентификатора отсылается на web- браузер пользователя как заголовок файла cookie, сгенерированного объектом HttpServl etResponse. Стандартное имя такого файла cookie — jsessionid, а значением может быть строка, подобная следующей: "97187996250188366", то есть просто случайное число, генерируемое процессором сервлетов. Это значение автоматически считывается при поступлении следующего запроса от того же пользователя, так что наша программа просто запрашивает объект HttpSession, принадлежащий данному пользователю.
    Перезапись URL. Значение идентификатора присоединяется к каждому URL-адресу, который посетитель сайта может использовать для отправки запроса на сервер. Поскольку сюда входят также URL-адреса, которые являются частью статических шаблонов или динамически генерируемого текста, программист должен приложить дополнительные усилия, чтобы гарантировать, что все URL-адреса будут переписаны.
    Чтобы не усложнять наш пример, в сервлете, реализующем корзину покупателя, мы будем использовать подход с мониторингом файлов cookie:
    HttpSession session = req.getSession(true);
    if( session.isNew() ){
    System.out.print("Session is new " + session.getld() ); }
    Этот простой фрагмент кода метода doPost формирует объект HttpSession для данного пользователя. Переменная req является объектом класса HttpServl etRequest. Булева постоянная true в вызове метода getSession указывает процессору сервлетов, что нужно создать новый объект сеанса, если в пользовательском запросе отсутствует соответствующий идентификатор. Вторая строка содержит метод isNew, позволяющий определить, является ли данный сеанс новым или клиент (браузер) уже участвовал в сеансе.
    В интерфейсе API для JSP-страниц переменная класса HttpSession называется session, поэтому желательно и в сервлетах для таких переменных использовать именно это имя. Теперь можно перейти к краткому обзору интерфейса API, который используется при работе с объектом HttpSession.



    Класс CatalogBean

    Как вы уже, вероятно, заметили при обсуждении методов класса CatalogServ, вся основная работа по созданию форматированного отображения информации о товарах выполняется методами класса CatalogBean. Эти методы форматирования выделены из сервлета в отдельный класс для того, чтобы упростить реализацию функций каталога в технологии JavaServer Pages. В этой главе мы за недостатком места не приводим JSP-версию каталога, а технологии JSP посвящена глава 5.
    Большинство методов класса CatalogBean, используемых в этой главе, остались такими же, как в главе 3. Наиболее значительные изменения касаются объектов ShoppingCart и Cartltem. Напомним, что в CatalogBean имеется статическая переменная для объекта TheCatalog, который управляет объектной моделью документа, созданной на основе каталога (файла XML). В обновленной версии CatalogBean мы сделали эту переменную закрытой (private) и предложили следующий метод доступа:
    private static TheCatalog cat ;
    static void setTheCatalog( String path, String name ){
    File f = new File( path, name );
    cat = new TheCatalog( f, null, null );
    cat.scanCatalog();
    }
    static TheCatalog getCat(){ return cat ; }
    Мы также добавили статическую переменную resoursePath и метод setResourse- Path, который задает значение этой переменной при инициализации сервлета:
    private static String resourcePath ; // используется для
    // изображений, звуковых
    // файлов и т.д.
    static void setResourcePath( String s ){ resourcePath = s ; }



    Класс CatalogServ

    Класс, который мы написали для отображения элементов каталога и манипулирования корзиной покупателя (классом ShoppingCart), называется CatalogServ. Здесь мы используем усовершенствованные версии классов для представления каталога сети, описанных в главе 3, и добавляем функции для организации корзины покупателя и мониторинга сеанса. Возможности сервлета CatalogServ перечислены ниже.
    Отображение компактных списков товаров каталога с использованием критерия отбора, в частности:

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

    добавление данного товара в корзину;
    изменение количества заказанных экземпляров.
    Отображение списка выбранных на текущий момент товаров.
    Для простоты будем считать, что заказ товара (добавление его в корзину и изменение количества заказанных экземпляров) может происходить только на странице с подробной информацией о данном товаре.
    Кроме того, наш сервлет не будет выполнять никаких других функций, свойственных обычному коммерческому сайту. Как показано на рис. 4.1, наш сервлет просто размещает текст "Your site navigation could go here" [Здесь может быть расположен интерфейс для навигации по вашему сайту. — Примеч. перев. ], в то время как в этом месте обычного коммерческого сайта, как правило, находятся логотипы и навигационный интерфейс.
    Класс CatalogServ

    Рис. 4.1. Отображение полного каталога с помощью класса CatalogServ
    Перед тем как углубляться в изучение исходного кода CatalogServ, рассмотрим некоторые другие представления, которые он генерирует. На рис. 4.2 показана страница с раскрывающимся списком ключевых слов. Это тот же самый список, который представлен на рис. 3.1 в главе 3, но сервлет CatalogServ добавляет в нижнюю часть страницы дополнительные ссылки:
    Full Catalog (Весь каталог);
    Books (Книги);
    CDs (Компакт-диски):
    Widgets (Приборы и устройства);

    Search (Поиск).

    Класс CatalogServ



    Рис. 4.2. Отображение раскрывающегося списка ключевых слов

    В табл. 4 2 представлен перечень команд отображения (значений параметра action), которые определяют ответ сервлета CatalogServ Команды, управляющие отображением, обычно модифицируются дополнительными параметрами



    Таблица 4.2. Команды, распознаваемые сервлетом CatalogServ



    Команда (параметр action)


    Дополнительные параметры


    Отображение
    showcatalog Параметр select = "all" Таблица со списком всех товаров (см. рис. 4.1)
    showcatalog Параметр select =одна из серий товаров (books, CDs, widgets) Таблица со списком товаров определенной серии (см. рис. 4.3)
    selectkeyword Параметр select = "all" Раскрывающийся список ключевых слов (см. рис. 4.2)
    showproduct Параметр id из формы Полная информация о товаре (см. рис. 4 4)
    keywdsearch Параметр keyword из формы Список товаров с этим ключевым словом
    setcart Параметры id и itemct из формы Полная информация о товаре с измененным количеством заказанных экземпляров (см. рис. 4.5)
    showcart Параметры отсутствуют Список всех товаров в корзине покупателя с указанием количества заказанных экземпляров (см. рис. 4.6)




    Класс ProductFormatter

    Мы добавили множество методов в класс ProductFormatter, описанный в главе 3. Вообще говоря, эти методы просто расширяют те возможности, которыми уже обладал этот класс. В листинге 4.16 показаны статические переменные, которые определяют два различных стиля. "Краткий" (short) стиль используется для отображения списков товаров (всего каталога или одной серии), а полный (full) стиль используется в методе doFullItem класса CatalogBean для отображения полной информации об одном товаре. Также мы создали коллекцию fieldHash, которая связывает названия товаров с целочисленными константами.

    Листинг 4.16. Статические переменные класса ProductFormatter (ProductFormatter.java)
    package com.XmlEcomBook.catalog;
    import java.util.* ;
    import java.io.* ;
    import org.xml.sax.* ;
    import org.w3c.dom.* ;
    public class ProductFormatter
    {
    static String brcrlf = "
    \r\n" ; // xhtml style br
    static String[] shortEl = { "prname", "price" // for product name
    };
    static String[] shortSt = { "ch3", "ch4"
    };
    // as used in doListOutput
    static String[] fullEl = { "prname",
    "author","artist","description",
    "price" // for product name
    };
    static String[] fullSt = { "ch3",
    "au1", "au1", "ch4", "ch4"
    };
    static Hashtable fieldHash ;
    // field names for lookup
    static String[] fields = { "id", "keywords",
    "prname", "price", "author", "artist", "description",
    "image", "caption", "quantity_in_stock", "onsale_date",
    "shipping_info"
    } ;
    static { fieldHash = new Hashtable() ;
    for( int i = 0 ; i < fields.length ; i++ ){
    fieldHash.put( fields[i], new Integer( i ) );
    }
    }
    В листинге 4.17 показано начало кода для методов и переменных экземпляра и конструктора ProductFormatter. Заметим, что конструктор задает формат в соответствии с переданной ему переменной frmt, которая может принимать значение "short" или "full".



    Листинг 4.17. Начало кода методов и переменных экземпляра (Product Formatter.java)

    String[] elem, style ;

    String resourcePath ;

    String aLink ;

    int linkN ;

    // when aLink is supplied, it should be something like

    // "/servlet/catalog?action=showproduct", then the doListOutput will build a

    // complete link adding &id=xxxxxx to attach to the first parameter

    public void setALink(String s, int pos ) { aLink = s ; linkN = pos ;}

    public void setResourcePath( String s ){ resourcePath = s ; }

    // throws exception if unknown format

    public ProductFormatter( String frmt ){

    if( frmt.equals("short")){

    elem = shortEl ; style = shortSt ;

    }

    else if( frmt.equals("full")){

    elem = fullEl ; style = fullSt ;

    }

    else { throw new IllegalArgumentException ("ProductFormatter: " + frmt );

    }

    }





    Классы Cartltem и ShoppingCart

    Минимальным требованием к объекту, представляющему товар в корзине покупателя, является наличие идентификатора данного товара и количества заказанных экземпляров. Название товара, его цену и другие сведения можно найти в каталоге — документе XML. Но мы решили не ограничиваться минимальными требованиями и добавили в Cartltem название товара, цену и информацию по доставке, как показано в листинге 4.1.
    Следует отметить несколько важных моментов, касающихся класса Cartltem. Во-первых, конструктор работает непосредственно с объектом El ement структуры DOM, представляющим данный товар. Это упрощает добавление различных дополнительных переменных в XML-каталог. Во-вторых, класс Cartltem реализует интерфейс Serializable. Это позволяет посылать коллекцию объектов Cartltem, представляющую собой список заказанных товаров, другой программе Java, используя сериализацию данных. Сериализация также требуется, если процессору сервлетов приходится хранить сеанс или пересылать его на другой сервер. Наконец, названия методов доступа, например getld и setNumberOrdered, соответствуют соглашению об именах, принятому в JavaBeans, чтобы упростить использование объекта Cartltem в коде JSP-страницы.
    Листинг 4.1. Класс Cartltem (cartltem.java) [Все представленные в книге тексты программ можно найти на сайте издательства по адресу www.piter.com. — Примеч. ред. ]
    package com.XmlEcomBook.catalog;
    import java.util.* ;
    import java.io.* ;
    import org.xml.sax.* ;
    import org.w3c.dom.* ;
    public class CartItem implements java.io.Serializable
    { // be sure to change this if substantive variables change
    static final long serialVersionUID = 3260689382642549142L;
    // these are set from the constructor
    private String id ; // from product element
    private String name ; // from name element
    private String price ; // from price element
    private String shippingType ; // from shipping_info element
    private String shippingValue ; // may be null if type is special
    // these may change
    private int numberOrdered ; // changes

    public String getId(){ return id ;}

    public String getName(){ return name ; }

    public String getPrice() { return price ; }

    public String getShippingType() { return shippingType ; }

    public String getShippingValue() { return shippingValue ; }

    public int getNumberOrdered(){ return numberOrdered ; }

    public void setId(String s){ id = s ; }

    public void setName(String s){ name = s; }

    public void setPrice(String s){ price = s ;}

    public void setShippingType(String s ){shippingType = s ;}

    public void setShippingValue(String s) { shippingValue = s ;}

    public void setNumberOrdered( int n ){ numberOrdered = n ;

    System.out.println("setNumberOrdered " + n );

    }

    // needed for operation as a Bean

    public CartItem(){

    }

    // constructor uses a org.w3c.dom.Element

    public CartItem( Element pe ){

    id = pe.getAttribute("id");

    NodeList nl = pe.getElementsByTagName( "name" );

    name = nl.item(0).getFirstChild().getNodeValue() ;

    nl = pe.getElementsByTagName( "price" );

    price = nl.item(0).getFirstChild().getNodeValue() ;

    nl = pe.getElementsByTagName( "shipping_info" );

    Element ship = (Element) nl.item(0);

    shippingType = ship.getAttribute("type");

    shippingValue = ship.getAttribute("value"); // may be ""

    }

    // handy for debugging

    public String toString() {

    StringBuffer sb = new StringBuffer("CartItem name:");

    sb.append( name ); sb.append(" numberOrdered: ");

    sb.append( Integer.toString( numberOrdered ));

    return sb.toString();

    }

    }

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


    Заметим, что класс ShoppingCart реализует интерфейс Serializable, так что вся корзина (то есть объект класса Shoppi ngCart) может пересылаться между программами Java или записываться в файл посредством сериализации.



    Листинг 4.2. Начало кода класса ShoppingCart (ShoppingCart.java)

    package com.XmlEcomBook.catalog;

    import java.io.*;

    import java.util.* ;

    public class ShoppingCart implements java.io.Serializable

    { private Vector items ; // maintains order of selection of items

    private Hashtable itemsById ;

    public ShoppingCart(){

    items = new Vector();

    itemsById = new Hashtable();

    }

    // items vector may be empty

    public Vector getItems(){ return items ; }

    // returns CartItem for this id or null if not in list

    public CartItem getProdById(String s ){

    return (CartItem) itemsById.get( s );

    }

    // CartItem is assumed to be unique

    public int addItem( CartItem x ){

    items.addElement( x );

    itemsById.put( x.getId() , x );

    return items.size();

    }

    В листинге 4.3 показаны остальные методы класса ShoppingCart. Поскольку мы храним ссылки на объекты Cartltem в двух коллекциях, для удаления элемента из объекта HashTable применяется метод removeByld с указанием идентификатора товара и затем вызывается метод removeEl eraent вектора items.



    Листинг 4.3. Остальная часть кода класса ShoppingCart (ShoppingCart.java)

    // remove an item from the cart by product id

    public CartItem removeById( String s ){

    CartItem ret = (CartItem)itemsById.get( s );

    if( ret == null ) return null ;

    itemsById.remove(s); // remove by key

    items.removeElement( ret );

    return ret ;

    }

    // remove all CartItem for which the numberOrdered is zero

    // returns the count of items left

    public int removeEmptyItems(){

    Enumeration keys = itemsById.keys();

    while( keys.hasMoreElements()){

    String key = (String)keys.nextElement();

    CartItem ci = (CartItem)itemsById.get(key);

    if( ci.getNumberOrdered() == 0 ){

    removeById( key );

    }

    }

    return items.size();

    }

    // mainly for debugging

    public String toString()

    { StringBuffer sb = new StringBuffer( "ShoppingCart has " +

    items.size() + " items.\r\n" ) ;

    Enumeration e = items.elements();

    while( e.hasMoreElements()){

    sb.append("Item: ");

    sb.append( e.nextElement().toString() );

    sb.append("\r\n");

    }

    return sb.toString();

    }

    }



    Корзина покупателя на языке Java

    В этой главе мы создадим сервлет, функции которого ограничены отображением каталога и оформлением заказа на товары. В реальном коммерческом сайте эти функции составили бы лишь небольшую часть всех возможностей, предложенных пользователю. В нашем примере новый объект класса HttpSession создается, когда сервлет CatalogServ в первый раз получает запрос от данного пользователя. В реальном виртуальном магазине сеанс может быть создан в другой части сайта.
    Возможной "точкой входа" посетителя может быть просмотр всего каталога или списка товаров какой-либо одной серии или просмотр результатов поиска по ключевому слову. В любом случае, мы не создаем объект ShoppingCart до тех пор, пока пользователь не проявит достаточного интереса к товару, чтобы просмотреть его полное описание. В этот момент мы создаем объект Cartltem для просматриваемого товара непосредственно из его описания в документе XML и добавляем объект Cartltem в ShoppingCart, то есть в корзину.
    Пока пользователь просматривает информацию о различных товарах, мы создаем объекты Cartltem для каждого выбранного товара и удаляем из корзины те товары, которые он раздумал покупать. На любой странице пользователь имеет возможность вернуться к полному каталогу, найти товар по ключевому слову, просмотреть содержимое корзины или перейти к оплате товара. Теперь рассмотрим детали реализации описанной функциональности.



    Метод addText

    Метод addText, приведенный в листинге 4.20, вызывается некоторыми другими методами из класса ProductFormatter. Ему передается объект StringBuffer, в который добавляется текст. Параметр name определяет, какой именно текст требуется добавить, а элемент Element соответствует некоторому товару в каталоге. Целое число, являющееся значением параметра name, управляет точками перехода в инструкции switch.

    Листинг 4.20. Метод addText (ProductFormatter.java)
    // заметим, что в большинстве случаев нам нужно
    // значение узла
    private void addText(StringBuffer sb, String name, Element el ){
    Object obj = fieldHash.get( name );
    if( obj == null ){
    sb.append( "no " + name + " found " ); return ;
    }
    switch( ((Integer)obj).intValue()){
    case 0 : // "id",
    addID( sb, el ); break ;
    case 1 : // "keywords",
    case 2 : // "prname", product name
    addProductName( sb, el ); break ;
    case 3 : // "price"
    addPrice( sb, el ); break ;
    case 4 : // "author",
    addAuthor( sb, el ); break ;
    case 5 : // "artist",
    addArtist( sb, el ); break ;
    case 6 : // "description",
    addExtendedText( sb, el ); break ;
    case 7 : // "image",
    addImageTag( sb, el ); break ;
    case 8 : // "caption"
    addExtendedText( sb, el ); break ;
    case 9 : // "quantity_in_stock",
    case 10 : // "onsale_date"
    }
    }
    Различные методы, вызываемые методом addText, приведены в листингах 4.21 и 4.22. Эти методы извлекают тот или иной текст из элемента product и добавляют его в Stri ngBuffer.

    Листинг 4.21. Различные методы, вызываемые методом addText (ProductFormatter.java)
    // Элемент е - это товар
    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 or 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 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 Christoph Minwich

    // 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 . Ниже приводится пример из файла catalog.xml:


    src="images/covers/pi ants.gif">



    This is the cover from the

    first edition.






    Помимо создания тега , который вставляет в 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("
    // 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()

    // одним из вариантов стиля является

    // , простой текст

    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(""); addExtendedText( sb, e );

    sb.append("
    ");

    }

    else if( name.equals("italics")){

    sb.append("" ); addExtendedText( sb, e );

    sb.append("
    ");

    }

    else if( name.equals("bold")){

    sb.append("" ); addExtendedText( sb, e );

    sb.append("
    ");

    }

    else { addExtendedText( sb, e );

    }

    }

    public String toString()

    { StringBuffer sb = new StringBuffer("ProductFormatter ");

    return sb.toString();

    }

    }

    Метод doListOutput

    Метод doListOutput (листинг 4.19) вызывается из CatalogBean для создания объекта типа String, содержащего форматированные данные по отдельному товару. Этот метод используется при конструировании таблицы, подобной приведенной на рис. 4.3. Обратите внимание, что мы создаем объект StringBuffer для построения строки (объекта String), поскольку добавление новых фрагментов в StringBuffer гораздо эффективнее, чем конкатенация (объединение) объектов String. Также заметим, что если методу передается переменная aLink, то текст, соответствующий элементу с индексом linkN, отображается в виде гипертекстовой ссылки.

    Листинг 4.19. Метод doListOutput (ProductFormatter.java)
    // создается строка с данными об отдельном товаре,
    // используемая во многих листингах
    // внешний вид определяется содержимым elem style
    // обычно добавляется ссылка на более подробную
    // информацию
    public String doListOutput( Element el ){
    StringBuffer sb = new StringBuffer( );
    String pid = null ;
    if( aLink != null ){
    pid = "&id=" + el.getAttribute("id") ;
    }
    for( int i = 0 ; i < elem.length ; i++ ){
    if( i == linkN && pid != null ){
    sb.append( " 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( "
    ");
    }
    else {
    sb.append( " sb.append( style[i] ); sb.append("\">");
    addText( sb, elem[i], el );
    sb.append( "
    ");
    }
    }
    return sb.toString();
    } // end doListOutput



    Метод doPageEnd

    В этом примере метод doPageEnd (листинг 4.6) просто создает набор активных ссылок в нижней части страницы. Ссылки на различные варианты представления каталога присутствуют всегда, но некоторые ссылки, связанные с корзиной покупателя, появляются, только если в корзину добавлены какие-либо товары. Обратите внимание, мы используем метод removeEmptyElements, чтобы гарантировать, что переменная nitem правильно отражает содержимое корзины.

    Листинг 4.6. Метод doPageEnd (CatalogServ.java)
    public void doPageEnd( HttpServletRequest req, HttpServletResponse resp,
    PrintWriter out, HttpSession session ){
    ShoppingCart cart = (ShoppingCart)session.getValue("cart");
    String a1 = "");
    if( cart != null &&
    (nitem = cart.removeEmptyItems()) > 0 ){
    // out.print( cart.toString()); // debugging
    out.print( brcrlf );
    out.print( a1 +"showcart\" > Show Cart (" + nitem +
    " items)
      " );
    out.print(" "?action=initial\" >Checkout Now   \r\n");
    }
    out.println( a1 + "showcatalog&select=all\" > Full Catalog   ");
    String[] prodL = CatalogBean.getCat().getProductLineNames();
    for( int i = 0 ; i < prodL.length ; i++ ){
    out.print( a1 + "showcatalog&select=" + prodL[i] + "\" >");
    out.println( " " + prodL[i] + "   ");
    }
    out.print( a1 + "selectkeyword&select=all\" > Search " );
    out.print("
    \r\n");
    out.println("
    " + version + "
    \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

    price ea = $12.99

    Этот код делает название товара активной ссылкой, которая отсылает параметры 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("

    " + line + " Catalog

    ");

    out.println("");

    out.println("" + "");

    out.println("
    " + line + "
    ");

    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("
    ");

    }

    Метод doPageMid



    Рис. 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("

    Selected by " + keyword + " Catalog

    ");

    out.println("");

    out.println("" + "");

    out.println("
    " + keyword + "
    ");

    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("
    ");

    }





    Методы 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("");
    out.println("");
    out.close();
    }
    private void outputHead( PrintWriter out ){
    out.println("");
    out.println(" Catalog Information");
    out.println( cssLink );
    out.println("\r\n");
    }
    // compose and output all material at the top of the page
    public void doPageTop( HttpServletRequest req, HttpServletResponse resp,
    PrintWriter out, HttpSession session ){
    out.print("

    XMLgifts

    ");
    out.print("

    Your Site Navigation Could Go Here

    \r\n");
    }
    В этом примере метод doPageTop очень прост, но в реальном коммерческом сайте этот метод можно использовать для отображения средств навигации по сайту и выполнения других функций.



    Методы, вызываемые методом doFullItem

    Теперь рассмотрим методы, которые используются для полного описания товара. Эти методы, приведенные в листинге 4.18, вызываются методом doFullItem класса CatalogBean.

    Листинг 4.18. Методы, используемые для полного описания товара (Product Formatter.java)
    public String doImageTag( Element el ){
    NodeList nl = el.getElementsByTagName( "image" );
    int ct = nl.getLength();
    if( ct == 0 ) { return null ;
    }
    Element img = (Element)nl.item(0);
    StringBuffer sb = new StringBuffer( );
    addText( sb, "image", img );
    return sb.toString();
    }
    // element is the complete product
    public String doProdName( Element el ){
    NodeList nl = el.getElementsByTagName( "name" );
    if( nl.getLength() == 0 ) return "";
    StringBuffer sb = new StringBuffer( );
    sb.append( " sb.append( "ch3" ); sb.append("\">");
    addText( sb, "prname", (Element)nl.item(0) );
    sb.append( "

    ");
    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("" );
    if( ct == 1 ) sb.append( "Artist: " );
    else sb.append("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("
    ");
    }
    ct = unl.getLength();
    if( ct > 0 ){
    sb.append("" );
    if( ct == 1 ) sb.append( "Author: " );
    else sb.append("Authors: ");

    for( i = 0 ; i < unl.getLength() ; i++ ){

    addText( sb, "author",(Element) unl.item(i) );

    if( ct > 1 && ( i + 1) < ct ) sb.append(", ");

    }

    sb.append("
    ");

    }

    return sb.toString();

    }

    // element is the complete product

    public String doDescription( Element el ){

    NodeList nl = el.getElementsByTagName( "description" );

    if( nl.getLength() == 0 ) return "No Description Available";

    StringBuffer sb = new StringBuffer( );

    addText( sb, "description",(Element) nl.item(0) );

    return sb.toString();

    }

    public String doPrice( Element el ){

    NodeList nl = el.getElementsByTagName( "price" );

    if( nl.getLength() == 0 ) return "Contact XMLgifts";

    StringBuffer sb = new StringBuffer( );

    addText( sb, "price", el );

    return sb.toString();

    }





    Отображение полной информации о товаре

    Метод doShowProduct класса Catal ogServ отвечает за отображение полной информации об одном конкретном товаре. Существует несколько вариантов формата этого отображения в зависимости от наличия в X ML-каталoгe ссылки на изображение этого товара и от того, находится ли уже данный товар в корзине покупателя.
    На рис. 4.4 приведено типичное представление, сгенерированное для товара, которого еще нет в корзине покупателя и для которого имеется изображение. Кнопка, расположенная в правом нижнем углу, предназначена для добавления в корзину этого товара. При щелчке на этой кнопке генерируется запрос к сервле- ту, параметр action принимает значение setcart, а параметр itemct — значение 1, в результате в корзине оказывается один экземпляр данного товара.
    Отображение полной информации о товаре

    Рис. 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("\r\n");

    cb.doFullItem( id, out, cart, alias );

    out.print("
    \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("");

    cb.doCartList( out, cart );

    out.print("
    \r\n");

    }





    Проблема корзины покупателя

    Для посетителей виртуального магазина интуитивно понятна аналогия "корзины покупателя" (shopping cart). Поэтому им кажется, что все должно происходить так же, как и в реальном магазине, где корзина, в которую они складывают выбранные товары, остается с ними в течение всего времени посещения магазина, пока они не подошли к кассе и не заплатили за покупки. К сожалению, то, что посетителю виртуального магазина представляется как протяженный во времени единый процесс просматривания информации, для web-сервера является последовательностью не связанных друг с другом запросов, поступающих от браузера, и высылаемых ему ответов. Так получается потому, что основные HTTP- протоколы неустойчивы, то есть не позволяют определить, что очередной запрос должен быть связан с предыдущим, поскольку оба запроса поступают от одного пользователя.
    Задача сохранения информации в течение одного сеанса (то есть в течение времени посещения данным пользователем данного сайта) обычно называется задачей сеансового мониторинга (session-tracking). Существуют три способа решения этой задачи: файлы cookie, скрытые поля (переменные) формы и перезапись URL.
    Файл cookie — это фрагмент текстовой информации, который ассоциируется браузером с адресом конкретного web-сайта Web-сервер посылает данные (cookie) вместе с ответом, а web-браузер хранит полученный текст. Каждый раз, когда web-браузер посылает запрос этому же web-серверу, файл cookie присоединяется к одному из заголовков сообщения, а сервер отыскивает и интерпретирует cookie. Текст файла cookie имеет хорошо знакомую вам форму атрибут = значение, похожую на имена и значения атрибутов XML.
    Очень полезное свойство файла cookie заключается в том, что программист имеет возможность контролировать время жизни этого фрагмента данных. Вы можете создать файл cookie, который будет существовать только в течение одного посещения сайта пользователем, в течение фиксированного промежутка времени или иметь неограниченный срок действия. Коммерческие web-сайты часто используют файлы cookie с длительным сроком действия для того, чтобы запоминать пользователей, часто посещающих данный сайт.

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

    При использовании скрытых полей формы для мониторинга сеанса ваша программа должна заносить скрытые данные в каждую форму или ссылку на каждой HTML-странице. Например, если нам нужно отследить значение переменной customer Id, в каждой форме должен содержаться следующий элемент:



    Кроме того, каждая ссылка должна включать в себя соответствующий фрагмент:

    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. Этот класс позволяет процессору сервлетов детально управлять мониторингом сеанса с минимальным участием программиста.





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

    Безопасность

    Безопасность — очень важный фактор для любого коммерческого 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-страница Approved

    Эта страница вызывается из сервлета SubmitOrder, когда заказ получает подтверждение поставщика услуг по обработке. Эта простая страница включает в себя идентификатор заказа (листинг 5.26); ее можно использовать как квитанцию и при необходимости распечатать данные заказа. Так как у нас имеется объект Order с полной информацией о заказе, в страницу Approved можно включить помимо идентификатора и другие характеристики заказа.

    Листинг 5.26. JSP-страница Approved (Approved.jsp)
    <%@ page import="com.XmlEcomBook.Chap05.*" %>

    Approved Order


    Your order has been approved.
    Your order number is:





    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.*" %>



    <% CustomerInfo cust = order.getCustomerInfo();
    cust.setCreditInfo( creditInfo );
    %>
    Код HTML этой страницы вновь отображает все введенные клиентом данные. Это позволяет клиенту проверить всю введенную им информацию до того, как она будет окончательно отправлена на сервер поставщика для получения подтверждения. Для этого берутся объекты Customerlnfo и Fullfilment, связанные с данным сеансом, и с помощью элементов jsp:getProperty из них извлекаются различные параметры заказа, которые затем отображаются на странице. Внизу страницы располагается кнопка, которая позволяет клиенту отправить данные на сервер, если он не обнаружил ошибок. Чтобы исправить неверно введенные данные, клиент может воспользоваться кнопкой Back (Назад) в окне браузера. Листинг 5.23 содержит код для отображения и проверки введенных клиентом данных.

    Листинг 5.23. Отображение информации для ее подтверждения клиентом (Confirmlnfo.jsp)

    Confirm Info




    Verify the information you entered:


    Name:








    Address:








    ,








    Email:


    Phone Number:






    Credit Card Type :
    property="creditCardType" />



    Credit Card Number:
    property="creditCardNumber" />



    Expiration Date :
    property="expirationDate" />







    Shipper:
    property="shipper" />



    Class :
    property="shippingClass" />





    Press the back button on your browser to correct any information.















    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-странице сначала задается объект Fullfilment, связанный с данным сеансом и содержащийся в объекте Order. Также создается объект ShippingCalculator, который используется для подсчета выбранного способа доставки. После того как знак $ убран из строки с указанием цены, Shippinglnfo преобразуется к типу double и используется для вызова метода setCostToCustomer объекта Shippinglnfo (листинг 5.20).

    Листинг 5.20. Определение стоимости доставки (Creditlnfo.jsp)

    <% 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)



    Credit Card Information



    Your order price










    Items<%=order.getTotalItemPrice()%>
    Shipping<%=price%>
    Total <%=order.getOrderTotal()%>


    Please enter your credit card information:





    Credit Card Type:



    Visa





    Master Card





    American Express





    Discover






    Credit Card Number:


    Expiration Date:














    JSP-страница Declined

    JSP-страница Declined формируется, если по какой-либо причине кредитная карта клиента не прошла проверку поставщика. Страница генерирует сообщение, в котором указывается эта причина.

    Листинг 5.27. JSP-страница Declined (Declined.jsp)
    <%@ page import="com.XmlEcomBook.Chap05.*" %>

    Credit Card Declined


    Your credit card was declined.

    The reason given was:
    <% Authorization auth = order.getAuthorization();
    if( auth != null ) {
    out.println( auth.getReason() );
    }
    %>





    JSP-страница OrderDateSelector

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

    Листинг 5.28. JSP-страница OrderDateSelector (OrderDateSelector.jsp)
    <%@ page import="java.util.*" %>

    0rder Date Selector

    <% 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 ); %>
    Select a date: ">









    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

    Листинг 5.29. JSP-страница SelectOrder (SelectOrder.jsp)
    <%@ page import="java.io.*" %>

    Select Order

    <%
    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 );
    %>

    <%= orderNum %>


    <% }
    }
    %>





    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.*" %>




    <% order.setCustomerInfo( custInfo );
    order.setItems( theorder );
    %>
    Затем JSP-страница выдает код HTML, который позволяет пользователю выбрать один из предложенных способов доставки. В этом коде используется объект ShippingCalculator, рассмотренный нами выше в этой главе. Скриптлет, встроенный в код HTML, создает объект ShippingCalculator, а затем с помощью метода getTypes предлагает несколько способов доставки. На этом этапе создается ряд переключателей (radio buttons), по одному на каждый из указанных способов. Это делается с помощью цикла for. Для каждого способа создается элемент input типа radio. Атрибут value (значение) элемента устанавливается с помощью выражения JSP "". Затем такое же выражение используется для отображения этого значения на странице. Другой скриптлет обеспечивает получение данных о стоимости того или иного способа доставки с помощью метода getPrice объекта ShippingCalculator, как показано в листинге 5.18.



    Листинг 5.18. Код HTML, который формируется в JSP-странице Shippinglnfo (Shippinglnfo.jsp)





    Shipping Info







    Select a Shipper and Class:




    <% ShippingCalculator calc = new ShippingCalculator( order );

    String[] types = calc.getTypes();

    for( int i = 0; i < types.length; i++ ) {

    %>



    <% } %>

    <%=types[i] %> <%=calc.getPrice(types[i]) %>














    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.*"
    %>

    Order

    <%
    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" );
    %>

    Order #<%=id%>


    Items




    <% NodeList items = order.getElementsByTagName( "item" );
    int numItems = items.getLength();
    for( int i = 0;i < numItems; i++ ) {
    Element item = (Element)items.item( i );
    %>







    <% String priceString = item.getAttribute( "price" );

    priceString = priceString.replace( '$', ' ' );

    price += Double.parseDouble( priceString ); %>



    <% }%>

    ItemDescriptionQuantity Price
    <%=item.getAttribute( "id" )%> <%=item.getFirstChild().getNodeValue()%> <%=item.getAttribute( "quantity" )%> <%=item.getAttribute( "price" )%>


    <% 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);

    %>

    Customer Information



    Name:

    <%=firstName.getFirstChild().getNodeValue()%>

    <%=lastName.getFirstChild().getNodeValue()%>



    Address:


    <%=address1.getFirstChild().getNodeValue()%>


    <%=address2.getFirstChild().getNodeValue()%>


    <%=city.getFirstChild().getNodeValue()%>

    <%=state.getFirstChild().getNodeValue()%>

    <%=zip.getFirstChild().getNodeValue()%>



    Email:<%=email.getFirstChild().getNodeValue()%>


    Phone:<%=phone.getFirstChild().getNodeValue()%>


    <% 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);

    %>

    Credit Card Information



    Type:<%=type.getFirstChild().getNodeValue()%>


    Number:<%=number.getFirstChild().getNodeValue()%>


    Expiration Date:<%=exp.getFirstChild().getNodeValue()%>


    <% 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);

    %>

    Authorization Information



    Auth Code:<%=auth_code.getFirstChild().getNodeValue()%>




    Approved:<%=approved%>


    Reason:<%=reason.getFirstChild().getNodeValue()%>


    <% 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)








    value="<%=email.getFirstChild().getNodeValue()%>" />




    value="<%=auth_code.getFirstChild().getNodeValue()%>" />



    Fulfillment Info



    Shipper:<%=shipper.getFirstChild().getNodeValue()%>


    Class:<%=clas.getFirstChild().getNodeValue()%>


    Cost:$<%=cost.getFirstChild().getNodeValue()%>


    Tracking #:

    <%if( trackingString.equals( "NO_TRACKING_NUMBER" ) ) { %>



    Date Sent:



    <%} else {%>

    <%=trackingString%>


    Date Sent:<%=dateSentString%>

    <%}%>




    Back to date selection









    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.*" %>



    Update Complete



    <% 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 );

    %>

    The fulfillment was updated with the new information.


    Back to date selection





    <%!

    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-сервер магазина. В идеале вся персональная информация о клиентах должна храниться в зашифрованном, безопасном источнике данных на изолированном сервере, к которому нет доступа из Интернета.

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

    Класс 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;
    }
    }



    Класс 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;
    }
    }



    Класс 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;
    }
    }



    Класс 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";
    Первый метод в этом классе используется для сообщения клиенту об отправке ему посылки с заказом. Хотя это подтверждение не является абсолютно необходимым, оно имеет большое значение, так как клиент, получив такое сообщение, будет уверен, что его заказ действительно выполняется, а также найдет ответы на возможные вопросы о доставке заказа. В этом методе после получения информации о пользователе из объекта класса Order мы вызываем служебный метод под названием


    Листинг 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;

    }

    }





    Класс 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;

    }

    }





    Класс 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)




    date CDATA #REQUIRED>




    quantity NMTOKEN #REQUIRED

    price CDATA #REQUIRED>


    city, state, zip, email, phone, credit_info )>




































    cost, tracking_number, date_sent)>











    В листинге 5.8 приведен метод, который создает документ XML, соответствующий этому определению DTD. Это примитивный, слишком прямолинейный метод, в котором просто перебираются все поля класса Order и для каждого поля выписываются элементы и атрибуты XML. Такой подход не всегда является оптимальным, так как изменения в классах, входящих в класс Order, приведут к необходимости изменения самого метода. Таким образом, теряются преимущества инкапсуляции, которую обеспечивает объектно-ориентированный подход к программированию. Было бы лучше рассматривать эти классы в единстве. Более удачный метод создания документа XML мы рассмотрим в главе 6.




    Листинг 5.8. Метод writeXML (Order.java)

    public void writeXML( Writer writer ) {

    try {

    writer.write( "" );

    writer.write( "
    + File.separator + "order.dtd'>" );

    writer.write( "
    writer.write( "date='" + date + "'>" );

    Enumeration enum = items.elements();

    while( enum.hasMoreElements() ) {

    CartItem item = (CartItem)enum.nextElement();

    writer.write( "
    writer.write( "quantity='" + item.getNumberOrdered() + "' ");

    writer.write( "price='" + item.getPrice() + "'>" );

    writer.write( item.getName() + "
    " );

    }

    writer.write( "\n" +

    customerInfo.getFirstName() + "
    " +

    "\n" + customerInfo.getLastName() + "" +

    "\n" + customerInfo.getAddress1() + "" +

    "\n" + customerInfo.getAddress2() + "" +

    "\n" + customerInfo.getCity() + "" +

    "\n" + customerInfo.getState() + "" +

    "\n" + customerInfo.getZip() + "" +

    "\n" + customerInfo.getEmail() + "" +

    "\n" + customerInfo.getPhoneNumber() + "" );

    CreditInfo credit = customerInfo.getCreditInfo();

    writer.write( "\n\n" +

    credit.getCreditCardNumber() + "
    " +

    "\n" + credit.getCreditCardType() + "" +

    "\n" + credit.getExpirationDate() +


    "
    \n
    \n
    " );

    writer.write( "\n" +"\n" +

    authorization.getReason() + "
    \n" +

    authorization.getAuthorizationCode() + "
    " +

    "
    " );

    writer.write( "\n\n" +

    "\n" + fulfillment.getShipper() + "" +

    "\n" + fulfillment.getShippingClass() + "" +

    "\n" + fulfillment.getCostToCustomer() + "" +

    "\n" + fulfillment.getTrackingNumber() +

    "
    \n" +

    fulfillment.getDateSent() + "
    \n
    " );

    writer.write( "\n
    " );

    }

    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;

    }

    }





    Класс 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";
    }
    }



    Класс 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 ) {
    }
    }



    Обновление информации о доставке

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



    Процесс оплаты

    Для коммерческих 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-сайт поставщика коммерческих услуг, что упрощает взаимодействие с ним.





    Сбор информации о заказе

    Когда покупатель отобрал товары, которые он собирается приобрести, следующим этапом является подсчет стоимости покупки и выполнение заказа. Необходимо получить определенную информацию о покупателе, в том числе его имя, адрес и номер телефона или адрес электронной почты (или установить какой- нибудь другой способ связи с ним). Также покупатель может выбрать один из способов доставки товара. Наконец, самое главное — покупатель должен сообщить номер своей кредитной карты, которой он будет оплачивать покупку.
    Мы начнем с изучения классов, которые будут использоваться в JSP-страни- це как компоненты JavaBean. Customerlnfo, Creditlnfo, Fulfillment, Authorization и Order — все это классы, которые содержат информацию, собранную в процессе оформления заказа.
    Следующие три класса, которые мы изучим, — TestPaymentAuthorizer, Ship- pi ngCal cul ator и Eraai 1 er — также используются в процессе оформления заказа. Затем мы рассмотрим HTML-страницу, сервлет и несколько JSP-страниц, с помощью которых осуществляется взаимодействие с пользователем при сборе необходимой информации.



    Сервлет 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 ) {

    }

    }

    }





    Страница Customerlnfo

    Теперь мы перейдем к классам, с помощью которых осуществляется получение всей информации от клиента и передача ее рассмотренным выше классам. В первую очередь — это HTML-страница Customerlnfo.html. На этой странице имеется форма, поля которой предназначены для введения информации пользователем и соответствуют полям класса Customerlnfo (листинг 5.16). Как будет видно на JSP-странице Shippinglnfo, это соответствие упрощает передачу данных из формы в класс.

    Листинг 5.16. Страница Customerlnfo (Customerlnfo.html)


    Customer Info




    First Name:
    Last Name:



    Address line 1:

    Address line 2:

    City:
    State:
    Zip:


    Email Address:


    Phone Number:









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

    Главная HTML-страница

    На главной HTML-странице пользователю предлагаются три возможности: добавить товар, редактировать товар или удалить товар. Существует также возможность отменить все изменения, сделанные пользователем в текущем сеансе, или сохранить эти изменения. Для всех этих операций вызывается один и тот же сервлет, но всякий раз с различными значениями скрытого поля с именем operation. В начале HTML-страницы (листинг 6.41) содержится ее заголовок и открывающий тег элемента body.
    Листинг 6.41. Начало HTML-страницы (main.html)

    Catalog Upkeep

    Каждая из пяти операций, предложенных пользователю, представлена специальной формой на странице. Первую операцию, Add Product (Добавить товар), иллюстрирует листинг 6.42. Для нового товара требуется уникальный идентификатор, поэтому нужно проверить все уже существующие идентификаторы, чтобы не получилось повторения. Также требуется указать серию товаров, к которой принадлежит новый товар. Атрибут action элемента form содержит имя сервлета, который вызывается, когда форма заполнена и отправлена на сервер. В данном случае мы вызываем сервлет Main. В следующей строке указывается заголовок для формы Add Product.
    Первый элемент этой формы является скрытым. Это значит, что пользователь не увидит его на экране своего компьютера. Он просто передается сервлету вместе со всеми остальными введенными данными. Этот элемент позволяет сервлету определить, что именно хотел сделать пользователь. Следующие две строки предназначены для того, чтобы пользователь мог ввести идентификатор товара и указать серию товаров, к которой он будет принадлежать. Последняя строка элемента form нужна для того, чтобы добавить кнопку, с помощью которой пользователь сможет отправить введенные данные на сервер.
    Листинг 6.42. Форма для добавления товара (main.html)

    Add Product:




    Product ID:

    Product Line:



    Формы Delete Product (Удалить товар) и Edit Product ( Редактировать товар) аналогичны форме Add Product, но в них требуется ввести только идентификатор товара. Для визуального разграничения этих форм между ними добавляется горизонтальная линия. Код для форм Delete Product и Edit Product приведен в листинге 6.43.

    Листинг 6.43. Формы Delete Product и Edit Product (main.html)






    Delete Product:





    Product ID:








    Edit Product:





    Product ID:





    Последние две операции — отмена и сохранение всех изменений, внесенных пользователем в течение данного сеанса, — иллюстрирует листинг 6.44. Сервлет и JSP-страница загружают данные XML из файла, когда пользователь начинает работу с каталогом. Форма Cancel All Changes (Отменить все изменения) отменяет все изменения, которые были сделаны с момента загрузки файла. Форма Save All Changes (Отменить все изменения) записывает в файл XML все изменения, сделанные пользователем. Визуально обе эти формы представляют собой просто кнопки, на которых может щелкнуть пользователь. В каждой форме имеется скрытый элемент input, который сообщает сервлету, какую из этих операций выбрал пользователь.

    Листинг 6.44. Формы Cancel All Changes и Save All Changes (main.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 ); %>
    Delete Product <%= pid %> deleted.
    Return to main page.


    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. Сначала нужно установить заголовок страницы в тегах и , после чего следует элемент form, содержащий остальную часть страницы. Элемент form вызывает сервлет с именем UpdateProduct, который мы обсудим в следующем разделе. Первые два элемента input этой формы — скрытые поля, содержащие информацию, необходимую для сервлета UpdateProduct. Это введенные пользователем идентификатор товара и серия товаров, к которой он относится. Название товара, серия и идентификатор вставляются в выходные данные JSP-страницы с помощью выражения (элемента сценария), которое начинается с символов <*=.

    Листинг 6.51. Начало кода элементов HTML-страницы (Edit.jsp)

    <%= name %>

    <%= name %> Product

    " />

    Далее начинается элемент table, который помогает выровнять строки, входящие в форму, как показано в листинге 6.52. Первый элемент, который вы отображаете, — это идентификатор товара. Он просто выводится в первой строке таблицы table. Название товара отображается в той же строке таблицы, что и его идентификатор, но в виде отдельного текстового поля, состоящего из одной строки, — этот тип для объекта input задается по умолчанию. Служебный метод notNull используется потому, что метод getName может выдать значение null, а мы хотели бы, чтобы в таких случаях отображалась пустая строка, а не строка "null". В следующей строке таблицы отображаются ключевые слова, характеризующие данный товар. Размер этого текстового поля установлен равным 40, потому что предполагается, что вводимая строка может быть несколько длиннее

    Листинг 6.52. Отображение названия товара и ключевых слов (Edit.jsp)



    Далее, у нас имеется два скриптлета, представленные в листинге 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)

    Product ID <%= product.getId() %> Name
    Keywords
    Price Discount
    Quantity in Stock
    On Sale Date (mm-dd-yyyy hh:mm:ss)
    Description



    Для отображения элементов Image и Clip, относящихся к данному товару, за- действуется новая таблица, задаваемая в листинге 6.55. Скриптлеты в данном случае аналогичны тем, которые использовались для отображения элементов Author и Artist. После отображения таблицы в нижней части страницы появляется кнопка Submit (Принять), на которой после завершения редактирования пользователь может щелкнуть для отправки содержимого формы на сервер.

    Листинг 6.55. Отображение элементов Image, Clip и кнопки Submit (Edit.jsp)

    Price Discount Quantity in Stock On Sale Date (mm-dd-yyyy hh:mm:ss) Description

    В нижней части этой 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 = "" + i + ")"; s += "Format"; s += "" ; s += "Source File"; s += "" ; s += ""; s += ""; s += "Height"; s += ""; s += "Width"; s += ""; s += ""; s += ""; s += "Caption"; s += ""; s += "\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 = "" + i + ")"; s += "Format"; s += "" ; s += "Source File"; s += "" ; s += ""; s += ""; s += "Title"; s += ""; s += ""; s += ""; s += "Length"; s += "" ; s += "Size"; s += ""; s += ""; s += ""; s += "Description"; s += "" ; s += ""; return s; }

    private String outputAuthor( String i, String author ) { String s = "Author"; s += " "; return s; }

    private String outputArtist( String i, String artist ) { String s = "Artist"; s += " "; return s; } %>



    Класс Catalog

    Класс Catalog представляет корневой элемент DTD каталога, приведенного ниже:
    У этого элемента нет атрибутов и имеется только один многократно повторяемый дочерний элемент с именем 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( "" ); writer.indent(); Enumeration enum = productLines.elements(); while( enum.hasMoreElements() ) { ProductLine pl = (ProductLine)enum.nextElement(); pl.toXML( writer ); writer.writeln( "" ); } writer.unindent(); writer.writeln( "" ); } }





    Класс Clip

    Элемент clip очень похож на элемент image, что видно из его DTD:



    Все атрибуты элемента 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( "" ); writer.indent(); writer.writeln( "" + title + "" ); if( description != null ) writer.writeln( "" + description + "" ); writer.unindent(); writer.writeln( "" ); }

    }





    Класс DateTime

    Класс DateTime используется для представления даты. Он основан на следующем фрагменте DTD:

    Параметрическая сущность 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( "" ); writer.indent(); if( dayOfWeek != null ) writer.writeln( "" + dayOfWeek + "" ); if( month != null ) writer.writeln( "" + month + "" ); if( dayOfMonth != null ) writer.writeln( "" + dayOfMonth + "" ); if( year != null ) writer.writeln( "" + year + "" ); if( hour != null ) writer.writeln( "" + hour + "" ); if( minute != null ) writer.writeln( "" + minute + "" ); if( seconds != null ) writer.writeln( "" + seconds + "" ); writer.unindent(); writer.writeln( "" ); } }



    Класс Image

    Очередной класс, который мы рассмотрим, — это класс Image. Он основан на следующем определении DTD:

    #REQUIRED>

    Определенный здесь элемент 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( "" ); writer.indent(); if( caption != null ) writer.writeln( "" + caption + "" ); writer.unindent(); writer.writeln( "" ); } }





    Класс 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( "" ); writer.writeln( "" ); 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(" " + title ); out.println(""); out.println("" + text + "
    "); out.println("
    Return to main page."); out.println(""); } }



    Класс Product

    Элемент product сложнее, чем элементы catalog и product_line, которые мы уже рассматривали. Ниже приводится DTD для этого элемента:

    Элемент 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, как показано ниже:




    Вместо того чтобы создавать для этого элемента новый класс всего лишь с двумя полями, мы включим оба этих поля прямо в класс Product. Поле discount имеет тип Integer. В данном случае мы не можем задействовать примитивный тип int, так как элемент discount является необязательным и может отсутствовать. В такой ситуации нужно воспользоваться объектом null типа Integer.

    Другое отличие связано с полем description. В DTD это поле было представлено следующим образом:


    quote|link|general)*">
















    alt CDATA #IMPLIED>





    Это сложная модель содержимого, включающая большое количество дочерних элементов, которые могут повторяться в произвольном порядке, что приводит к следующим двум затруднениям. Первое связано с представлением поля 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( "" ); writer.indent(); writer.writeln( "" + name + "" ); for( int i = 0; i < authors.size(); i++ ) { writer.writeln( "" + authors.elementAt( i ) + "" ); } for( int i = 0; i < artists.size(); i++ ) { writer.writeln( "" + artists.elementAt( i ) + "" ); } writer.writeln( "" + description + "" ); writer.writeln( "" + price + "" ); writer.writeln( "" + quantityInStock + "" ); 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( "" ); }

    }





    Класс ProductLine

    Следующий класс, ProductLine, основан на определении DTD для элемента productline:

    Как и в элементе 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( "" ); writer.indent(); Enumeration enum = products.elements(); while( enum.hasMoreElements() ) { Product p = (Product)enum.nextElement(); p.toXML( writer ); writer.writeln( "" ); } writer.unindent(); writer.writeln( "" ); }

    }





    Класс 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 += ""; } 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 ); }

    }





    Класс 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; } }





    Код для представления информации пользователю

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


    Объекты данных

    Иногда взаимодействие с документом XML на уровне анализатора оказывается непростым делом. Если документ имеет достаточно сложную структуру, то работа с каждым элементом и атрибутом по отдельности превращается в чреватое многочисленными ошибками кропотливое, длительное занятие. Так получается потому, что анализаторы DOM или SAX не знают о деталях устройства схем отдельных документов. Эти анализаторы общего назначения должны уметь читать любой документ независимо от используемого определения DTD. Было бы гораздо проще, если бы существовали объекты, имеющие такую же структуру, как и тот документ XML, который они анализируют.
    Процесс создания набора объектов, предназначенных для преобразования данных XML к более удобным объектам Java, называется связыванием данных (data binding). Связывание данных позволяет работать с исходными данными XML на уровне структуры документа, вместо того чтобы непосредственно обращаться к элементам и атрибутам. Когда набор объектов для связывания данных готов, использующий их программист может даже не подозревать, что основу этих объектов составляет XML.
    В этой главе показано, как вручную создать объекты для связывания данных из нашего XML-каталога, но эти объекты можно также генерировать автоматически. Для этой цели имеется множество инструментальных средств. Для создания кода эти программы считывают DTD, схему XML или образец документа XML. Обычно для каждого элемента XML создается свой класс, а поля этого класса отводятся под атрибуты элемента XML.
    В качестве примера приведем фрагмент DTD для описания адреса:


    lastname CDATA #IMPLIED>
    Инструмент связывания данных может создать следующий класс для элемента 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. Эти методы очень полезны при отладке, но не являются составной частью классов, поэтому не включены в описание программ, чтобы в этой главе вы сосредоточились на основной части кода.





    Общие принципы редактирования каталога

    Так как данные в нашем каталоге представлены в виде кода 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-страницы, которые будут отображать информацию для пользователя и обрабатывать введенную им информацию. При этом информация будет извлекаться из объектов данных и с помощью браузера отображаться в формах. После заполнения форм пользователем информация отправится обратно на сервер. На этом этапе также потребуется программа, которая получит эту информацию и обновит объекты данных.





    Сервлет 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( " Update Successful " ); out.println( "

    Update Succesful

    " ); out.println( "Return to main page " ); }

    Метод 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-каталога товаров небольшого сайта.

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

    Класс для создания файлов снимков

    Создание файлов снимков осуществляется классом 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 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, чтобы создать файл, у которого имеется открывающий и закрывающий теги , необходимые для создания корневого элемента. Имя этого файла получается добавлением символов 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( ""); write.println( ""); write.println( ""); String tmp = read.readLine(); int ct = 1 ; while( tmp != null ){ write.println( tmp ); tmp = read.readLine(); } read.close(); write.println(""); 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(); // by id

    public String tableStyle = "align=\"center\" border=\"3\" " ; public String lastErr = null ; public int resultCt = 0 ; String id ; // 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 ordered.addElement( quesv ); E = (Element)qnl.item(i); // Element is a 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, работа этого метода состоит из следующих этапов.

    Файл открывается как объект org.xml .InputSource.

    Создается анализатор в соответствии со строкой parserClass (см. листинг 7.26). Мы используем анализатор из пакета Sun, но вы можете заменить его на любой подходящий вам анализатор.

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


    Вызывается метод 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("

    " + ordered.size() + " Questions " + resultCt + " Responses

    "); Enumeration e = ordered.elements(); while( e.hasMoreElements() ){ Vector v = (Vector) e.nextElement(); String id = (String)v.firstElement(); out.println("

    Question: " + id + "

    "); out.println("" + qtext.get(id) + "
    " ) ; out.println(""); out.print(""); out.print(""); out.println(""); for( int i = 1 ; i < v.size(); i++ ){ Counter c = (Counter) v.elementAt(i); out.print(""); out.print("" ); out.println(""); } out.println("
    ValCountShort Option Text
    " + c.val + "" + c.count + "" + c.text + "


    "); } }


    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 ; //
    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, описанный в следующем разделе.





    Класс Interpreter

    В классе Interpreter инкапсулирована вся работа по созданию на основе документа XML форм для вопросов. Этот класс устроен так, чтобы быть максимально гибким в отношении способов анкетирования. Хотя в данной версии поддерживаются только два типа вопросов, используемый механизм может быть легко расширен для включения в анкету других типов вопросов.
    Статические методы и инструкции импорта для класса Interpreter показаны в листинге 7.9. Константы QMC и QMCM означают два допустимых в нашей версии типа вопросов: QMC — вопрос, допускающий выбор одного ответа из списка, а QMCM — вопрос, допускающий выбор нескольких ответов из списка. Рассмотрим в качестве примера код XML для вопроса о музыкальных предпочтениях, приведенный в листинге 7.8.

    Листинг 7.8. Начало блока вопросов в документе XML (customersurvey.xml)
    Please select all of the categories of CD that you would like to see in our catalog Classical music Country and Western The latest Pop Groups Current Rock Golden Oldies Rock Environmental Novelty and Humor
    Открывающий тег 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 = "
    \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 }


    Для того чтобы обеспечить некоторую гибкость в форматировании вопросов, предусмотрена возможность задавать каскадную таблицу стилей для всего документа и заменять определенный по умолчанию стиль для каждого блока. Метод writeHead, показанный в листинге 7.11, управляет выводом начала HTML-страницы и включает в себя ссылку на таблицу стилей, если она применяется. В этом листинге также показаны методы startForm и endForm. Заметим, что переменная quesid записывается в форму как скрытая переменная, которая впоследствии извлекается в методе doPostQ (листинг 7.16).



    Листинг 7.11. Методы для создания различных частей HTML-страницы (Interpreter.java)

    void writeHead( PrintWriter out ){ out.println(""); out.println(" " + title + ""); if( css.length() > 0 ){ out.println("" ); } out.println(""); }

    // assumes nowNOde is set to the first question // output form start and question text public void startForm(PrintWriter out ){ out.print(""); }

    // fills in hidden variable and button public void endForm( PrintWriter out, String id ){ out.print("
    " ); out.print("
    "); out.println("
    "); }





    Класс Recorder

    Класс Recorder отвечает за хранение ответов пользователя на каждый вопрос и за их запись в файл для дальнейшего анализа. Каждому пользователю отводится экземпляр класса Recorder, который связан с данным сеансом и записывает ответы только этого пользователя.
    Корневым элементом документа XML является тег Questionnaire, у которого имеется несколько атрибутов, используемых классом Recorder. Как показано в следующем примере тега Questionnaire, эти атрибуты называются title, author, date, method и file:

    Атрибуты 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 attributes */ 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("\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 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("
    \r\n"); pw.close(); } // end synchronized block } // recording format in xml /*


    */ 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(""); 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(""); } break ; default : sb.append("UNKNOWN TYPE"); } sb.append("\r\n\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













    Код сервлета управления опросом

    В листинге 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 = "
    \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("
    Servlet version: " + version + "
    "); out.println(""); out.println(""); out.close(); }

    // assumes response has been set to text/html private void errorMsg ( PrintWriter out, String msg, Exception ex ){ out.println(""); out.println(" QuestionnaireServ Output"); out.println(""); out.println("

    Error: " ); out.println( msg ); out.println("


    "); if( ex != null ){ ex.printStackTrace( out ); } out.println("
    "); out.println(" Please mail me the error message.
    "); footer( out ); }

    public String getServletInfo() { return "Administers a questionnaire"; }

    }





    Обеспечение конфиденциальности

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



    Обработка элемента 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 tag may have an altfile="filepathandname" // that replaces the default established in the file attribute of // the 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.
    "); } else { // use text from ... Element E = (Element)nl.item(0); // only one 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"); } }



    Общественные организации

    Многие считают, что частная информация подвергается такой же опасности при сборе данных, проводимых правительственными организациями, как и при опросах коммерческих организаций. Поэтому неудивительно, что существует несколько общественных организаций, деятельность которых связана с этой проблемой. Одной из таких организаций является 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 присваивает тем организациям, которые, по ее мнению, ведут наиболее агрессивную и нечестную политику в отношении использования частной информации.
    Знание потребностей и интересов своих покупателей существенно для успеха любого предприятия. Но для того, чтобы у посетителей вашего сайта не возникло никаких опасений за их персональную информацию, лучше всего ясно изложить им принципы вашей политики в этом отношении и всегда предоставлять им возможность отказаться от сообщения несущественной информации. Если ваш сайт ориентирован на детей, вам также следует убедиться, что принципы работы с персональной информацией соответствуют принятому законодательству.



    Определение последовательности вопросов

    На рис. 7.1 показаны возможные ветви сценария опроса. Существенно следующее: имеются блоки вопросов, в которых ветвление отсутствует и которые заканчиваются многовариантным вопросом — ответ на такой вопрос ведет к переходу по той или иной ветви. Многовариантный вопрос может приводить несколько ветвей к одному блоку (например, блоки С и D приводят к одному блоку Е) или к различным блокам (например, из блока А можно попасть, в зависимости от ответа на многовариантный вопрос, в блок В, С или D). Блоки либо заканчиваются многовариантными вопросами, либо являются завершающими и прекращают опрос. По достижении завершающего блока система записывает все ответы в файл. Система может быть устроена таким образом, чтобы каждому завершающему блоку соответствовал свой уникальный файл.
    Переводя эту диаграмму на язык сущностей XML, мы приходим к следующей структуре. В документе Questionnaire на первом уровне имеется элемент Intro (введение) и одна или несколько сущностей Block. Каждая сущность Block содержит одну или более сущностей Ques (вопросы) и может заканчиваться сущностью Terminal. В сущности Block имеется атрибут name, который используется для адресации ветвления, и атрибут type, значение которого равно terminal, если блок заканчивается тегом Terminal. Этот внешний уровень структуры системы опросов схематически описан в листинге 7.1 [Questionnaire — анкета, intro — введение (сокращение от introduction). — Примеч. перев. ].
    Листинг 7.1. Первый и второй уровень иерархии анкеты







    Определение последовательности вопросов

    Рис. 7.1. Возможные ветви сценария
    Каждый вопрос в блоке создается с помощью тега dues, который включает в себя текстовый фрагмент и два или более варианта выбора, создаваемых тегами Qopt. Атрибуты тегов dues и Qopt позволяют контролировать ветвление и способ представления вопросов. Самый простой способ увидеть, как все это устроено, — рассмотреть несколько вопросов, представленных в примере анкеты в следующем разделе.



    Отображение вопросов

    Метод 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 Метод genQuest в предыдущем листинге вызывает метод doOption (листинг 7.13) для каждого элемента . Если вы захотите добавить дополнительные типы ответов, например поле для ввода текста, вам потребуется модифицировать именно этот метод. В этом листинге также показан метод checkBl ockType, который используется для проверки атрибутов элемента Block.

    Листинг 7.13. Метод doOption (Interpretr.java)
    // opN is from node list of - create output // Option a. 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("" ); break ; case QMCM : out.print("" ); break ; } // now for the text out.println( content ); out.println( brcrlf ); }
    // look at the type and css attributes in 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)
    // has been set, we are in public void doIntro(PrintWriter out ){ writeHead( out ); nowBlock = blockNodeList.item(0); if( nowBlock == null ){ out.println("Error 1 setting up first question.
    "); return ; } if( setQnodeInBlock( 0 )== null ){ out.println("Error 2 setting up first question.
    "); return ; } checkBlockType( ); // sets the terminal flag out.println( getIntro() ); out.print("
    "); endForm( out, "intro" ); }



    Пример анкеты

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

    ПРИМЕЧАНИЕ

    Полная версия анкеты имеется на прилагаемом к книге компакт-диске. Тип первого вопроса (атрибут type) определен как QMC (Question Multiple Choice — вопрос, допускающий выбор одного ответа из списка); это вопрос, в качестве ответа на который пользователь должен выбрать один из предложенных вариантов.

    Листинг 7.2. Начало документа XML, определяющего анкету (customsurvey.xml)
    Welcome Customers
    We here at BuyStuff.com want to meet your every desire to buy STUFF. To that end we are greatly expanding our on-line catalog and we want to concentrate on STUFF you will want to buy as soon as you see it. Please help by completing this simple survey.
    ]]>
    Which of the following are you most interested in buying on-line? Books Cds Electronic goodies I am not interested in buying Stuff! Thanks for looking anyway! ]]>

    Элементы 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)
    Please select all of the book categories you would like to see in our catalog. Best Sellers of all types Science Fiction Fantasy Fiction History and Biography Computer Technology Business Related Thanks for answering the survey! ]]>



    Пример сервлета для просмотра результатов отчета

    Сервлет 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 = "
    \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(""); out.println(" QanalysisServ Output "); out.println(""); out.println("

    Select The Questionnaire XML File

    "); out.println("Found " + v.size() + " XML files" + brcrlf ); out.println(""); out.println(""); out.println("
    " ); out.println("
    "); out.println("
    " ); out.println(""); 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("

    Test: " + pQ.title +"

    " ); out.println("XML questionnaire file: " + source + "
    "); out.println("Author: " + pQ.author + " Dated: " + pQ.date + "
    "); out.println("The primary answer file is: " + pQ.primaryfile + "
    " ); out.println("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 Start
    "); createQList( out, source, files ); }


    Когда пользователь выберет один из файлов с результатами, переменная action принимает значение analyze. Как показано в листинге 7.34, это инициирует создание нового объекта TallyQues, который используется для формирования таблицы с результатами.

    Листинг 7.34. Метод doPost, продолжение (QanalysisServ.java)
    if( action.equals("analyze") ){ out.println("

    Analysis

    "); out.println("XML questionnaire file: " + source + "
    "); String ansXml = pQ.getAnsXml( ansfile ); out.println("Answer file: " + ansfile + "
    "); out.println("Processing file: " + ansXml + "
    "); TallyQues tQ = new TallyQues( doc ); // build with questions if( tQ.tallyAns( ansXml )== null ){ out.println("

    Error " + tQ.lastErr + "

    ") ; } 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("
    "); out.println("
    "); out.println("
    "); out.println(""); out.println("
    " ); out.println("

    "); }


    Мы почти завершили рассмотрение класса QanalysisServ! В листинге 7.36 показаны некоторые служебные методы, необходимые для форматирования выходных страниц и сообщений об ошибках.

    Листинг 7.36. Служебные методы в классе QanalysisServ (QanalysisServ.java)
    public void header( PrintWriter out ){ out.println(""); out.println(" QanalysisServ Output "); out.println(""); } public void footer( PrintWriter out ){ out.println("
    " + version + "
    "); out.println(""); out.println(""); out.close(); } // assumes response has been set to text/html private void errorMsg ( PrintWriter out, String msg, Exception ex ){ out.println(""); out.println(" QanalysisServ Output "); out.println(""); out.println("

    " ); out.println( msg ); out.println("


    "); if( ex != null ){ ex.printStackTrace( out ); } out.println("
    "); footer( out ); } }



    Промышленные стандарты

    Желание решить описанную проблему в области электронной коммерции без вмешательства правительства привело к созданию независимой некоммерческой инициативной организации под названием TRUSTe [Trust (англ.) — доверие. — Примеч. перев. ] (www.truste.org). Основными принципами TRUSTe являются следующие.
    Пользователь имеет право на осведомленность и осознанный выбор того, каким образом будет использоваться его персональная информация.
    Ни один из принципов соблюдения конфиденциальности не годится для всех случаев.
    Организация TRUSTe осуществляет проверку соблюдения принципов конфиденциальности, так же как, например, организация UL (United Laboratories — объединенные лаборатории) проводит проверки в области электротехнического оборудования- Организации — члены программы получают право на размещение на своих сайтах логотипа TRUSRe, если принципы обеспечения конфиденциальности, которым они следуют, удовлетворяют стандартам TRUSTe. Также эти организации должны заплатить членский взнос и пройти контрольную проверку. Членский взнос составляет сумму 299 долларов для компаний, чей годовой доход не превышает одного миллиона долларов, так что членство в программе TRUSTe вполне доступно.

    ПРИМЕЧАНИЕ

    Сайты, которые могут собирать информацию у детей в возрасте до 13 лет, должны удовлетворять еще более жестким требованиям в отношении соблюдения конфиденциальности. В настоящее время Конгресс США готовит новое законодательство в данной области.
    TRUSTe постоянно проводит наблюдения за теми сайтами, которые являются членами программы, и проверяет соблюдение ими заявленной практики обеспечения конфиденциальности. В процессе наблюдения, в частности, на сайт отправляется вымышленная персональная информация, а затем отслеживается ее использование. Кроме того, TRUSTe активно преследует те сайты, которые несанкционированно размещают логотип TRUSTe.
    Как для членов программы, так и для других организаций сайт www.truste.org представляет собой прекрасное место для знакомства с новостями в области охраны права пользователей на конфиденциальность, особенно в США. TRUSTe также сотрудничает со многими другими организациями, которые пытаются установить стандарты в сфере охраны частной информации пользователей.



    Сервлет управления опросом

    В этом разделе приводится полный код сервлета Java, который управляет опросом, основанным на приведенном в предыдущем разделе документе XML. Этот сервлет записывает результаты в форме, удобной для дальнейшего анализа. Также мы опишем служебный класс, который позволяет управлять объектами document в процессоре сервлетов, и сервлет для составления таблицы и отчета по результатам опроса.



    Служебная библиотека документа 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 и допускающий возможность изменения без перезапуска сервера, вы можете направлять опрос в то или иное русло в зависимости от мнений, которые высказывает клиент по ходу опроса.

    Создание системы опросов с помощью XML-сценария

    Простая, но эффективная форма сбора информации — это проведение опроса по сети. Поскольку язык XML предназначен, в частности, для определения структуры документов, а опрос осуществляется как раз с помощью четко структурированных документов, XML как нельзя лучше подходит для решения нашей задачи. Основанная на XML система опросов, которой посвящены все остальные разделы этой главы, отличается гибкостью. В этой системе конкретный пользователь не связывается со своими ответами; система просто собирает ответы всех пользователей вместе. В систему несложно внести изменения, которые позволили бы сохранять ответы каждого пользователя в базе данных, но в таком случае вам пришлось бы объяснять пользователю, что именно вы собираетесь делать с его персональной информацией.
    В этом разделе мы займемся разработкой обобщенной структуры XML для создания системы сетевых опросов. Мы начнем со списка критериев, которым должна отвечать система.
    Управление внешним видом. В идеале, мы хотели бы, чтобы дизайн-страницы с вопросами не нарушал общего стиля нашего web-сайта.
    Гибкость методов опроса. Мы должны иметь возможность применять различные методы опросов: от простых вопросов, на которые пользователь отвечает «да» или «нет», до выбора одного из предложенных ответов.
    Возможность ветвления. В одном и том же сценарии очередность вопросов должна зависеть от ответов пользователей. Например, если из ответа на некоторый вопрос понятно, что данный пользователь никогда не покупает музыкальные компакт-диски через Интернет, не имеет смысла расспрашивать его о музыкальных предпочтениях и нужно выбрать другую ветвь вопросов.
    Расширяемость. Если в систему потребуется добавить новую форму представления вопросов, необходимые изменения кода должны быть минимальными.
    Запись результатов. Результаты опроса каждого участника должны быть записаны полностью и не должны зависеть от результатов других участников. Это дает максимальную гибкость при анализе.



    Управление ветвлением опроса

    В листинге 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("This questionnaire has been terminated"); return ; } String action = req.getParameter("action"); String quesid = req.getParameter("quesid"); if( !action.equals("Next") ){ out.println("Unexpected state in Interpreter.doPost
    "); return ; } if( quesid.equals("intro") ){ // this calls for generating first question // doIntro already set nowNode to first 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 " ); } // end doPostQ





    Варианты анализа анкеты

    Поскольку формат записи результатов опроса таков, что ответы каждого пользователя сохраняются в виде отдельной записи, имеется много вариантов анализа этих ответов. Для наших целей годится самый простой способ — составление таблицы по каждому вопросу, в которой для каждого варианта ответа указано количество выбравших его пользователей. Задействованные для составления таблиц классы отделены от сервлета, отвечающего за представление опроса в сети, и могут быть использованы для автономного создания HTML-страниц.
    Первая проблема, которую предстоит решить, — это преобразование всех написанных сервлетом Questionnaire выходных файлов в формат, пригодный для анализа результатов опроса. Вспомним, что класс Recorder просто записывает теги , которые аккумулируются в выходном файле (или файлах). Нам нужно создать файл, в котором имелся бы корневой элемент. Фактически этот файл явился бы снимком (snapshot) собранных результатов опроса. Для этого программа, анализирующая результаты, должна выполнить следующие шаги.
    Получить объект org.w3c.dom.Document, содержащий сценарий опроса.
    Отыскать имена выходных файлов.
    Создать для каждого новый файл, объединяя корневые элементы с текущим содержимым выходного файла.



    Вспомогательные методы класса 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
    " ; 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() ; } }



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

    Добавление свежих новостей

    Важной особенностью этого приложения является возможность добавления новых сообщений без нарушения нормальной работы web-сайта Эту функцию иллюстрирует верхний правый угол рис. 8 1. Вместо того чтобы модифицировать DOM в памяти сервера, сервлет CompanyNewsServ записывает модифицированную версию исходного файла XML на диск. Этот обновленный файл сообщений будет автоматически загружен в очередной раз при вызове DOM из библиотеки DOMlibrary.



    Гибкость отображения

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



    Информация для управления сообщениями

    Поскольку вы или ваши служащие будут обновлять страницу новостей в режиме подключения к сети, было бы полезно отслеживать, кто какое сообщение написал. (Например, чтобы знать, кто должен получать нагоняй за допущенную ошибку.) Для этого используется атрибут элемента . Сервлет обновления сообщений, описанный в разделе "Добавление свежих новостей" этой главы, обладает простым механизмом контроля доступа, в котором используются имя автора и пароль; этот то самое имя автора, которое становится значением атрибута author.
    Чтобы создать документ HTML, в котором заголовок содержит ссылку на полную версию текста, нужно использовать уникальный идентификатор. Мы, например, выбрали простейший вариант: при создании каждого сообщения ему присваивается серийный номер, который и становится значением его атрибута id.



    Использование класса NewsFormatter

    В этом разделе рассматриваются два способа использования класса NewsFormatter: с сервлетом общего назначения TheNewsServ и с JSP-страницами.



    Элементы текстов сообщений

    Хотя в области искусственного интеллекта и понимания компьютером естественных языков сделаны большие успехи, никто не рассчитывает, что компьютер напишет хороший заголовок, проанализировав текст сообщения. Поэтому приходится согласиться с тем, что нужен человек, который для каждого способа представления новостей создаст отдельный заголовок. В сообщении обычно указывается дата, а иногда рядом еще и место, где произошло то событие, о котором идет речь, например: "Остин, Техас, 1 января 2000". Эта задача тоже должна выполняться человеком.
    Некоторые сообщения очень выигрывают, когда сопровождаются графикой, звуковыми клипами или ссылками на другие сайты, поэтому продумайте, как сконструировать документ XML, чтобы его можно было дополнить различными элементами, способствующими увеличению привлекательности страницы новостей. Мы решили, что было бы слишком сложно и неудобно снабжать систему показа новостей в нашем примере всеми возможными "украшениями". Поэтому мы будем хранить тексты сообщений для второго и третьего способов (краткое и полное сообщение), используя тег XML <[[CDATA...]]>.
    Поскольку анализаторы XML не пытаются анализировать текст, содержащийся внутри раздела СВАТА, в этот раздел вы можете поместить любую разметку HTML, не сбивая с толку анализатор.
    Элементы, содержащие дату, заголовок, краткое сообщение и полный текст, показаны в листинге 8.1.

    Листинг 8.1. Дата, заголовок, короткое и полное сообщения (thenews.xml)
    Austin, TX, Jun 14 2000
    Best Seller at a Great Price

    Dryer Lint Art
    at 50% off the retail price.]]>


    suitable for the novice and advances through easy
    stages tothe (literally)
    monumental
    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.]]>



    Другим аспектом гибкости является способность выборочно представлять сообщения в соответствии с темой, интересующей посетителя. Предполагая, что спектр возможных интересов посетителей сайта XMLGifts.com очень широк, мы хотим показать каждому посетителю те новости, которые связаны с его излюбленной темой, и в том месте сайта, куда он с наибольшей вероятностью заглянет. В такой структуре неизбежны перекрывающиеся области; например, книга о музыкальной группе может оказаться интересной как для покупателей книг, так и для покупателей музыкальных компакт-дисков. Следовательно, каждое сообщение должно быть снабжено одной или несколькими пометками, которые указывают, к каким тематическим категориям можно его отнести; а формат представления новостей должен допускать переключения между различными темами сообщений.

    Для того чтобы пометить сообщение и отнести его тем самым к определенной категории, мы можем использовать элемент или атрибут. Следуя советам, приведенным в разделе "Элементы или атрибуты?" главы 2, можно заключить, что в данном случае лучше использовать атрибуты, так как тема сообщения — это данные о содержимом элемента, и мы предполагаем, что количество тем сообщений будет ограниченным.





    Класс 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 ; // 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 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]; //
    Мы решили, что заголовки сообщений всегда будут форматироваться как маркированные списки (unordered lists) HTML. Это очень упрощает метод doHeadli - neNews, показанный в листинге 8.6.



    Листинг 8.6. Метод, форматирующий список заголовков новостей (NewsFormatter.java)

    // Headline always formatted as with link public void doHeadlineNews(PrintWriter out){ out.println( " " ); for( int i = skipNitems ; i < itemNodes.length ; i++ ){ if( i >= maxNitems ) break ; Node n = itemNodes[i]; // " ); out.print( nodeHash.get("head") ); out.println(""); } out.println(""); }

    Метод 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]; //
    Как показано в листинге 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]; // = 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]; //

    Метод findNodes, показанный в листинге 8.11, вызывается для каждого сообщения, которое должно быть помещено на страницу. Входной элемент Element — это узел Newsltem документа XML. Метод findNodes создает переменную nodeHash, которая позволяет другим методам извлекать дочерние элементы Newsltem, например , из коллекции 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)

    // " ); out.print("

    "); out.print( nodeHash.get("head") ); out.println("

    "); out.println(); }

    // formatting private void doNewsItemShort( PrintWriter out, String id ){ // note anchor to full item display out.print("" ); out.print("

    "); out.print( nodeHash.get("head") ); out.println("

    "); Element de = (Element)nodeHash.get("date"); out.print( de.getFirstChild() ); out.println("
    "); Element ne = (Element)nodeHash.get("short"); String wrk = ne.getFirstChild().getNodeValue().trim() ; if( !(wrk.startsWith("") || wrk.endsWith("/P>"))){ out.print("
    "); } itemsCount++ ; out.println(); }


    Как показано в листинге 8.13, метод doNewsItemLong форматирует текст заголовка с помощью тега

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



    Листинг 8.13. Метод doNewsItemLong выводит полную версию сообщения (NewsFormatter.java)

    // formatting private void doNewsItemLong( PrintWriter out ){ out.print("

    "); out.print( nodeHash.get("head") ); out.println("

    "); Element de = (Element)nodeHash.get("date"); out.print( de.getFirstChild() ); out.println("
    "); Element ne = (Element)nodeHash.get("long"); String wrk = ne.getFirstChild().getNodeValue().trim() ; if( !(wrk.startsWith("") || wrk.endsWith("/P>"))){ out.print("
    "); } itemsCount++ ; out.println(); }

    Наконец, в листинге 8.14 представлены два служебных метода. Метод setFor- matTempl ate отыскивает файл и считывает его строка за строкой. Предполагается, что в файле имеется строка, начинающаяся с текста "


    Листинг 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(""); int ct = rootNNM.getLength(); if( ct == 0 ){ out.println(""); } else { out.print(""); }


    Затем, как показано в листинге 8.30, пишется новый тег , за которым следует заголовок сообщения, указывается дата и приводятся краткая и полная версии текста сообщения. Для того чтобы записать старые элементы , вызывается метод writeNewsNode. После закрытия временного файла старый файл XML удаляется, а временный файл получает имя. Следующий раз, когда этот файл будет запрошен, класс DOMIibrary по изменившейся метке даты модификации файла (timestamp) определит, что нужно считывать новый файл.



    Листинг 8.30. Метод addltem, продолжение (NewsllpKeep.java)

    out.print(""); // end of out.println(" " + head.trim() + "" ); out.println("" + date.trim() + "" ); out.println(""); out.println(""); out.println(""); for( int i = 0 ; i < itemNodes.length ; i++ ){ writeNewsNode(out, (Element)itemNodes[i] ); } out.println("
    "); 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, который записывает отдельный элемент , показан в листинге 8.31.



    Листинг 8.31. Метод, который записывает отдельный элемент из DOM (NewsllpKeep.java)

    // write a "); 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(""); } else { out.print( chn.getNodeValue() ); } out.println(""); } } // loop over child nodes out.println(""); } }

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

    Код для сервлета 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 = "
    \r\n" ; static String defaultHead = "\r\n" + " Company News Servlet\r\n" + "\r\n" + "

    Here is the news

    \r\n" ; static String defaultFoot = "\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 = "

    News Servlet " + version + " properties: " + pversion + "
    \r\n" + "\r\n\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("

    Error: " ); out.println( msg ); out.println("


    "); if( ex != null ){ ex.printStackTrace( out ); } out.println("
    "); out.println("Please mail me the error message.
    "); footer( out ); }

    private void header(PrintWriter out ){ out.println(""); out.println(" Company News Servlet "); out.println(""); }

    private void footer(PrintWriter out ){ out.println("

    Company News Servlet " + version + " properties:
    " ); out.println(""); out.println(""); out.close(); }

    }





    Корневой элемент документа

    Мы отслеживаем некоторые параметры, используемые во всем файле, с помощью атрибутов, которые задаются в корневом элементе документа, Newsfile. Очередной атрибут id — это просто next id. Присваивание нового значения каждому следующему атрибуту nextid является обязанностью программы, добавляющей новые сообщения или, в случае редактирования в автономном режиме, автора элемента Newsitem.
    Корневой элемент также является подходящим местом для хранения атрибутов, связанных с различными заданными по умолчанию параметрами отображения. В приведенном ниже примере имеется только один такой атрибут, longtemplate, который идентифицирует заданный по умолчанию файл — шаблон HTML, используемый для форматирования сообщений.
    В листинге 8.2 показано, как используются все перечисленные теги.

    Листинг 8.2. Элемент с одним элементом (thenews.xml)




    Your Favorite Music Now Available
    Austin, Feb 1, 2000

    CD that has all the geeks singing,
    It's Dot Com Enough for He.]]>


    It's Dot Com Enough For He.
    now in stock!

    All those great songs created during breaks in all-night coding sessions - now recorded by top Silicon Valley garage bands on our private label. It's Dot Com Enough for Me 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.
    ]]>






    Минимальная нагрузка на сервер

    Поскольку мы надеемся, что посещаемость нашего сайта будет достаточно велика, мы хотим, чтобы отображение главной страницы выполнялось по возможности просто, то есть чтобы страница быстро загружалась, а ее генерирование не требовало больших затрат ресурсов со стороны сервера. Рассмотрим следующие альтернативные варианты отображения новостей.
    Статические страницы новостей (Static News Pages). Статические страницы быстро загружаются, и главная страница благодаря XML может формироваться заново при появлении нового сообщения. Однако использование статических страниц исключает возможность их индивидуальной настройки для постоянных посетителей.
    Страницы новостей, генерируемые сервлетами (Servlet-Generated News Pages). Сервлеты Java могут генерировать все, что потребуется; при этом предполагается, что они используют файлы с шаблонами HTML для регулировки многочисленных атрибутов внешнего вида сайта.
    JSP-страницы (JavaServer Pages). Преимущество JSP-страниц перед сервлетами заключается в том, что для изменения внешнего вида страницы web-дизайнеру не нужно уметь программировать на Java.
    При выборе методов обработки следует сделать выбор между моделями SAX и DOM, а также между анализаторами XML, проверяющими либо не проверяющими допустимость документа. Модель SAX подразумевает работу анализатора для доступа к каждой странице. При использовании модели DOM доступ к страницам осуществляется быстрее, но зато все приходится хранить в памяти. Предполагая, что количество новостей будет не больше нескольких сотен, затраты ресурсов на хранение всей модели DOM в памяти не окажутся слишком значительными, поэтому мы считаем, что в данном случае модель DOM является безусловно оптимальным вариантом.
    Мы не видим никаких причин в использовании анализатора, проверяющего допустимость документа. Так как новые сообщения будут создаваться автоматически, вероятность ошибки форматирования весьма невелика. Кроме того, вряд ли ошибки, связанные с недопустимостью документа, могут смутить пользователя.



    Пример JSP-страницы

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

    Листинг 8.19. Первая часть упрощенной JSP-страницы для отображения новостей (mockup.jsp)
    The XMLGifts News
    <%@ 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 ); } %>

    Чтобы не усложнять пример, мы жестко запрограммировали тему сообщений — музыкальные компакт-диски (листинг 8 20) Первый раз объект NewsFormatter используется для создания левого столбца таблицы, где расположены заголовки сообщений. Это делается в первую очередь, так как, когда тематика сообщений задана, объект NewsFormatter будет содержать только данные по сообщениям, соответствующим выбранной тематике

    Листинг 8.20. Продолжение JSP-страницы с выходными данными NewsFormatter (mockup.java)


    Various Corporate Navigation Links Go Here
    News Headlines
    <% // 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 ); %>
    <% nf.doNews( pw, "","", topic, "L", null, 0, 1 ); %>

    <%= "Recent news items about " + topic + "
    " %>
    <%
    /* 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 ); %>
    Repeat the Navigation links here for convenience
    ©2000 XMLGifts.comSM




    Простота ввода данных

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



    Расположение сообщений в зависимости от их новизны

    Самые свежие новости должны располагаться первыми. Так как документ XML автоматически сохраняет порядок следования элементов, новые элементы должны добавляться к началу документа. Более того, было бы неплохо предусмотреть возможность отображения только самых свежих новостей. Следовательно, нам нужен способ представления "возраста" сообщений.
    После долгих колебаний между многочисленными способами представления даты, которые позволили бы нам отображать только недавние сообщения, мы остановились на использовании простого целочисленного представления количества дней, прошедших с 1 января 1970 года. Для этого значение типа long, возвращаемое методом System.currentTimeMillisO, делится на количество миллисекунд в сутках, и полученное число становится значением атрибута timestamp тега News item. Альтернативные варианты — использование классов Java DateFormat или Calendar — были отвергнуты, так как они подразумевают создание большого количества объектов, а мы хотим, чтобы показ новостей создавал минимальную нагрузку на сервер.



    Разработка системы показа новостей

    Мы хотим предложить посетителям нашего web-сайта последние новости фирмы в компактном и удобном для чтения формате. Помните, что обычно в вашем распоряжении имеется всего лишь несколько секунд, чтобы привлечь внимание к вашему сайту случайного посетителя, который просто "прогуливается" по сети. Мы хотим, чтобы посетителю было понятно, как найти на сайте подробную информацию, если его что-то заинтересовало, как войти в систему каталога и, будем надеяться, что-нибудь приобрести.
    Если вы подумаете, какими характеристиками должна обладать система показа новостей, в ваш список, вероятно, войдут следующие пункты:
    гибкий способ отображения;
    удобный ввод данных;
    минимальная нагрузка на сервер.
    Наряду с обеспечением этих свойств при разработке системы вы, в первую очередь, должны будете решить, какие элементы и атрибуты XML использовать для представления данных, затем сделать выбор между моделями DOM и SAX (механизмами отображения данных для пользователя) и, наконец, определить механизм добавления новых сообщений.



    Сервлет 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 = "
    \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("User: " + username + " password: " + password + " not found.
    " ); } 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 + "
    "); 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("

    Enter Company News Item Data

    "); out.println("
    "); out.println("Headline - 80 char max
    "); out.println("
    " ); out.println("Dated
    "); out.println("
    " ); out.println("Topics separated by commas - please stick to the official list.
    "); out.println("
    " ); out.println("Short version
    "); out.println("
    "); out.println("Long version
    "); out.println("
    "); out.println("
    " ); out.println("
    "); out.println("
    " ); out.println("
    "); }


    Наконец, в сервлете имеются обычные вспомогательные методы, показанные в листинге 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("

    Error: " ); out.println( msg ); out.println("


    "); if( ex != null ){ ex.printStackTrace( out ); } out.println("
    "); out.println(" Please mail me the error message.
    "); footer( out ); }

    private void header(PrintWriter out ){ out.println(""); out.println(" Company News Servlet "); out.println(""); }

    private void footer(PrintWriter out ){ out.println("

    Company News Servlet " + version + " properties:
    " ); out.println(""); out.println(""); out.close(); }

    }





    Система показа новостей

    Окончательный вариант устройства системы показа новостей изображается блок-схемой, приведенной на рис. 8.1. Обработка информации, происходящая на сервере, представлена в правой части блок-схемы, а обработка в автономном режиме — в левой части. Исходный файл XML может редактироваться как в режиме подключения к сети, так и автономно, но предпочтительным является режим подключения, так как он позволяет автоматически задавать атрибуты id и timestamp.
    Система показа новостей

    Рис. 8.1. Обработка сообщений
    Объектная модель документа поддерживается в памяти с помощью класса DOMlibrary, который был описан в главе 7. Обновление исходного файла XML в режиме подключения выполняется сервлетом CompanyNewsServ и классом NewsUpKeep, которые обсуждаются далее в этой главе. Формирование web-страниц новостей на основе текстов сообщений осуществляется сервлетами или JSP-страницами, использующими класс NewsFormatter, который обсуждается в следующем разделе. Для создания информационных бюллетеней и печатной версии требуются другие форматы, которые легко написать, основываясь на приведенных ниже примерах.



    Внешний вид web-страницы

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

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


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

    Формат 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).



    Формат сообщений Moreover.com

    Для задач, которые решаются в этой главе, мы используем формат импорта и экспорта сообщений, предложенный организацией Moreover.com (www.moreover.com). Мы выбрали Moreover.com для рассказа о технологиях обмена новостями по причине простоты используемого формата и большого количества тематических категорий на сайте. Разумеется, в пользу этого формата говорит и то, что он распространяется свободно.
    Хотя организация Moreover.com была создана только в декабре 1999, к июлю 2000 она получала заголовки с 1500 сайтов. Программное обеспечение, занимающееся сбором заголовков новостей, автоматически разделяет их на более чем 250 категорий.
    Формат, используемый Moreover.com, проще RSS и выдает только сами заголовки. Для индивидуальной настройки импорта информационных сообщений нужно зарегистрироваться в системе, сообщив свой электронный адрес и пароль, а затем выбрать нужные тематические категории и указать количество заголовков сообщений, которые вы хотели бы увидеть. На рис. 9.1 показана небольшая часть страницы, на которой происходит выбор тематических категорий.
    Формат сообщений Moreover.com

    Рис. 9.1. Выбор категорий заголовков на сайте Moreover.com



    Главный управляющий класс

    Теперь мы переходим к рассмотрению класса, который управляет процессом получения заголовков через определенные промежутки времени и обеспечивает доступ к ним сервлетов. Класс 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() ; }

    }





    Источники новостей и стандарты

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



    Класс 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(" \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

    Класс NetNewsBean



    Рис. 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() ; }

    }





    Класс NewsModel

    Теперь, когда файл XML и соответствующий файл DTD находятся на локальном жестком диске, нам нужен класс для синтаксического анализа файла XML и создания DOM. Написанный нами для ^того класс называется NewsModel; он создает коллекции элементов в соответствии с типом содержимого, или, если использовать терминологию Moreover.com, кластеры (clusters). В классе NewsModel предусмотрены также методы для извлечения и форматирования отдельных элементов.



    Классы для отображения заголовков

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



    NewsML и планы стандартизации

    Международный совет по медиа-телекоммуникациям (The International Press Telecommunications Council, IPTC), представленный по адресу www.iptc.org, предпринимает попытки создать на основе XML стандарт кодирования, который упростил бы процесс составления, передачи и получения информационных сообщений. Во время написания этой книги была выпущена версия 1.0 этого стандарта, названного NewsML.
    Возможности этого стандарта по сравнению со стандартом RSS весьма широки. Например, он включает в себя различные мультимедийные элементы и предусматривает явные связи между сообщениями.
    Поскольку в совет IPTC входит большое количество организаций, многие из которых зарабатывают на информационных сообщениях, при создании стандарта сталкивается множество различных интересов. Если NewsML утвердится в качестве стандарта, описанные в этой главе методики по-прежнему будут применимы.



    Получение файла 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
    http://c.moreover.com/click/here.pl?x8510756 Cyclone Commerce Poised to Fulfill Promise of E-Signature Legislation Java Industry Connection text Java news http://industry.java.sun.com/javanews/more/hotnews/ Jul 25 2000 8:34AM

    Обратите внимание, что элемент, который в Moreover.com называется cl uster, идентифицирует основную тематическую категорию, к которой относится данное сообщение. В приведенном ниже примере сервлета мы используем только элементы url, headline_text, source и cluster. Определение DTD moreovernews, как видно из листинга 9.2, устроено относительно просто.



    Листинг 9.2. Файл moreovernews.dtd (moreovernews.dtd)



    Сервлет, который используется в рассматриваемом в этой главе приложении, включает в себя класс 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
    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("

    В предположении, что метод 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(); } } }





    Сервлет 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 = "\" > <%headline_text>" + "   from <%source>" ;
    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(""); out.println(" NetNewsServ Output "); out.println(""); try { NetNewsBean nnb = new NetNewsBean( queryStr, destDir, queryFile );

    out.println("

    The News

    "); out.println("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.
    " ); out.println("
    " ); out.println("Key Words:
    "); out.println("Select one or more topics (use <ctrl>click.)
    "); out.println( nnb.getTopicsAsSelect() ); out.println("
    "); out.println("

    "); footer( out ); }catch(Exception e){ errorMsg( out, "NetNewsServ.doGet ", e ); } }

    Сервлет NetNewsServ



    Рис. 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("

    Error: " ); out.println( msg ); out.println("


    "); if( ex != null ){ ex.printStackTrace( out ); } out.println("
    "); out.println(" Please mail me the error message.
    "); footer( out ); } private void header(PrintWriter out ){ out.println(""); out.println(" Network News Servlet "); out.println(""); }

    private void footer(PrintWriter out ){ out.println("

    Network News Servlet " + version + "
    " ); out.println(""); out.println(""); 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(" Selected by keywords: " + keywords + ""); out.println( nnb.getContentByKeyWord( keywords, fmt )) ; } out.println("
    ") ; if( topics == null ){ // none selected for( i = 0 ; i < tstr.length ; i++ ){ out.println("topic: " + tstr[i] + "" ); out.println( nnb.getContentByTopic( tstr[i], fmt ) ); } } else { for( i = 0 ; i < topics.length ; i++ ){ int tn = Integer.parseInt( topics[i] ); out.println("topic: " + tstr[tn] + "" ); out.println(nnb.getContentByTopic(tstr[ tn ], fmt)); } } }catch(Exception e){ out.println( "" ); e.printStackTrace(out ); out.println(""); } }


    Все остальные методы в классе 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("

    Error: " ); out.println( msg ); out.println("


    "); if( ex != null ){ ex.printStackTrace( out ); } out.println("
    "); out.println(" Please mail me the error message.
    "); footer( out ); } private void header(PrintWriter out ){ out.println(""); out.println(" Network News Servlet "); out.println(""); }

    private void footer(PrintWriter out ){ out.println("

    Network News Servlet " + version + "
    " ); out.println(""); out.println(""); out.close(); }

    }





    Создание 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 ); }





    Возможные усовершенствования

    Хотя основанный на использовании DOM подход является вполне приемлемым для систем с небольшой нагрузкой на сервер, он не слишком эффективен, так как для доступа к каждой странице нужно выполнять одни и те же вычисления Хуже всего, когда в тексте заголовков встречаются ссылки на сущности
    Разумной альтернативой хранению DOM в памяти является представление отдельного заголовка через класс Java и создание коллекции таких классов путем перехвата событий анализатором SAX Если у этого класса также имеется возможность применять требуемый формат HTML к заголовкам сообщений, то конструктор может заранее форматировать весь элемент и хранить только строку, представляющую данный элемент, плюс строку, содержащую текст заголовка для поиска
    Создание пользовательской библиотеки тегов JSP для заголовков не требует написания длинных программ, но такая библиотека упростила бы работу web- дизайнера по наделению любой страницы средствами представления заголовков новостей, причем без необходимости модифицировать код Java.

    Выбор заголовков

    Теперь мы подошли к рассмотрению методов сервлета, которые отвечают на запросы пользователя о поиске определенных заголовков сообщений. Для нашего примера мы ограничились двумя методами выбора заголовков: вы можете либо просмотреть всю коллекцию в поисках заголовков, которые содержат определенную последовательность символов, либо извлечь все заголовки, входящие в конкретную категорию.
    При вызове метода 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. Рассмотрим содержимое элемента , куда включена сущность (например, &атр;):

    Q&A: Will Sony I

    Rule the Digital World

    Эта строка будет разделена анализатором на три объекта 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 & notation sb.append( '&' ); sb.append( n.getNodeName()); sb.append( ';' ); } else { sb.append( n.getNodeValue() ); } } return sb.toString(); }

    Метод locateCategories (см. листинг 9.12) создал коллекцию clusterHash, в которой элементы (заголовки сообщений) хранятся в соответствии со своими тегами . Мы не совсем понимаем, почему в 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:

    "  from <%source>


    Работа этого метода заключается в отыскании символов <%, выделении имени элемента и вызове метода 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(); } }





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

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

    В отличие от жестко структурированной среды J2EE в концепции Spaces web- приложение создается как слабосвязанная система. Эта концепция несколько лет обсуждалась в академических кругах, а недавно было найдено новое ее применение. Проект JINI, разработанный Sun для связи между распределенными приложениями, использует новый интерфейс API JavaSpaces.
    Приложения, взаимодействующие через JavaSpaces, обмениваются сообщениями не напрямую, а посредством специального приложения, которое управляет пространством сообщений. Сообщения содержат данные (объекты Java) и идентифицирующие пометки, которые требуются, чтобы сопоставить заданию соответствующее приложение, способное это задание выполнить. Приложение, отправившее сообщение в пространство сообщений, может быть проинформировано о том, что посланный им объект прошел требуемую обработку. Сравнивая эту ситуацию с той, в которой имеется специальный диспетчер, назначающий задания приложениям, мы видим, что взаимодействие, основанное на использовании пространства сообщений, автоматически распределяет нагрузку между различными системами более равномерно.
    Например, программа "корзина покупателя" может отправить сообщение, представляющее содержимое корзины, с меткой, которая означает запрос о подсчете стоимости доставки заказа покупателю. Программа подсчета стоимости доставки, ожидающая очередного запроса, получает этот объект, выполняет необходимые вычисления и возвращает заполненный объект в пространство сообщений.
    Тем, кто ищет подробную информацию о программировании в соответствии с концепцией пространства сообщений JavaSpace, можно предложить в качестве исходного пункта следующий адрес:
    http://java.sun.com/products/javaspaces/

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

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


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

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

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


    cattest com.XmlEcomBook.catalog.CatalogTestServ catalog com.XmlEcomBook.catalog.CatalogServ workdir e:\\scripts\\XMLgifts
    В листинге 10.2 показана начальная часть файла web.dtd из версии 2.2 API сервлетов. Полная копия файла, содержащая более 400 строк, имеется на прилагаемом к книге компакт-диске.

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





    J2EE и Enterprise JavaBean

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

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


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

    На этап определения web-приложения API сервлетов не накладывает никаких ограничений. Производители имеют полную свободу в создании собственных систем для определения контекста приложения на сервере. Сервер Tomcat использует файл server.xml для определения элемента ContextManager, содержащего многочисленные элементы Context. Многие из основных серверных функций определены в элементах Context, и они также задействуются для определения web- приложений. Например, ниже мы приводим элемент Context, который мы использовали для данной книги:
    reload,able="true" >
    Таким образом, относительно исходной папки сервера устанавливается корневая папка в физической структуре файлов, которую web-сервер использует для приложения XMLbook. Папки, входящие в корневую папку, требуются серверу для хранения HTML-страниц, JSP-страниц и других ресурсов, связанных с этим приложением, как будет показано в следующем разделе.
    Также вы можете определить параметры, которые будут доступны всем серв- летам или JSP-страницам данного приложения через объект ServletContext. Ниже приводится пример применения элемента Parameter:
    "webapps/XMLbook" debug="0"
    reloadable="true" >

    В Tomcat используется соглашение о том, что корневая папка вашего приложения и ее вложенные папки по умолчанию содержатся во вложенной папке ROOT, как указано в следующем объявлении Context:
    reloadable="true" >
    Папка ROOT содержит файл index.html, который автоматически отображается при вводе URL-адреса, например такого: http://localcost:8080/.


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

    Как видно по именам элементов в листинге 10.2, большое количество параметров конфигурации web-приложения можно устанавливать из файла web.xml. Эти параметры удобно разделить на следующие категории:
    параметры инициализации ServletContext;
    конфигурация HttpSession;
    определение и отображение сервлетов и JSP-страниц;
    отображение типов MIME;
    стандартные страницы с приветствием;
    страницы ошибок;
    параметры безопасности.
    Использование параметров каждой из этих категорий подробно описано в официальном руководстве по API сервлетов, поэтому здесь мы не будем рассказывать обо всех параметрах.) Наиболее вероятно, что из всех этих параметров вам потребуются только параметры для определения и отображения сервлетов и JSP-страниц. Например, рассмотрим следующий фрагмент листинга 10.1:
    catalog
    com.XmlEcomBook.catalog .CatalogServ

    workch r
    e:\\scMpts\\XMLgTfts

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


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

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


    Протокол SOAP

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


    Развертывание 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-приложенями каких бы то ни было ресурсов.


    Следующее поколение 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/.


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

    Сейчас, когда пишется эта книга, уже выпущена следующая версия 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.


    Содержимое папки 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 способом с использованием стандартных настроек путей классов. Не существует способа разрешить совместное использование какого-либо класса только избранным приложениям; доступ к классу разрешен либо только одному приложению, либо всем.


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

    Самая большая проблема при разделении нагрузки между несколькими машинами связана с приложениями, которые должны отслеживать состояние пользователя. В приложении "корзина покупателя" мы использовали объект HttpSession, управляемый контейнером сервлетов, чтобы сохранять объект ShoppingCart в промежутках между циклами запрос-ответ. К сожалению, управление объектом HttpSession осуществляется целиком внутри одного объекта ServletContext в одной виртуальной машине Java. Чтобы в условиях кластеризации данные корзины покупателя оставались актуальными в течение всего времени, пока пользователь находится на вашем сайте, в системе должна применяться одна из приведенных ниже технологий.
    Каждый запрос данного пользователя нужно направлять на одну и ту же машину, для этого в каждом запросе нужно отслеживать специальные данные, например файл cookie, содержащий идентификатор сеанса в строках заголовка, или IP-адрес пользователя. Недостаток данного способа заключается в том, что он неустойчив к сбоям, так как информация о сеансе хранится только на одной машине.
    Можно направлять запрос каждого пользователя в определенную машину, как и в первом способе, но создавать запасную копию каждого сеанса для того, чтобы, если сбой происходит на одной машине, информация о сеансе могла быть восстановлена.
    Можно распределять информацию о сеансе по всем системам, входящим в кластер, — тогда будет безразлично, какая система получит запрос.
    Другое соображение, говорящее в пользу кластеризации, — балансировка нагрузки, так что все машины примерно поровну задействуют свои ресурсы. Отдельные машины должны иметь возможность сообщать распределителю запросов насколько они заняты.


    Спецификации в интерфейсе 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.


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

    Атрибут 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-сервер не имеет права отправлять пользователю любые содержащиеся в этой папке ресурсы.


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

    Доступ к стандартным переменным

    В классе 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 не станет недействительным




    Другие методы интерфейса 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 используется для чтения и синтаксического анализа тела запроса строка за строкой





    Формирование ответа пользователю

    В этом разделе мы рассмотрим классы и методы, связанные с отправкой данных в ответ на запрос пользователя.
    ServletResponse. Объект, реализующий этот интерфейс, должен быть создан процессором сервлетов и передан методу service сервлета для передачи клиенту данных MIME.
    HttpServletResponse. Расширение интерфейса ServletResponse, в котором добавляются некоторое методы, специфические для протокола HTTP.
    Servl etOutputStream. Класс для создания потока двоичных данных как части ответа.



    Интерфейс 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-страницы





    Интерфейс 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, которую поддерживает данный контейнер





    Класс 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



    Класс HttpSession и классы, связанные с cookie

    В этом разделе мы обсудим класс HttpSession и классы, связанные с cookie. Объект HttpSession используется для хранения информации о состоянии в промежутках между соединениями с конкретным пользователем.
    HttpSession. Объекты, реализующие этот интерфейс, позволяют программисту хранить информацию о пользователе в промежутках между отдельными посещениями страницы или соединениями. Процессоры сервлетов обеспечивают методы для мониторинга объектов класса HttpSession, используя уникальные идентификаторы (см. примеры в главе 3).
    HttpSessionBindingListener. Этот интерфейс аналогичен многим интерфейсам слушателей событий в GUI Java. Программисты могут реализовать этот интерфейс в объекте, которому требуется извещение, когда он присоединяется к объекту HttpSession или отсоединяется от него.
    Cookie. Эти объекты используются для манипулирования информацией, содержащейся во фрагментах cookie, которые посылаются сервером браузеру и возвращаются при последующих запросах. Информация cookie, содержащаяся в запросе, преобразуется в объекты Cookie методами класса HttpServletRequst.
    HttpUti I s. Статические методы этого класса используются в различных ситуациях.
    HttpSessionBindingEvent. Объекты этого типа используются для передачи информации объектам класса HttpSessionBindingListener, когда они присоединяются к объекту 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, после чего завершает строку





    Класс PageContext

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



    Коды состояний и ошибок 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 (Показывать уведомление о каждой ошибке сценария).





    Методы, добавляемые интерфейсом 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 запроса, которая соответствует данному сервлету





    Методы, добавляемые интерфейсом 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)


    Устанавливает код состояния для ответа





    Методы интерфейса HttpSession Listener

    Если объект сохраняется в сеансе и хочет "знать", когда он был присоединен к сеансу и когда он от него будет отсоединен, этот объект должен реализовать интерфейс HttpSessionListener, то есть объект должен реализовать два показанных ниже метода.
    valueBouncKHttpSessionBindingEvent event). Объект, который связывается с сеансом. Параметр event содержит идентификатор сеанса и имя, с которым связан объект.
    valueUnbound(HttpSessionBindingEvent event). Объект, который отсоединяется от сеанса, возможно, по причине того, что срок сеанса истек.



    Методы интерфейса 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)





    Методы класса 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





    Методы класса 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





    Методы класса 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( )


    Возвращает уникальный идентификатор, присвоенный данному сеансу





    Методы, связанные с безопасностью

    Методы обеспечения безопасности, перечисленные в табл. А.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( )


    Проверяет, является ли запрошенный идентификатор сеанса действительным





    Ошибки и исключения 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)


    Добавляет сообщение об ошибке





    Ошибки и исключения

    В 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)


    Конструирует новое исключение сервлета с сообщением, что данный сервлет временно недоступен, и с оценкой времени, в течение которого он будет недоступен





    Пакет 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





    Параметры приложений

    В интерфейсах API для сервлетов и JSP-страниц параметры рассматриваются как объекты, обладающие определенной областью видимости. Эта область состоит из тех частей системы, в которых применение данного параметра является корректным. Перечислим области видимости в порядке убывания: сервер, приложение, сеанс, запрос и страница.
    Сервер. Параметры из этой области могут использоваться во всех приложениях.
    Приложение. Sun термином "web-приложение" называет совокупность сервлетов, JSP-страниц и других ресурсов, установленных в определенном адресном пространстве сервера. Класс Servl etContext предназначен для обеспечения возможности совместного использования информации всеми ресурсами, входящими в приложение. Это осуществляется с помощью объектов, которые доступны всем ресурсам приложения и относятся к области видимости приложения. Данная область видимости влияет на интерпретацию относительных URL-адресов.
    Сеанс. Как было сказано в главе 3, сервлеты и JSP-страницы могут хранить специфическую для данного пользователя информацию в объекте HttpSession, которым управляет процессор сервлетов. Объекты, хранящиеся таким образом, называются объектами области видимости сеанса.
    Запрос. Запрос на самом деле может обрабатываться несколькими страницами с помощью механизма переадресации (метода forward). Запрос (объект Servl etRequest или HttpServl etRequest) может включать в себя дополнительные объекты.
    Страница. В сервлетах, созданных процессором JSP-страниц, может иметь место область видимости отдельной страницы. Если запрос обрабатывается с помощью метода forward или include, переменная, имеющая данную область видимости, доступна только на той странице, где она создана.



    Получение информации о запросе

    Сервлет получает информацию о запросе пользователя в виде объекта, реализующего интерфейс 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





    мы обнаружили, что документация JDK,

    Интерфейсы 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- страниц, которые позволяют создавать пользовательские библиотеки тегов.



    Создание сервлета

    Поскольку методы сервлета определяются в интерфейсе, а интерфейсы не могут определять конструкторы, инициализация сервлета полностью осуществляется в методе 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) Вызывается контейнером сервлетов для того, чтобы сервлет получил возможность ответить на запрос




    Специальные объекты, связанные с запросом

    В интерфейсе RequestDispatcher определены два метода, которые позволяют одному сервлету или одной JSP-странице переадресовать или включить выходные данные другого сервлета или файл, находящийся на сервере. Вы получаете объект RequestDispatcher из объекта ServletRequest с конструктором, который указывает, какой ресурс должен быть присоединен. Этот объект создается процессором сер- влетов, как показано в следующем примере:
    RequestDispatcher rd = req.getRequestDispatcher(String path)
    Используя этот объект, вы можете вызвать любой из следующих методов:
    forward(ServletRequest req, ServletResponse resp) include(ServletRequest req, ServletResponse resp)



    Выходные данные JSP-страниц

    Экземпляр класса JspWriter, называемый out, автоматически создается в методе _jspService класса PageContext. Объект JspWriter аналогично объекту Java.iо.PrintWriter записывает поток символов в специальной кодировке. Но между ними имеется значительная разница: методы класса JspWriter для вывода данных могут генерировать исключение lOException. В классе PrintWriter исключение IOЕхсерtion обрабатывается внутри, и программист должен вызывать метод checkError(), чтобы узнать, было ли сгенерировано исключение.
    Способность генерировать исключение lOException является существенной для управления буферизацией, которую осуществляет JspWriter.



    

        Бизнес: Предпринимательство - Малый бизнес - Управление