Электронный магазин на 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:
ПРИМЕЧАНИЕ
Слова, набранные в предыдущих примерах только прописными буквами, являются ключевыми словами 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 можно использовать для достижения одной цели:
или
Хотя выбор остается за вами, имеется несколько общих принципов. Мы расскажем о них в главе 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, может содержаться фрагмент, подобный следующему:
В атрибутах перечислимых типов приводится список всех возможных значений этого атрибута. Например, если вы хотите объявить атрибут с именем 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
В первую очередь в этом листинге следует отметить, что в объявлении XML присутствует атрибут standalone="yes". Это означает, что в данном документе нет определения DTD. Наличие DTD в документах XML не является обязательным. Фактически приложения, в которых задействованы данные XML, часто не включают DTD в целях повышения эффективности в тех случаях, когда структура документа и возможность его многократного использования не являются важными факторами.
Следом за объявлением XML идут элементы. Элемент (element) — это наиболее распространенная форма разметки; он выделяется с помощью угловых скобок (< и >) и описывает тот фрагмент данных, который заключен между скобками > и <. Элемент состоит из открывающего и закрывающего тегов (
Элемент, лишенный содержимого, называется пустым элементом (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:
]]>
Схема XML
Хотя определения DTD в настоящее время являются стандартом определений типов документов XML, у них есть несколько серьезных ограничений. Определения DTD были унаследованы от языка SGML, где они были исходно разработаны для определения языков разметки, а не для создания схем баз данных. Самое существенное ограничение определения DTD заключается в том, что оно не обеспечивает достаточного контроля над содержимым элементов. Например, с помощью DTD невозможно указать, что показанный ниже элемент является допустимым [Today's date — сегодняшняя дата — Примеч. перев. ]:Точно так же нельзя указать, что следующий элемент не является допустимым:
Кроме того, вам может потребоваться задать более точные ограничения на количество появлений элемента в документе. С помощью 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)
Элементы сложного типа определяются с помощью элемента 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)
A lovely collection of songs that the whole family can sing right along with.
Допустим, вы хотите создать и напечатать список всего, что имеется в вашей библиотеке. Один из способов сделать это заключается в том, чтобы применить к документу 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.

Рис. 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 позволяет программисту самому создать такой язык разметки, который в точности соответствует требованиям конкретного приложения. В нашей книге мы проиллюстрируем процесс создания приложения, относящегося к области электронной коммерции. Язык разметки, который мы будем использовать, содержит теги, выглядящие осмысленно именно в этой области (такие, например, как
В приложениях 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">
<АВС_Lighting:name>Неоновая лампа
так, как неоновая лампа!
Первая строка этого документа — объявление 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)
subject = "ваша задача на сегодня">
Поздравляю с окончанием проекта XMLGnfts.com
Предлагаю
Этот метод — изучение реального образца данных и последующее исправление DTD с учетом возможных потребностей в будущем — мы и будем использовать при создании каталога товаров для сайта XMLGifts.com
В листинге 2 4 приводится первый черновой вариант документа XML, описывающего несколько товаров из будущего каталога для сайта XMLGifts.com Этот документ написан без DTD Он является правильно оформленным документом XML, но, поскольку никакому определению DTD он не соответствует, его нельзя назвать допустимым и самодокументируемым [Caption — подпись (к рисунку) productlme — серия товаров — Примеч перев ].
Листинг 2.4. Первая черновая попытка описания товаров
src="images/covers/plants.gif ">
This is the cover from the first edition
Somewhere
Internet companies.
src="track2.mp3">
produce heat that can be used for oxidization
purposes.
calculating machine through tapping!
Достоинства 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
to know about plants.
src="images/covers/plantsv1.gif">
edition.
to know about plants.
src="images/covers/plantsv2.gif">
edition.
keywords="how-to, technology">
into a transporter..and other neat tricks.
src="images/covers/millenium.gif">
that's catching on like wildfire.
src="images/covers/dryerart.gif">
away from the house! Each of the 15 tracks
on this CD has been scientifically shown to
relax pets of all kinds.
src="http://www.musicfordogs.com/images/cover.gif">
electric calculating machine--through
tapping!
Гибкость инструментальных средств
Хотя данные для нашего электронного магазина хранятся в виде документов 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 часа, то существуют по меньшей мере два способа записать эту информацию:
Согласно существующему определению 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) в нашем примере таким образом, чтобы информацию из него могли извлечь и человек, и программа:
звучание которой можно определить как смесь
стремительно завоевывает популярность во всем
мире.
Теперь приложение легко справится с задачей преобразования элементов 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>Справочник по растениям
<Р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 Преступление и наказание |
Эрнест Хемингуэй Вильям Брогден, Крис Минник Федор Достоевский |
Листинг 2.1. Документ XML, получившийся в результате преобразования реляционной базы данных (BookView.xml) [Все представленные в книге тексты программ можно найти на сайте .издательства по адресу www.piter.com, — Примеч. ред. ]
Поддержка Unicode
Допустим, что компания XMLGifts решила, помимо всего прочего, распечатать свой каталог на нескольких языках. Встроенная в Java и XML поддержка символов Unicode упрощает эту задачу.Поскольку web-разработчики все больше и больше стремятся к тому, чтобы пользовательский интерфейс и содержимое web-страниц не были ограничены в языковом отношении, поддержка Unicode становится все более важной характеристикой любой Интернет-технологии. Поддержка Unicode была добавлена и в другие популярные языки web-программирования, такие как Perl и Tel. Однако в отличие от них язык Java был исходно рассчитан на поддержку Unicode, поэтому обработка символов различных алфавитов нисколько не усложняет структуру приложения, как это может произойти на других платформах.
Покупка готового пакета — это быстрее и дешевле
Покупка готового пакета приложений для электронной коммерции, который затем настраивается соответственно конкретным требованиям, иногда может сэкономить время на разработку сайта Однако чаще получается, что вы оказываетесь привязанным к конкретным технологиям, и хотя приложение как-то заработает, вряд ли оно будет точно соответствовать нуждам вашего бизнеса и хорошо масштабироваться.Если вы планируете разместить на своем сайте достаточно стандартный электронный магазин и вам нужно как можно скорее запустить его в работу, то, вероятно, вам подходит такой вариант В этом случае вам следует внимательно отнестись к своему выбору — вы должны убедиться, что выбранный вами пакет действительно будет работать так, как нужно
Такой метод можно назвать быстрее и дешевле, так как он удовлетворяет этим двум требованиям.
ВНИМАНИЕ
Если вы решите воспользоваться этим методом, но не будете достаточно внимательны, вы рискуете оказаться в ситуации, когда вам придется постоянно модифицировать и дополнять исходное приложение, чтобы приспособить его к своим нуждам. Тем самым ваш проект потребует больших затрат времени и денег, чем многие другие решения.
Причины
Более гибкие типы документов могут оказаться более удобными. Универсальный элемент облегчает обработку документа.Ниже приводится пример кода шаблона:
Ниже приводится пример кода шаблона:Повесть Алберта Камю
служит примером выражения идей
экзистенциализма в литературе
Принципы разработки 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:
Этот запрос просто передает на сервер все данные, необходимые для вызова указанного метода. Обратите внимание, что пространство имен, указанное в первом элементе внутри 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)>
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
До сих пор мы занимались форматированием данных для одного товара Теперь посмотрим, как создать на странице список товаров со ссылками на их полные описания В классе 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, который устанавливает заголовок страницы, затем создает теги и
Листинг 3.14. Метод doPost сервлета, отображающий весь каталог (CatalogTestServ.java)
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
resp.setContentType("text/html");
PrintWriter out = new PrintWriter(resp.getOutputStream());
String action = req.getParameter("action");
out.println("");
out.println("
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("
| Books | CDs | 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-страницы
Complete Catalog
Метод | Возвращаемое значение | Описание | |||
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)
you've ever wanted to know about plants.
src="images/covers/plants.gif»>
Например, если вы выполните метод 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
В этом интерфейсе имеются только два метода:Node item ( int n ) — возвращает ссылку на n-й узел в списке или null, если данная позиция списка пуста.
Язык тегов JSP
В приведенном ниже коде JSP-страницы теги JSP начинаются с символов <*= и заканчиваются символами %>. После компиляции в Java-класс запрос, обращенный к этой JSP-странице, выдаст обычный статический текст HTML-страницы, куда будет вставлена динамически сгенерированная строка, созданная с помощью метода toStnng, примененного к новому объекту Date: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.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).
Если исходная страница изменилась или еще не была скомпилирована, то компилятор обрабатывает исходный код и создает эквивалентный исходный код 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( "\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("
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
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( "
}
}

Рис. 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" [Здесь может быть расположен интерфейс для навигации по вашему сайту. — Примеч. перев. ], в то время как в этом месте обычного коммерческого сайта, как правило, находятся логотипы и навигационный интерфейс.

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

Рис. 4.2. Отображение раскрывающегося списка ключевых слов
В табл. 4 2 представлен перечень команд отображения (значений параметра action), которые определяют ответ сервлета CatalogServ Команды, управляющие отображением, обычно модифицируются дополнительными параметрами
Таблица 4.2. Команды, распознаваемые сервлетом CatalogServ
|
Команда (параметр action) |
Дополнительные параметры |
Отображение |
| showcatalog | Параметр select = "all" | Таблица со списком всех товаров (см. рис. 4.1) |
| showcatalog | Параметр select =одна из серий товаров (books, CDs, widgets) | Таблица со списком товаров определенной серии (см. рис. 4.3) |
| selectkeyword | Параметр select = "all" | Раскрывающийся список ключевых слов (см. рис. 4.2) |
| showproduct | Параметр id из формы | Полная информация о товаре (см. рис. 4 4) |
| keywdsearch | Параметр keyword из формы | Список товаров с этим ключевым словом |
| setcart | Параметры id и itemct из формы | Полная информация о товаре с измененным количеством заказанных экземпляров (см. рис. 4.5) |
| showcart | Параметры отсутствуют | Список всех товаров в корзине покупателя с указанием количества заказанных экземпляров (см. рис. 4.6) |
Класс 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
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
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
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
// 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
src="images/covers/pi ants.gif">
first edition.
Помимо создания тега
Листинг 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 = " int nitem = 0 ; // permit checkout if cart has any items
out.print("
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("
}
Метод 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("
| " + 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(" |
}

Рис. 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("
| " + 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("
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("
}
Если 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("
}
Проблема корзины покупателя
Для посетителей виртуального магазина интуитивно понятна аналогия "корзины покупателя" (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.*" %>
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)
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)
Your order price
| Items | <%=order.getTotalItemPrice()%> |
| Shipping | <%=price%> |
| Total | <%=order.getOrderTotal()%> |
Please enter your credit card information:
JSP-страница Declined
JSP-страница Declined формируется, если по какой-либо причине кредитная карта клиента не прошла проверку поставщика. Страница генерирует сообщение, в котором указывается эта причина.Листинг 5.27. JSP-страница Declined (Declined.jsp)
<%@ page import="com.XmlEcomBook.Chap05.*" %>
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.*" %>
<% 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 ); %>
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.*" %>
<%
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 "
Листинг 5.18. Код HTML, который формируется в JSP-странице Shippinglnfo (Shippinglnfo.jsp)
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.*"
%>
<%
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
| Item | Description | Quantity | 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)
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.*" %>
<% 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( "" );
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( "
customerInfo.getFirstName() + "
"\n
"\n
"\n
"\n
"\n
"\n
"\n
"\n
CreditInfo credit = customerInfo.getCreditInfo();
writer.write( "\n
credit.getCreditCardNumber() + "
"\n
"\n
"
writer.write( "\n
authorization.getReason() + "
authorization.getAuthorizationCode() + "
"
writer.write( "\n
"\n
"\n
"\n
"\n
"
fulfillment.getDateSent() + "
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)
Электронный магазин на Java и XML
Главная HTML-страница
На главной HTML-странице пользователю предлагаются три возможности: добавить товар, редактировать товар или удалить товар. Существует также возможность отменить все изменения, сделанные пользователем в текущем сеансе, или сохранить эти изменения. Для всех этих операций вызывается один и тот же сервлет, но всякий раз с различными значениями скрытого поля с именем operation. В начале HTML-страницы (листинг 6.41) содержится ее заголовок и открывающий тег элемента body.Листинг 6.41. Начало HTML-страницы (main.html)
Каждая из пяти операций, предложенных пользователю, представлена специальной формой на странице. Первую операцию, Add Product (Добавить товар), иллюстрирует листинг 6.42. Для нового товара требуется уникальный идентификатор, поэтому нужно проверить все уже существующие идентификаторы, чтобы не получилось повторения. Также требуется указать серию товаров, к которой принадлежит новый товар. Атрибут action элемента form содержит имя сервлета, который вызывается, когда форма заполнена и отправлена на сервер. В данном случае мы вызываем сервлет Main. В следующей строке указывается заголовок для формы Add Product.
Первый элемент этой формы является скрытым. Это значит, что пользователь не увидит его на экране своего компьютера. Он просто передается сервлету вместе со всеми остальными введенными данными. Этот элемент позволяет сервлету определить, что именно хотел сделать пользователь. Следующие две строки предназначены для того, чтобы пользователь мог ввести идентификатор товара и указать серию товаров, к которой он будет принадлежать. Последняя строка элемента form нужна для того, чтобы добавить кнопку, с помощью которой пользователь сможет отправить введенные данные на сервер.
Листинг 6.42. Форма для добавления товара (main.html)
Формы Delete Product (Удалить товар) и Edit Product ( Редактировать товар) аналогичны форме Add Product, но в них требуется ввести только идентификатор товара. Для визуального разграничения этих форм между ними добавляется горизонтальная линия. Код для форм Delete Product и Edit Product приведен в листинге 6.43.
Листинг 6.43. Формы Delete Product и Edit Product (main.html)
Последние две операции — отмена и сохранение всех изменений, внесенных пользователем в течение данного сеанса, — иллюстрирует листинг 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 ); %>
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. Сначала нужно установить заголовок страницы в тегах и
Листинг 6.51. Начало кода элементов HTML-страницы (Edit.jsp)
<%= name %> Product
В нижней части этой 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 = "
Вывод объектов Author, Artist и Clip очень похож на вывод объектов Image. Каждый из элементов и атрибутов отображается в отдельной строке таблицы. Строка, которая идентифицирует конкретный объект, также используется двояким образом: для отображения в поле ввода и как значение атрибута name объекта input. Этот метод для объекта Clip показан в листинге 6.57.
Листинг 6.57. Отображение объекта Clip (Edit.jsp)
<%! private String outputClip( String i, Clip clip ) { String s; s = "
private String outputAuthor( String i, String author ) { String s = "
private String outputArtist( String i, String artist ) { String 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( "
Класс 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( "
}
Класс 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( "
Класс 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( "
Класс 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("
"); 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( "
}
Класс 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( "
}
Класс Util
В классе Util имеется несколько удобных служебных методов, которые вызываются как из объекта данных, так и из классов представления. Все они являются открытыми и статическими, поэтому их можно вызывать откуда угодно. Первые два метода этого класса, приведенные в листинге 6.37, используются для извлечения информации из данных в формате XML. Метод extractTextFrom ищет конкретный элемент, и если находит, извлекает его текстовое содержимое и возвращает. Следующий метод, extractlntFrom, использует первый метод для извлечения текста из элемента, а затем преобразует этот текст в формат Integer, после чего возвращает полученное значение.Листинг 6.37. Начало кода класса Util и методы extractTextFrom и extractlntFrom (Util.java)
package com.XmlEcomBook.Chap06;
import org.w3c.dom.*; import java.io.*;
public class Util {
static public String extractTextFrom ( String childElementName, Element element ) { NodeList nameList = element.getElementsByTagName ( childElementName ); if( nameList.getLength() < 1 ) { return null; } Text nameText = (Text)nameList.item(0).getFirstChild(); return nameText.getData(); }
static public Integer extractIntFrom ( String childElementName, Element element ) { String s = Util.extractTextFrom( childElementName, element ); if( s == null || s.equals( "" ) ) { return null; } return new Integer( s ); }
Следующий метод также помогает работать с данными XML. Этому методу, приведенному в листинге 6.38, передается объект NodeList (список узлов), а возвращается он уже в виде текста. Основная обработка происходит в цикле for, где конструируется строка. Для каждого узла из списка узлов к строке добавляется символ открывающей угловой скобки (<) и имя тега. Затем в открывающий тег добавляется каждый из атрибутов этого узла, и тег завершается символом закрывающей угловой скобки (>). После этого рекурсивным образом вызывается метод extractMarkupAsText, чтобы преобразовать все дочерние элементы текущего узла к текстовому формату. Если узел, обрабатываемый в цикле for, является текстовым, то он непосредственно добавляется к строке.
Листинг 6.38. Метод extractMarkupAsText (Util.java)
static public String extractMarkupAsText( NodeList nodeList ) { //recursively extract String text = ""; if( nodeList != null ) { for( int i = 0; i < nodeList.getLength(); i++ ) { Node node = nodeList.item(i); if( node instanceof Element ) { Element el = (Element)node; text += "<" + el.getTagName(); NamedNodeMap attList = el.getAttributes(); int length = attList.getLength(); for( int j = 0; j < attList.getLength(); j++ ) { Attr att = (Attr)attList.item( j ); text += " " + att.getName() + "='" + att.getValue() + "'"; } text += ">"; text += extractMarkupAsText( el.getChildNodes() ); text += "" + el.getTagName() + ">"; } if( node instanceof Text ) { text += ((Text)node).getData(); } } } return text; }
Следующие три метода, показанные в листинге 6.39, используются для отображения различных типов данных. Пустые объекты типа String, Integer и Float отображаются как null, а мы не хотели бы, чтобы пользователям приходилось видеть это на экране. Поэтому мы используем приведенные ниже вспомогательные методы, которые заменяют nul 1 на пустую строку.
Листинг 6.39. Методы notNull (Util.java)
static public String notNull( String s ) { if( s == null ) { return ""; } return s; }
static public String notNull( Integer i ) { if( i == null ) { return ""; } return i.toString(); }
static public String notNull( Float f ) { if( f == null ) { return ""; } return f.toString(); }
Следующий ряд методов, показанный в листинге 6.40, используется для преобразования строки, введенной пользователем (то есть объекта String), в некоторое значение. Мы хотим быть уверены, что, если пользователь оставил какую- либо форму незаполненной, мы получим значение null.
Листинг 6.40. Преобразование строки в значение (Util.java)
static public int getInt( String s ) { if( s == null || s.equals( "" ) ) { return 0; } return Integer.parseInt( s ); }
static public Integer getInteger( String s ) { if( s == null || s.equals( "" ) ) { return null; } return new Integer( s ); }
static public float getFloat( String s ) { if( s == null || s.equals( "" ) ) { return 0.0f; } return Float.parseFloat( s ); }
static public double getDouble( String s ) { if( s == null || s.equals( "" ) ) { return 0.0; } return Double.parseDouble( s ); }
}
Класс 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 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
public String[] getFiles(){ return files ; }
Для каждого выходного файла из опроса метод createFiles, показанный в листинге 7.25, вызывает метод makeXML, чтобы создать файл, у которого имеется открывающий и закрывающий теги
Листинг 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( "
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(); //
public String tableStyle = "align=\"center\" border=\"3\" " ; public String lastErr = null ; public int resultCt = 0 ; String id ; //
// 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
Обработка снимка опроса
Фактическая обработка файла, содержащего снимок результатов опроса, начинается с метода 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
Question: " + id + "
"); out.println("" + qtext.get(id) + "" ) ; out.println("
| Val | Count | Short Option Text | "); out.println("
|---|---|---|
| " + c.val + " | "); out.print("" + c.count + " | " ); out.println("" + 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)
Открывающий тег 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("
// assumes nowNOde is set to the first question // output form start and question text public void startForm(PrintWriter out ){ out.print("
"); }
Класс 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
В листинге 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
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("
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
"); } else { // use text from
Общественные организации
Многие считают, что частная информация подвергается такой же опасности при сборе данных, проводимых правительственными организациями, как и при опросах коммерческих организаций. Поэтому неудивительно, что существует несколько общественных организаций, деятельность которых связана с этой проблемой. Одной из таких организаций является 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
Листинг 7.13. Метод doOption (Interpretr.java)
// opN is from node list of
// look at the type and css attributes in
Отображение введения
В листинге 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("