Java Server Pages. Библиотека профессионала 5-8459-0290-8, 0-13-030704-1


260 38 18MB

Russian Pages 431 Year 2002

Report DMCA / Copyright

DOWNLOAD PDF FILE

Recommend Papers

Java Server Pages. Библиотека профессионала
 5-8459-0290-8, 0-13-030704-1

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

JAVASERVER PAG Использование сервлетов и JSP при проектировании и реализации гибких, расширяемых  приложений, удобных  в  сопровождении Мощные  средства аутентификации и  интернационализации Совместное  использование стандартов XML и XSLT и технологии JSP Применение  шаблонов JSP для разработки приложений на базе компонентов

Sun microsystems

ДЭВИД  М.  ГЕРИ Серия  Java™  2  Platform,  Enterprise  Edition

ББК  32.973.26-018.2.75 Г37 УДК 681.3.07 Издательский  дом  "Вильяме" Зав.  редакцией  С.Н.  Тршуб Перевод с английского и редакция ВВ. Вейтмана По общим вопросам обращайтесь в Издательский дом "Вильяме" по  адресу:  [email protected],  hitp://www.wilUamspubtishing.com Г е р ц Дэвид, М. Г37  Java  Server  Pages.  Библиотека  профессионала.  :  Пер,  с  англ.  -  М.:  Издательский дом "Вильяме", 2002. - 448 с.: ил.  -  Парал. тит. англ. ISBN  5-8459-0290-8  (рус.) Данная  книга  начинается  с  рассмотрения  пользовательских  дескрипторов,  т.е.  с  тех вопросов,  которыми  обычно  заканчиваются  книги,  представляющие  собой  введение  в JSP. В  ней  рассматривается  около  50  пользовательских  дескрипторов JSP.  Они  выполняют  различные задачи:  от поддержки форматов,  специфических для  разных стран, до  разбора XMLкода  с  использованием  Document Object  Model.  Поддержка  пользовательских дескрипторов — одно  из  главных  преимуществ JSP,  поскольку  данная  возможность  позволяет  организовывать  одновременную  работу  нескольких  специалистов,  при  этом  они  практически  не  зависят друг от друга. Далее  В  книге рассматриваются  HTML-формы, JSP-шаблоны,  архитектуры Mode!  1  и  Model  2,  поддержка  событий,  вопросы  безопасности,  работа  с  базами  данных  и XML.  В  последней  главе  продемонстрировано  использование  данных  технологий  при  создании  реального  Web-приложения.  Главной  целью  было  рассказать  читателю  о  том,  как  с помощью  компонентов  bean,  сервлетов  и JSP  создаются  гибкие  расширяемые  приложения, удобные  в  сопровождении. Данная книга написана для разработчиков, имеющих опыт использования языка Java и знакомых с серояетамн и JSP. Б Б К 32.973.26-018.2.75 Все  названия  программных продуктов  являются зарегистрированными торговыми марками  соответствующих  фирм. Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то  ни  было  форме  и  какими  бы  то  ни  было  средствами,  будь  то  электронные  или  механические, включая фотокопирование и запись на магнитный  носитель,  если на это нет  письменного  разреше-. ния издательства Prentice Hall, Inc. Authorized translation from the English language edition published by Prentice Hall, Ptr., Copyright © 2001 A31  rights  reserved.  No  part  of  this  book  may  be  reproduced  or  transmitted  in  any  form  or  by  any means,  electronic  or  mechanical,  including  photocopying,  recording  or  by  any  information  storage retrieval  system,  without  permission  from  the  Publisher. Russian  language  edition  published  by  Williams  Publishing  House  according  to  the  Agreement  with R&I Enterprises International, Copyright О 2002

ISBN  &-845ВД290-8  (рус.)  ISBN01M)3O7O4-l  (англ.) 

© Издательский дом "Вильяме", 2002 © Prentice Hall  Inc.,2001

Содержание Введение 

10

Глава 1. Основы построения пользовательских дескрипторов  17 Применение  пользовательских  дескрипторов  —  JSP-файл  19 Определение  пользовательских  дескрипторов  —  файл  описания  20 Реализация пользовательских дескрипторов — класс поддержки дескриптора  21 Ссылка на описание библиотеки в WEB-INF/web.xml  24 Элементы  и   24 Жизненный цикл пользовательского дескриптора  26 Организация взаимодействия потоков  27 Пользовательские дескрипторы с атрибутами  28 Доступ к информации о документе  32 Обработка ошибок  35 Класс],] для реализации пользовательских дескрипторов  35 Тело дескриптора  39 Глава 2. Дополнительные вопросы создания пользовательских дескрипторов  Обработчики тела дескриптора  Итерации  Переменные сценария  Тело дескриптора  Вложенные дескрипторы 

43 44 46 49 54 63

Глава 3. НТМЬформы  Формы и компоненты bean  Проверка корректности данных  Базовый набор классов для работы с формами  Применение пользовательских дескрипторов 

67 67 74 82 95

Глава  4. Шаблоны  Инкапсуляция  алгоритмов  компоновки  Необязательное содержимое  Содержимое, зависящее от роли  Раздельное определение областей  Вложенные области  Расширение областей  Объединение различных подходов к созданию областей  Реализация дескрипторов поддержки областей 

99 100 104 107 108 110 112 113 115

Содержание

7

Глава  5.  Разработка WEB-приложений Model I Model 2:  архитектура MVC Пример использования архитектуры Model 2

131

Глава 6.  Базовый набор классов для создания приложений Model 2 i Model 2 Базовый набор классов Model 2 Модернизация программ Учет новых сценариев развития Применение  пользовательских дескрипторов JSP-c цен ари и

149

Глава 7. Поддержка событий Поддержка событий в базовом наборе классов Model 2 Повторная активизация форм

17S

Глава 8.118N Unicode Кодировки Поддержка регионов Наборы ресурсов Форматирование данных,  чувствительных к региону Языки,  поддерживаемые броузерами Пользовательские дескрипторы 

197

Глава 9, Защита  Аутентификация  Базовая аутентификация  Дайджест-аутентификация  Аутентификация на основе форм  Использование SSL и сертификата клиента  Настройка процедуры аутентификации  Элементы защиты Х'УеЬ-приложений  Программная аутентификация 

235 235 239 242 242 246 246 251 254

Глава 10. Работа с базами данных  Создание базы данных  Источники данных  Пользовательские дескрипторы для работы с базами данных  Пул соединений  Предварительно  подготовленные  выражения  Транзакции  Прокрутка набора результатов 

267 268 270 271 283 294 300 303

Глава  11. XML  Генерация XML-документов  Обработка сгенерированных XML-данных  Разбор XML-кода 

309 311 316 317

132 133 134 149 157 162 167 169 174 178 197 199 201 202 212 219

223



Содержание Преобразование  XML-документов  Использование XPath 

347 356

Глава 12. Приложение на базе JSP 

363

Интерактивный магазин  Базовый набор классов Model 2  Интернационализация  Аутентификация  НТМЬформы  Повторная  активизация  чувствительных форм  SSL  XML  и  Приложение Л. Фильтры сервлетов  Пример  фильтра  Предметный указатель 

DOM 

364 385 403 409 420 427 428 429 435 436 439

ВВЕДЕНИЕ

В

скоре  после  того,  как  в  марте  1999  г.  был  опубликован  том  Graphic  Java,  посвященный  Swing,  я  заметил,  что Java-программы,  предназначенные  для  работы  на стороне  сервера,  приобретают  все  большую  популярность.  Пришлось  задуматься,  не  следует  ли  посвятить  мою  следующую  книгу  именно  этим  вопросам.  Несмотря на  то  что  все  мое  время  было  заполнено  увлекательными  экспериментами  с  XML, XSLT  и Java,  я  отдавал  себе  отчет  в  том,  что  эти  средства  играют,  скорее,  вспомогательную  роль  при  создании  Web-приложений.  Основной  технологией,  как  мне  тогда казалось,  являются сералеты. Надо  признаться,  сервлеты  не приводили  меня  в  восторг.  Я  недоумевал,  как могут разработчики  мириться  с тем,  что  им  приходится  создавать  интерфейсные  элементы, формируя  HTML-код  посредством  операций  печати.  С  1984  г.  я  участвовал  в  программных проектах,  где к моим услугам  были  объектно-ориентированные языки  и  инструментальные  средства  создания  пользовательских  интерфейсов.  Я  имел  опыт  разработки  приложений  на  Smalltalk,  Eiffel  и  NeXTSTEP  и  мне  казалось,  что  использование  HTML,  а  в  особенности  написание  программных  кодов  для  генерации  HTMLэлементов,  можно сравнить с  попытками  ездить на спортивном  автомобиле  по  песчаным насыпям. В  1999  г.  технология JSP  делала  свои  первые  шаги,  но  уже  тогда  можно  было  понять, насколько она перспективна. Благодаря JSP появилась возможность объединять Java  и  HTML  и  открылись  новые  перспективы  создания  Web-приложений.  Кроме  того,  в  спецификации JSP  1.0  одна  фраза  привлекла  особое  внимание.  Речь  шла  о  том, что  в  спецификации JSP  1.1  будет  предусмотрена  поддержка  расширяемых  дескрипторов;  эти  дескрипторы  можно  будет  применять  в  любом JSP-документе.  Разработчик получал  возможность  создавать  собственные  элементы,  инкапсулировать  в  них Javaкод  и  включать  в  документ  как обычные  дескрипторы.  Я  понял,  что  темой  моей  следующей книги будет JSP. Я  начал  работать  над  введением  в JSP  и  даже  написал  первую  главу,  но  тут  мне пришлось  пересмотреть  свое  решение.  На то  были  две  причины.  Во-первых,  в  изданиях,  рассчитанных  на  начинающих,  нет  недостатка,  а  я  не  хотел,  чтобы  моя  книга стала лишь одной  из многих.  Во-вторых,  первая  глава  получилась скучной,  а я  терпеть не  могу  скучных  книг.  Поэтому  я  решил  оставить  начатую  работу  и  написать  книгу, которую  вы держите  в  руках.

Введение 

11

О чем эта книга Как  видно  из  названия,  эта  книга о JavaServer  Pages,  в  частности  о  расширенных средствах,  предоставляемых  в  распоряжение  разработчика JSP-документов.  Главной целью было рассказать читателю о том,  как с помощью компонентов bean,  сервлетов и JSP создаются гибкие расширяемые приложения, удобные в сопровождении. Данная  книга  начинается  с  рассмотрения  пользовательских  дескрипторов,  т.е.  с тех вопросов,  которыми обычно заканчиваются книги, представляющие собой введение  в  JSP.  Поддержка  пользовательских  дескрипторов—  одно  из  главных  преимуществ JSP, поскольку данная возможность позволяет организовывать одновременную работу нескольких специалистов,  при этом они практически  не зависят друг от друга. Далее  в  книге рассматриваются  HTML-формы, JSP-шаблоны,  архитектуры  Model  1  и Model 2, поддержка событий, вопросы безопасности,  работа с базами данных и XML. В  последней  главе  продемонстрировано  использование  данных технологий  при  создании  реального Web-приложения.

API сервлетов и JSP Коды программ, приведенные в данной книге, соответствуют спецификациям Servlet 2.2 и JSP 1.1. Несмотря на то что проекты спецификаций Servlet 2.3 и JSP 1.2 появились в ноябре  2000  года,  к моменту  выхода этой  книги  в  печать  они  постоянно  дорабатывались.  Важным  дополнением,  которое  появилось  в  спецификации  Servlet  2.3,  стали фильтры сервлетов; они описаны  в приложении А.  Однако имейте в виду,  что к тому времени, как эта книга попадет к вам в руки, спецификация может измениться.

Как тестировались коды Все коды, приведенные в данной книге, были протестированы с помощью сервера Tomcat 3.2.I. Некоторые коды, в частности примеры, связанные с аутентификацией, некорректно работают с Tomcat S.2.1,  подобные случаи специально оговорены в тексте книги. Поскольку Tomcat является основным сервером для сервлетов  HJSP,  КОДЫ,  приведенные в этой книге, должны работать с любым контейнером сервлетов,  который соответствует Servlet  2.2  и JSP  1.1  (либо  более  поздним  версиям  этих  спецификаций). Если  пример  из  книги  некорректно  работает  с  вашим  контейнером  сервлетов,  причиной тому, вероятнее всего, является ошибка в реализации контейнера. Примеры  из  данной  книги  были  также  протестированы  с  помощью  контейнера сервлетов  Resin  1.2  (он  доступен  по  адресу  http://www.caucho.com).  Чтобы  убедиться, что код, написанный вами, работает корректно и является переносимым, желательно протестировать его с  помощью нескольких доступных вам контейнеров.

12 

Введение

На кого рассчитана эта книга Данная книга написана для разработчиков, имеющих опыт работы на языке Java и знакомых  с  сервлетами  и JSP.  Для  большинства  читателей  эта  книга  станет  второй книгой о сервлетах  HJSP,  прочитанной  ими.  Если же вы еще никогда не встречались с сервлетами и JSP, я рекомендую вам следующие книги для начинающих. • Core Servlets andJSP, Marty Hail, Sun Microsystems Press. • Java Servlet Programming, Jason Hunter, O'Reilly. • Web Development with JavaServer Pages, Fields и Kolb, Manning. Читателю не помешает также познакомиться с шаблонами проектов и UML (Unified Modeling Language— унифицированный  язык моделирования).  В этой  книге  используются диаграммы,  которые  показывают  взаимосвязь  между  классами.  Список  ресурсов, имеющих отношение к шаблонам проектов и UML, приведен в конце главы 6. Данная  книга  не  предназначена  для  авторов  Web-страниц.  Если  вы  создаете HTML-документы,  но  не  имеете  опыта программирования  на Java,  знакомство  с JSP вам лучше начать с одной из книг, перечисленных выше.

Как создавалась эта книга Создание объектно-ориентированных программ — это, как правило, итеративный процесс.  Вы  начинаете с  нескольких  классов,  добавляете  новые,  дорабатываете  созданные ранее, постоянно вносите изменения до тех пор,  пока не будет завершена работа над системой. Эту процедуру принято называть доводкой  (refactoring). Проработав  15 лет  программистом, я привык писать книги по тому же  принципу, что и  программы.  Каждая  глава  начиналась с нескольких "штрихов"  и  в процессе доводки выглядела так, как вы видите ее сейчас. Вы можете представить себе процесс доводки, ознакомившись с моей статьей, посвященной  JSP-шаблонам,  которая  была  опубликована  в JavaWoHd  ( h t t p : //developer. Java.sun.com/developer/technicalArticles/javaserverpages/jsp_templates) . Эта статья стала "исходным материалом" для главы 4 данной книги. Сравнив статью с главой 4, вы увидите, с чего начиналась работа над главой и чем закончилась. Как текст, так и коды были существенно переработаны.

Как пользоваться  этой  книгой Эта книга— не роман,  поэтому вряд ли  вы  сядете  и  прочтете ее "от корки до корки".  Поскольку многие  предпочитают читать  главы  в произвольном  порядке,  каждая глава написана так,  что она практически  не зависит от других.  Исключение составляет глава 6,  посвященная  Model 2. Архитектура Model  2 рассмотрена в главе 5,  поэтому эту  главу желательно  прочитать  перед главой  6. В  последней  главе  этой  книги  показано,  как  технологии,  рассмотренные  ранее, применяются  для  создания  Web-приложения.  Если  вы  хотите  составить  представление об  этих  технологиях,  просмотрите  последнюю  главу  перед тем,  как  приступать  к чтению.

Введение 

13

Библиотеки пользовательских дескрипторов В  данной  книге  рассматривается  около  50  пользовательских  дескрипторов JSP. Они выполняют различные задачи: от поддержки форматов, специфических для разных стран, до разбора XML-кода с использованием Document Object Model. Эти дескрипторы вы можете свободно использовать в своих разработках. Адрес,  по которому расположены коды дескрипторов, будет указан ниже, Рассмотрение  пользовательских дескрипторов  в данной  книге  преследует две  цели.  Во-первых,  приведенный  код  служит  примером  пользовательских дескрипторов. Во-вторых, они подтверждают основные идеи, которые обсуждаются в книге. Например, в главе,  посвященной поддержке кодировок и форматов различных стран, обсуждаются вопросы локализации текста, числовых значений, дат и денежных единиц. В конце этой главы показано, как реализуются пользовательские дескрипторы, предназначенные для выполнения этих задач.

Коды, приведенные в книге Коды, приведенные в данной книге, в том числе библиотеки пользовательских дескрипторов,  вы  можете  скопировать,  обратившись  по  адресу  http://www.ph.ptr. com/advj  эр.

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

Соглашение

Пример

Имена классов начинаются с прописной буквы

public class ClassName

Имена  методов  начинаются  со  строчной  буквы;  getLength остальные слова, входящие в имя метода,  начинаются с прописной буквы Имена переменных  начинаются  со  строчкой  буквы; остальные слова, входящие в имя переменной, начинаются с прописной буквы

p r i v a t e  i n t  length p r i v a t e  i n t bufferLength

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

14 

Введение

Таблица 0.2. Соглашения о представлении текста Шрифт 

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

c o u r i e r 

Команды,  имена  файлов,  имена  классов,  методы,  параметры, ключевые  слова Java,  HTML-дескрипторы,  текст в составе файла, фрагменты кода и URL

c o u r i e r  полужирный 

Текст,  введенный  в  командной  строке,  а  также  важные  фрагменты листингов

курсив 

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

Благодарности Несмотря на то что на обложке указано только мое имя, в создании данной книги участвовали  многие.  В  первую очередь я благодарен рецензентам  и  всем,  кто  прочитал книгу и сделал немало ценных замечаний. Ведущий  разработчик  Tomcat  и  базового  набора Apache  Struts  Грег  МакКленехэм (Craig McClanahan) поделился со мной рядом идей относительно сервлетов и JSP. Подобные  конструктивные  идеи  могли  прийти  в голову только специалисту чрезвычайно высокой квалификации, каковым является Грег. Скотт Фергюсон  (Scott  Ferguson),  разработчик контейнера сервлетов  Resin, также сделал  несколько ценных замечаний  и дал  ряд советов.  Благодаря  Скотту удалось не только  выверить технические детали,  но  и  выстроить материал данной  книги  в  последовательности,  наиболее удобной для восприятия читателем. Лэрри Кейбл (Larry Cable), соавтор исходной спецификации JSP, также внес существенный вклад в написание данной книги. Благодаря Лэрри в главе 2 появились разделы, в которых подробно обсуждается обработка тела пользовательских дескрипторов. Роб Гордон  (Rob Gordon), с которым мы  вместе работали в Sun Microsystems, дал мне  много  советов  о  структуре  книги, Java-кодах,  а  также  относительно  разделов,  в которых  рассматриваются  вопросы  объектно-ориентированного  проектирования. В этих советах нашла отражение высокая квалификация Роба. Я  благодарен  участникам  списка  рассылки,  посвященного  обсуждению  Struts,  за конструктивную  критику  пользовательских  дескрипторов,  которые  я  предложил включить  в состав Struts.  Благодаря  их  отзывам  я  получил  возможность  существенно улучшить библиотеку дескрипторов, которая рассматривается в главе 4 данной книги. Особенно  важно  для  меня  мнение  Седрика Дьюмоулина  (Ccdric  Dumoulin),  касающееся расширения библиотеки. Огромную помощью оказали мне Юн Сэнг Джанг (Yun Sang Jung) и Чем Джа Пинг (Chen Jia Ping), которые перевели английские файлы свойств на корейский и китайский языки. Их переводы были использованы в главах 8 и 12.

Введение 

15

Компания  Rational  Rose  Software  предоставила  мне  копию  Rational  Rose  for Java, которая была использована для подготовки UML-диаграмм, представленных в данной книге. Мэри Лоу Hop (Maty Lou Nohr),  с которой  мы работали еще в 1996 г. над книгой Graphic Java, проделала огромную работу по редактированию текста данной книги. Нельзя  не упомянуть Грега Донча (Greg Doench)  из Prentice Hall  и  Рэчел  Борден (Rachel Borden) из Sun Microsystems Press, которые верили в меня и помогли подготовить  книгу  к  печати.  Пэтти  Герриери  (Patti  Guerrieri)  из  Prentice  Hall  выполнила большую работу, в результате которой рукопись превратилась в книгу. Конечно же, я благодарен Лесэ (Lesa) и Эшли Анне Гери  (Ashley Anna Geary). Без их понимания и поддержки мне не имело смысла даже приступать к написанию книги. И, наконец, я признателен Блейзи, который постоянно был со мной во время работы.

От издательства Вы, читатель этой книги, и есть главный ее критик и комментатор. Мы ценим ваше мнение и хотим знать, что было сделано нами правильно, что можно было сделать лучше и  что еще  вы хотели бы увидеть изданным  нами.  Нам  интересно услышать  и любые другие замечания, которые вам хотелось бы высказать в наш адрес. Мы ждем ваших комментариев и  надеемся на них.  Вы можете прислать электронное письмо или  просто  посетить наш Web-сервер,  оставив свои замечания, — одним словом, любым удобным для  вас способом дайте нам знать, нравится или нет вам эта книга, а также выскажите свое мнение о том,  как сделать наши книги более подходящими для вас. Посылая письмо или сообщение, не забудьте указать название книги и ее авторов, а также ваш e-mail. Мы внимательно ознакомимся с вашим мнением и обязательно учтем его при отборе и подготовке к изданию последующих книг. Наши координаты: E-mail: 

infogwilliamspublishing. com

WWW: 

http://www.wi 11 iamspublishing.com

основы

ПОСТРОЕНИЯ ПОЛЬЗОВАТЕЛЬСКИХ ДЕСКРИПТОРОВ

В  этой  главе... Применение  пользовательских  дескрипторов  —  JSP-файл. Определение  пользовательских  дескрипторов  —  файл описания. Реализаций  пользовательских дескрипторов  —  класс поддержки дескриптора. Ссылка  на описание  библиотеки  в WEB-INF/web. xml. Жизненный  цикл  пользовательского дескриптора. Организация  взаимодействия  потоков. Пользовательские  дескрипторы  с  атрибутами. Доступ  к  информации  о документе. Обработка  ошибок. Классы  для  реализации  пользовательских дескрипторов. -  Интерфейс Tag. -  Класс TagSupport:  предки,  значения и идентификаторы. Тело дескриптора.

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

X

Radiohead OK  Computer $14.99

Подобно XML, JSP также можно использовать для  определения дескрипторов,  но если  XML-дескрипторы  представляют данные,  то  пользовательские  дескрипторы JSP представляют функции, специфические для конкретной области1.  Например,  в фрагменте JSP-кода,  представленном  в  листинге  1.1,  пользовательские  дескрипторы  используются для отображения таблицы базы данных. Листинг 1.1.  Применение пользовательских дескрипторов для доступа к базе данных Database  Example



1 Термин пользовательский дескриптор введен для того, чтобы отличать дескрипторы, созданные разработчиками, от встроенных дескрипторов. В XML встроенные дескрипторы отсутствуют, поатому необходимость в специальном термине отпадает. - Прим. авт.

18 

Глава 1. Основы построения пользовательских дескрипторов

  r



{

file.createNewFile(); saveCount  О;

Реализация пользовательских дескрипторов... 

23

private  String  getCounterFilename()  { HttpServletRequest  req  •=  (HttpServletRequest)pageContext. getRequest(}; String  servletPath  =  req.getServletPath ( ) ; String  realPath  =  pageContext.getServletContext[}. getRealPath(servletPath); return  realPath  +  ".counter"; ) private  void  saveCount0  throws  JspException  ( try  { FileWriter  writer  =  new  FileWriter  ( f i l e ) ; writer.write  (count); writer.close(); 1 catch(Exception  ex)  { throw  new  JspException(ex.getMessage()); private  void  readCount()  throws  JspException  { try  { FileReader  reader  =  new  FileReader(file); count  =  reader.read(); reader.close(); }

catch(Exception  ex)  i throw  new  JspException[ex.getMessage());

Показанный в листинге класс поддержки дескриптора подсчитывает число обращений к дескриптору, а следовательно, и к содержащему его JSP-документу. Информация о числе обращений хранится в файле. Имя этого файла совпадает с именем соответствующего JSPдокумента,  кроме того,  к  нему добавляется  суффикс  . c o u n t e r .  Например,  если  в файле /index, j s p  содержится  пользовательский  дескриптор,  реализующий  счетчик  обращений, информация о числе обращений хранится в файле / i n d e x , j s p . c o u n t e r . Подобно  многим  классам  поддержки  дескрипторов,  класс  t a g s . CounterTag  реализован  как  подкласс  TagSupport.  Класс  TagSupport  реализует  интерфейс  Tag, кроме того,  он  содержит ряд  вспомогательных  методов.  Подробно  класс  TagSupport будет  обсуждаться далее  в этой  главе. Метод CounterTag. d o S t a r t T a g  выводит число  обращений,  используя  для  доступа к объекту  o u t  переменную  pageContext,  определенную  в  классе  TagSupport  как  p r o t e c t e d . По окончании  выполнения метод возвращает значение  SKIP_BODY, указывающее  на то,  что  тело дескриптора,  если  оно  существует,  должно  игнорироваться.  После этого  контейнер  вызывает  метод  CounterTag.doEndTag,  который  возвращает  значение  EVAL_PAGE.  Данная  константа  означает,  что  контейнер  сервлетов  должен  обрабатывать остаток документа,  который следует за закрывающим дескриптором. Переменные  экземпляра  c o u n t  и  f i l e  инициализируются  при  каждом  вызове  метода d o S t a r t T a g ,  поэтому метод  r e l e a s e  в данном  классе  не  переопределяется.

24 

Глава 1. Основы построения пользовательских дескрипторов

Ссылка на описание библиотеки BWEB-INF/web.xml В JSP-файлс, представленном в листинге 1.2,а, директива t a g l i b использовалась для непосредстве!того обращения к описанию библиотеки дескрипторов. Эта директива может определять описание библиотеки косвенным образом, ссылаясь на другую директиву  t a g l i b ,  содержащуюся  в  дескрипторе  доставки  Web-приложения.  Например, директива t a g l i b  в листинге  1,2,а могла бы выглядеть следующим образом: //  В  JSP-файле  . . .

Элемент  t a g l i b дескриптора доставки Web-приложения  определяет  URI counters и задает расположение TLD-файла. //  в  файле  web.xml  . . .

counter3 /WEB-lNF/tldg/counter,tld

Косвенное  указание  описания  библиотеки  дескрипторов  обеспечивает  большую гибкость,  поскольку TLD-файл можно заменить,  не внося изменений eJSP-файл. Таким  образом,  при  доставке  приложений  следует  отдавать  предпочтение  косвенному указанию.  Прямое указание TLD  реализуется  проще  и,  как  правило,  используется  в процессе разработки.

Совет Создание простых пользовательских дескрипторов Пользовательские дескрипторы реализуются достаточно  просто,  особенно если  в них  не  предусмотрены  атрибуты  и.тело  дескриптора  отсутствует.  Для  создания пользовательского дескриптора надо выполнить следующие действия. •  Включить BjSP-файл, в котором используется дескриптор, директиву t a g l i b . •  Создать описание библиотеки дескрипторов (TDL-файл). •  Реализовать  класс  поддержки  дескриптора  как  подкласс  класса  TagSupport  и переопределить в нем методы doStartTag и doEndTag.

Элементы  и  В  данном  разделе  подробно  описываются  особенности  применения  элементов t a g l i b и  tag  в описании библиотеки дескрипторов  (TLD-файле).  Пример  использования этих дескрипторов см. в листинге  1.2,6. В табл. 1.1 перечислены элементы, входящие в состав t a g l i b .

Элементы  и  

25

Таблица 1.1. Элементы в составе t a g l i b {перечислены в том порядке, в котором они включаются в данный элемент) Элемент 

Тип  Описание

t l i b v e r s i o n 



jspversion 



shortname 



Версия библиотеки дескрипторов Версия  спецификации JSP,  используемой  при  создании  библиотеки. По умолчанию принимается спецификация JSP 1,1 Используется  средствами  авторизации для  идентификации библиотеки

u r i 



URI, однозначно идентифицирующий библиотеку

info 



Описание, поясняющее порядок использования библиотеки

tag 



Дескрипторы,  содержащиеся  в библиотеке

В столбце "Тип" использованы следующие обозначения: 1 — один элемент; ? — элемент может отсутствовать; • — один или более элементов. Для  определения  пользовательских  дескрипторов  служит  элемент  tag.  Пример  определения  дескриптора  counter  см,  в  листинге  1.2,6.  В  нем  заданы  имя  дескриптора, класс  поддержки,  сведения  о том,  что  тело  дескриптора  отсутствует,  а  также  информация о дескрипторе. Элементы, содержащиеся в составе  tag,  перечислены в табл.  1.2.

Таблица 1„2. Элементы в составе tag (перечислены в том порядке, в котором они включаются в данный элемент) Элемент 

Тип  Описание

name 



Имя, следующее за префиксом,  например 

tagclass 



Класс  поддержки,  реализующий  интерфейс Tag

teiclass 



Класс, определяющий переменные сценария для дескриптора

bodycontent 



Описание тела дескриптора: •  Дескриптор  самостоятельно  обрабатывает  содержащиеся в нем данные

info 



attribute 



• 

Контейнер сервлетов  обрабатывает тело дескриптора

• 

Тело  дескриптора должно  отсутствовать

Информация о дескрипторе Атрибут дескриптора

В столбце "Тип"  использованы следующие обозначения: 1 — один элемент;

26 

Глава  1.  Основы построения пользовательских дескрипторов

? — элемент может отсутствовать; * — нуль или более элементов. Атрибуты  определяются  с  помощью  элемента  a t t r i b u t e ,  который  будет  рассмотрен далее в этой главе. Элемент  bodycontent  указывает,  какие  действия  должен  выполнять  контейнер сервлетов с телом дескриптора.  По умолчанию  предполагается значение JSP,  в соответствии  с  которым  тело  дескриптора  должно  обрабатываться  контейнером.  Если элемент bodycontent  содержит значение  tagdependent, контейнер не обрабатывает тело дескриптора;  такую обработку  выполняет  класс  поддержки.  Значение  empty указывает на то,  что тело пользовательского дескриптора должно отсутствовать.

Жизненный цикл пользовательского дескриптора Класс  поддержки  пользовательского  дескриптора  представляет  собой  программный  компонент,  включаемый  в  контейнер сервлетов.  Контейнер создает  экземпляр класса поддержки,  инициализирует его и  вызывает методы doStartTag,  doEndTag  и release.  Для  простых  дескрипторов  инициализация  сводится  к  вызову  методов setPageContext  и  setParent.  Поскольку контейнер  вызывает  метод  r e l e a s e  класса поддержки, этот класс может быть подготовлен к последующему использованию. На  рис.  1.2  показана  диаграмма  взаимодействия  класса  поддержки  дескриптора, рассматриваемого в данной главе в качестве примера, с контейнером сервлетов. J^P  gpmamer

Pflrj^Contert

PWWTTH

.[spWrjler

gelujlQ prin!0

SKIP  вот

3 J

(JoEncfTagO EVAL_PAGE

re lease £

1 Рис.  1.2. Диаграмма взаимодействия для пользовательского дескриптора counter

Организация взаимодействия потоков 

27

Взаимодействие,  представленное  на  рис.  1.2,  типично  для  простых  дескрипторов без  атрибутов,  тело  которых  отсутствует.  Контейнер  сер  влетов  вызывает  метод doStartTag,  который  выполняет  требуемые  действия  и  возвращает  значение SKIP_BODY.  Значение  SKIP_BODY  указывает  на  то,  что  тело  дескриптора,  если  даже оно  присутствует,  ие должно  обрабатываться. Почти  для  всех  пользовательских  дескрипторов  метод  doEndTag  возвращает  значение  EVAL_PAGE,  в  результате  чего  контейнер  продолжает  обработку  остальной  части JSP-документа. После завершения метода doEndTag контейнер сервлетов вызывает метод  r e l e a s e ,  при  выполнении  которого  освобождаются  ресурсы  (например,  закрываются  соединения  с  базами  данных)  и  устанавливаются  необходимые  значения переменных. Для  более  сложных  дескрипторов  диаграммы  взаимодействия  также  имеют  более сложный  вид.  Например,  если  тело  дескриптора  должно  обрабатываться,  класс  поддержки  содержит  дополнительные  методы,  вызываемые  контейнером.  Диаграммы взаимодействия  для  дескриптора  с  атрибутами  и  дескриптора,  в  котором  предусмотрена многократная  обработка содержащихся  в  нем данных,  показаны  соответственно на рис. 1.5 и 2.2.

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

Таким образом,  в  каждый  момент  времени  с  дескриптором  может  работать лишь один  поток.  Если JSP допускает только  однопотоковое  выполнение,  то  при  реализации класса поддержки не обязательно принимать специальные меры для организации многопотокового  доступа  к  переменным  класса.  Конечно,  при  этом  надо  следить  за использованием  других  данных,  чувствительных  к  одновременному  обращению,  например атрибутам. Поскольку  контейнер  сервлетов  может  повторно  обращаться  к  классу  поддержки дескриптора,  необходимо  уделять  внимание  методу  r e l e a s e  и  инициализации  переменных  в  методе  doStartTag.  Например,  класс  поддержки,  приведенный  ниже,  работает корректно. public  class  TagHandler  extends  TagSupport  ( p r i v a t e  Hashtable  h a s h t a b l e ; public  irit  doStartTag (}  throws  JspException  { hashtable  =  new  Hashtable  0;

J public void released  { hashtable  =  null;

28 

Глава  1.  Основы построения пользовательских дескрипторов

Однако  при  повторном  обращении  к  следующему  классу  обработки  генерируется исключение, связанное с тем, что ссылка имеет значение n u l l . public  class  TagHandler  extends  TagSupport  ( private  Hashtable  hashtable; public  TagHandler()  { hashtable  -  new  Hashtable(); public  void  release()  { hashtable  -  n u l l ;

Пользовательские дескрипторы с атрибутами

Пользовательский  дескриптор  может  содержать любое  число атрибутов,  как  обязательных,  так  и  необязательных.  Атрибуты  задаются  с  помощью  выражения имл_атри6ута=знаценг1е_в_кавычках. Например, дескриптор с одним атрибутом выглядит следующим образом: < u t i l : i t e r a t e  times='4'> Значения атрибутов могут вычисляться в момент запроса. Пример такого атрибута приведен ниже. < u t i l l i t e r a t e  collection^'> Здесь атрибуту c o l l e c t i o n присваивается значение переменной. Для  того  чтобы  пользовательский  дескриптор  поддерживал  некоторый  атрибут, надо выполнить три дополнительных действия. 1. Добавитьтребуемый атр ибут к дес кр и пто ру в JSP-файле. 2.  Добавить элемент, соответствующий этому атрибуту, к TLD. 3.  Реализовать метод setAttra классе поддержки дескриптора. Здесь Лиг— имя атрибута, соответствующее соглашениям JavaBeans. Обычно  в  классе  поддержки  реализуют  также  метод  qetAttr,  таким  образом,  вложенные дескрипторы получают доступ ксвойствам. Чтобы  продемонстрировать  выполнение  перечисленных  выше  действий,  создадим простой пользовательский дескриптор с одним атрибутом. Если  на Web-странице  содержится  форма с  полями  редактирования,  то  при  очередном  отображении  формы  желательно  сохранять  в  полях  редактирования  текст, заданный  пользователем  ранее.  На  рис.  1.3  показана  Web-страница  с  регистрационной  формой,  которая  отображается  повторно,  если  пользователь некорректно  ввел данные.  В окис, расположенном слева,  показана форма,  в  которой  пользователь не заполнил одно из полей. В правом окне показана та же форма с сообщением об ошибке.  В полях редактирования сохраняются данные,  введенные ранее,  поэтому пользователю не надо повторно заполнять поля.

Пользовательские дескрипторы с атрибутами 

Please Register

Please Register

Ftrat  N»me: 

First Name:

|raph

La«l  Name:

Last Mams:

E-mail  Address:]

E-mail Address:

±1

29

Please fill  in  all of the fields above ч 



-  •

Рис.  1.З.  При повторном отображении формы данные 8 полях редактирования сохраняются

Первоначально  появляется  соблазн  решить эту задачу следующим  образом: • > Атрибут  v a l u e  HTML-дескриптора  inpflit  устанавливается  равным  значению  параметра  в  составе  запроса,  который  соответствует  данному  полю  редактирования. Теперь,  если  документ,  показанный  на  рис.  1.3,  будет  отображен  повторно,  содержимое полей  редактирования  сохранится. Однако  описанное  решение  имеет  суще- ;.•' Refill atoiP*oj-Mb«o*l*!HMtEi(*i«i ственный  недостаток.  Если  параметр  запроса,  соответствующий  полю  редактирования,  отсутствует  (такая  ситуация  возникает  при  первом  обращении  к  форме), Please Register метод  ServietRequest.getParameter вернет  значение  n u l l ,  которое  и  отобразится в поле. First  Name:  |null Last  Мэтпв:  (null Разрешить  эту  проблему можно,  реализовав  пользовательский  дескриптор,  который E-mail Address,  null возвращал  бы  параметр  запроса,  а  при  его отсутствии —  пустую  строку.  Фрагмент  JSPдокумента,  в  котором  применяется  такой дескриптор,  показан  в  листинге  1.3,а.  Пользовательский  дескриптор  r e q u e s t P a r a m e t e r содержит  один  обязательный  атрибут,  определяющий имя параметра запроса. Are. 1,4. Недостаток непосредственного вызова request.getParameter

30 

Глава  1. Основы построения пользовательских дескрипторов

Листинг 1.3,a. / r e g i s t e r . j э р t a g l i b  uri^'WEB-INF/tlds/htmL.tld'  prefix='html'%

< t d x i n p u t  type=  t e x t '  size=15  narae='  firstMame' value=' Item: 

Обе  реализации дескриптора  i t e r a t e  будут  обсуждаться далее.

44 

Глава 2. Дополнительные вопросы...

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

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

Интерфейс BodyTag Интерфейс BodyTag является расширением интерфейса Tag. По сравнению с интерфейсом Tag, в BodyTag объявлены следующие дополнительные методы: void doInitBody  [) int doAfterBody  [) void  setBodyContent(BodyContentl

Эти  методы  позволяют  реалиэовывать  итерации  и  обрабатывать тело дескриптора.  Контейнер сервлетов вызывает эти методы следующим образом: /7  Порядок  вызова  методов  BodyTag  контейнером  сервлетов if(tag.doStartTagO  ==  EVAL_BODY_TAG)  ( tag.setBodyContent(bodyContent); tag.doInitBodyO  ; do 1 //  Обработка  тела  дескриптора }

while(tag.doAfterBodyt)  == £VAL_BODY_TAG);

} Обращение  к  методу  doInitBody  производится  лишь  один  раз,  а  метод doAfterBody  может  вызываться  многократно.  Таким  образом,  пользовательские  дескрипторы  позволяют  организовывать  итерации.  Ниже  показан  дескриптор,  который реализует пять повторов.

Для  инициализации  цикла из  метода doInitBody  производится обращение  к атрибутам  from и  to  дескриптора  loop. До завершения  цикла  метод  doAfterBody  возвращает значение EVAL_BODY_TAG, а после окончания цикла — значение SKI P_BODY. Методы  doInitBody  и  doAfterBody  имеют доступ  к  телу дескриптора,  но  метод doInitBody вызывается до первой обработки содержимого.

Обработчики тела дескриптора 

45

В  табл.  2.1  описаны  значения,  возвращаемые  методами  интерфейса  BodyTag,  и действия, которые эти значения оказывают.

Таблица 2.1. Значения, возвращаемые методами интерфейса BodyTag Метод 

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

doStartTag  () 

EVAL_BODY_TAG:  обработка тела дескриптора  и  сохранение результата в  объекте  BodyContent SKI P_BODY: обработка тела дескриптора не производится

doAf  t e r T a g  ()  EVAL_BODY_TAG:  повторная  обработка  тела  дескриптора SKIP_BODY:  повторная  обработка тела дескриптора не производится doEndTag [) 

EVAL_PAGE:  обработка  части  документа,  следующей  за  закрывающим дескриптором SKIF_PAGE:  часть  документа  после  закрывающего  дескриптора не  обрабатывается

Класс  BodyTagSupport Практически  все  обработчики  тела  дескриптора  создаются  как  подклассы  класса BodyTagSupport,  реализующего  интерфейс  BodyTag.  Класс  BodyTagSupport  предоставляет как методы  класса  TagSupport,  так  и  методы,  объявленные  в  интерфейсе BodyTag, Кроме  того,  в  классе  BodyTagSupport  определены  дополнительные  методы,  перечисленные ниже. //  Класс  BodyTagSupport  является  подклассом //  TagSupport  и  реализует  интерфейс  BodyTag. //  В  нем  также  определены  следующие  дополнительные  методы: BodyContent  getBodyContent() JspWriter  qetPreviousOut() В  дополнение  к  методам,  унаследованным  от  BodyTag  и  TagSupport,  в  классе BodyTagSupport  добавлены  два  приведенных  выше  метода.  Метод  g e t B o d y C o n t e n t возвращает  тело  дескриптора,  а  метод  g e t P r e v i o u s O u t  —  выходной  поток,  связанный  с  родительским  дескриптором.  В  случае  дескриптора  верхнего  уровня  метод g e t P r e v i o u s O u t  возвращает  значение  предопределенной  переменной  o u t .  Подробно  эти  два  метода будут рассмотрены далее  в этой  главе. По  умолчанию  подклассы  класса  BodyTagSupport  единожды  обрабатывают  тело дескриптора.  Значения,  возвращаемые  по умолчанию  методами данного  класса,  приведены в табл.  2.2.

46 

Глава 2. Дополнительные вопросы...

Таблица 2.2. Значения, возвращаемые по умолчанию методами Класса BodyTagSupport Метод

Значение,  возвращаемое  поумолчанию

d o S t a r t T a g () 

EVAL_BODY_TAG:  обработка тела дескриптора

doAf  t e r T a g  ()  SKIP_BODY:  повторная  обработка  тела  дескриптора  не  производится doEndTag () 

EVAL_PAGE:  обработка  части  документа,  следующей  за  закрывающим  дескриптором

Итерации Обработчики  тела  дескриптора  содержат  встроенный  цикл  do-while,  что  позволяет  им  поддерживать  итерации.  Так,  например,  на  рис.  2.1  показан  JSP-документ, включающий  пользовательский  дескриптор,  который  перебирает  в  цикле  элементы вектора.

t  Sk 

&» 

lie» 

favorite 

look

tiers!ing over [one, two, three. Tour]  ... Item  one Hem:  Iwo Hem: throe Item,  four

Рис. 2.1. Дескриптор, поддерживающий итерации

~*y  L o ^  rtimd

JSP-код этого документа приведен в листинге 2.1 ,а. Листинг 2.1,a. /test.jsp An  Iterator

item 


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

iterate tags.IteratorTag tags.IteratorTaglnfo JSP

id tme t  rue

collection true true

Iterates  over  a  collection

Во-вторых, надо изменить класс дополнительной информации о дескрипторе так, чтобы  в нем могло  использоваться  значение  атрибута  id.  Класс дополнительной  информации для дескриптора i t e r a t e  выглядит следующим образом: public  class  IteratorTaglnfo  extends  TagExtralnfo  { public  Variablelnfo[]  getVariablelnfo(TagData  data)  { return  new  Variablelnfo[]  { new  Variablelnfo(data.getldO,  //  имя  переменной  сценария "Java,lang.Object",  //  тип  переменной true,  //  должна  ли  переменная  быть  создана Variablelnfo.NESTED)  //  область  видимости

54

Глава 2. Дополнительные вопросы...

Выполнив  эти  несложные  действия,  вы  обеспечиваете  возможность  именовать переменные  сценария  посредством  атрибута  id.  Классы  поддержки,  выполненные как  подклассы  TagSupport  или  BodyTagSupport,  не  нужно  модифицировать,  поскольку  класс  TagSupport  поддерживает  атрибут  id  и  содержит  соответствующий метод  s e t  Id.

Тело дескриптора В  процессе  работы  часто  возникает  не- в 1 Cw*«be I ч Ew4fe - МЁгаяП W e * E щкхя -tout обходимость  в  специальной  обработке  тела I  Obi £ddi yw Rauoriiii iced  №t> дескриптора,  например,  его  содержимое может  интерпретироваться  как  SQL-запрос, CAPITALIZE THIS STRING Класс  поддержки,  реализующий  интерфейс BodyTag,  имеет  доступ  к  содержимому  дескриптора.  Пример  документа,  включающего такой дескриптор, приведен на рис, 2.3, JSP-документ,  показанный  на  рис. 2.3, применяет  чолыкжатсльешй  дескриптор J c a p i t a l i z e  для  преобразования  текста,  со*-  Lor-J rt-w.f> держащегося  между  открывающим  и  закрывающим дескрипторами,  в  верхний  регистр. Рис.2.З. Пример обработхи содержимого пользовательского дескриптора Код документа  приведен  в листинге  2.3,а. Листинг 2.3,а. /test. Capitalize Tag Example

capitalize this string

Класс поддержки дескриптора capitalize представлен в листинге 2.3,6. ЛИСТИНГ2.3,6. /WEB-IKF/classes/tags/CapitalizeTag. Java package  tags; 

'

import javax.servlet.jsp.JspException; import  iavax.servlet.jsp.tagext.BodyTagSupport; public class CapitalizeTag extends BodyTagSupport { public int doAfterBody() throws JspException ! try ( String content - bodyContent.getstring();

Тело дескриптора  S t r i n g 

55

upper  =  c o n t e n t . t o U p p e r C a s e ( ) ;

bodyContent.clearBody(); bodyContent.print(upper); bodyContent.writeOut(getPreviousOut());

) c a t c h ( J a v a . i o . I O E x c e p t i o n  e)  { throw  new  J s p E x c e p t i o n ( e . g e t M e s s a g e ( ) ) ; 1 r e t u r n  SKIP  BODY;

В данном  классе  поддержки  дескриптора  переопределяется  метод  doAfterBody,  а переменная  bodyContent,  определенная  в  классе  BodyTagSupport,  используется для  получения  содержимого  дескриптора  в  виде  строки  символов.  Эта  строка  преобразуется  в  верхний  регистр,  и  содержимое  дескриптора  очищается.  Затем  метод doAfterBody  оформляет  преобразованную  строку  как  содержимое  дескриптора  и  записывает  в  поток ответа. Подобно  дескриптору  i t e r a t e ,  показанному  в  листинге  2.1,6,  дескриптор c a p i t a l i z e  записывает содержимое дескриптора  в  выходной  поток. Перед  тем  как  содержимое  дескриптора  становится  доступным  классу  поддержки, оно  обрабатывается  контейнером  сервлстов.  Например,  дескриптор  c a p i t a l i z e , представленный  ниже,  приведет  к  появлению  изображения,  показанного  на  рис.  2.3, поскольку  выражение  в  теле  дескриптора  обрабатывается  и  результат  обработки ( с т р о к а " c a p i t a l i z e  t h i s  s t r i n g " )  становится  доступной  классу  поддержки.

Подобное  поведение  пользовательского  дескриптора  обусловлено  тем,  что  в  описании  библиотеки  дескрипторов  по  умолчанию  используется  значение JSP  элемента bodycontent.

capitalize tags.CapitalizeTag JSP

Иногда бывает необходимо, чтобы содержимое дескриптора не обрабатывалось, например, если оно должно интерпретироваться как SQL-запрос. В таких случаях надо указать значение" дескриптора bodycontent, равное tagdependent.

capitalize tags.Capital!zeTag tagdependent

56

Глава 2. Дополнительные вопросы...

Особенности обработки тела дескриптора Если  вы хотите  реализовывать  пользовательские  дескрипторы,  обрабатывающие свое  содержимое  (такие  как  рассмотренные  выше  дескрипторы  c a p i t a l i z e  и i t e r a t e ) , вам надо составить ясное представление о том, что такое тело дескриптора и  как  оно  обрабатывается  контейнером  сервлетов.  Обсуждению  этих  вопросов  посвящен данный раздел. Тело дескриптора представляется  классом  BodyContent,  который  создан  на  базе буферизованного  выходного  потока.  Благодаря  наличию  буфера  вы  можете  выполнять  различные  действия  с  данными,  содержащимися  в  составе  дескриптора.  Диаграмма классов для BodyContent показана на рис. 2.4. Writer /

I.

BodyTagSupport

•doSlartTagQ *doEndTagO *5 91 Bod yContent 0 *d ofmi Bod f 0 ^duAfl 6 r8 ody 0 *ielease0

^•BodyContenifl ftodyConleni

*clearBodyO •getfieadsrj) •gelStringO *writBOwO *get Enc 1 osrngWnl efO

•jelPieviousOulfl

Рис. 2.4. Диаграмма классов для BodyContent

Класс  BodyContent  является  подклассом  JspWriter,  ссылка  на  который  содержится в предопределенной переменной out. Если в JSP-документе вы используете для вывода  информации  переменную  out,  данные  непосредственно  попадают  в  выходной поток,  связанный  с ответом  на запрос.  В пользовательском дескрипторе  выходные данные передаются экземпляру класса BodyContent. Контейнер сервлетов поддерживает стек объектов BodyContent так,  что вложенные  дескрипторы  не  заменяют  содержимое  родительского  дескриптора.  Каждый объект BodyContent  содержит ссылку на буферизованный  выходной поток, соответствующий  объекту,  расположенному  под ним в  стеке.  Этот поток  принято  называть выходным потоком предыдущею дескриптора (previous out), или включающим выходным

потоком (enclosing writer). Для обращения к нему используются методы BodyContent. getEnclosingWriter или BodyTagSupport. getPreviousOut.

Тело дескриптора 

57

Рассмотрим, как контейнер сервлетов обрабатывает объекты BodyContent. He зная этого, трудно понять, какой из потоков следует использовать для вывода модифицированного содержимого дескриптора и какие методы класса BodyTagSupport  следует переопределить. На примере простого JSP-документа, содержащего пользовательские дескрипторы  (листинг  2.4,а),  продемонстрируем действия,  которые  выполняет  контейнер сервлетов над содержимым пользовательского дескриптора. Листинг 2.4,a.  /teat.jap Body  Content

BODY  Kbr>

BODY  2




Вложенные дескрипторы printBody выводят данные, показанные на рис. 2.5. -lalxtl

i- • Bo* Content - Меток* Internet ЕмгЛмы |  pk 

Edit 

git* 

Fefflritei  .jpolj 

Help

Рис.  2.5.  Документ,  содержащий  два  вложенных пользовательских дескриптора

Как видно из рисунка, дескриптор printBody выводит содержащийся в нем текст. Класс поддержки дескриптора printBody представлен в листинге 2.4,6. ЛИСТИНГ 2.4,6. /WEB-INP/clasees/tags/PxintBodyTag. Java package  tags; import  import 

javax.servlet.jsp.JspException; javax.servlet.jsp.tagext.BodyTagSupport;

public  class  PrintBodyTag  extends  BodyTagSupport  { public  int  doftfterBody(}  throws  JspException  (

58 

Глава 2. Дополнительные вопросы...

try ( getBodyContent().writeOut(getPreviousOut()}; ) catch (Java,io.IOException e)  ( throw new JspException(e.getMessage()); ) return SKIP_BODY;

Как  видите,  код  класса  занимает  всего  несколько  строк,  но  разобраться  в  нем  непросто,  поскольку  непонятно,  почему данные  выводятся  в  выходной  поток  предыдущего  дескриптора.  Рассмотрим  JSP-документ,  представленный  в  листинге  2.4,а,  с  точки зрения контейнера сервлетов. 1   < % —  pageContext.pushBody()*  — % > 2 BODY  K b r >   3 BODY 2
  2   1 *  Вызывается  контейнером  сервлетов  после  обращения  к  d o S t a r t T a g ( ) **  Вызывается  контейнером  сервлетов  после  обращения  к  методу doAfterTagO  ,  но  перед  вызовом  doEndTagO До  появления  включающего,  или  внешнего,  дескриптора  p r i n t B o d y ,  предопределенная  переменная  ссылается  на  объект  J s p W r i t e r ,  посредством  которого  передается  ответ  на запрос.  Присвоим  этому  состоянию  номер  1.  На  рис.  2.6 два дескриптора p r i n t B o d y условно обозначаются  как внешний  и  внутренний. После  того  как  контейнер  сервлетов  вызывает  метод  d o S t a r t T a g  внешнего  дескриптора printBody,  он записывает в предопределенную переменную o u t  ссылку на экземпляр  класса  BodyContent.  В  этом  объекте  содержится  ссылка  на  J s p W r i t e r ,  который  в данном  случае  играет  роль  выходного  потока  предыдущего дескриптора.  В  этом состоянии  (ему  присвоен  номер  2)  стек  выходных  потоков  насчитывает два  элемента (рис. 2,6).  Стек формируется  посредством  контекста документа,  в частности с  помощью метода  PageContext. pushBody.  Метод  pushBociy  вызывается  контейнером  сервлетов сразу после  вызова  метода d o S t a r t T a g  внешнего дескриптора p r i n t B o d y . Встретив  внутренний  дескриптор  p r i n t B o d y ,  контейнер  сервлетов  снова  вызывает  метод  PageContext.pushBody.  Теперь  стек  содержит  объект  BodyContent внутреннего  дескриптора,  объект  BodyContent  внешнего  дескриптора,  а  также  объект  J s p W r i t e r .  Этому  состоянию  присваиваем  номер  3.  Заметьте,  что  выходным  потоком  предыдущего  дескриптора  для  внутреннего  p r i n t B o d y  является  объект BodyContent  внешнего  p r i n t B o d y .  Аналогично,  выходным  потоком  предыдущего дескриптора  для  внешнего  p r i n t B o d y  является  объект  J s p W r i t e r ,  посредством  которого  выводится  ответ  на  запрос.

Тело дескриптора 

Извлечение из стека

59

Обработка теш дескриптора и запись в стек

Содержимое внешнего дескриптора

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

Содержимое вн/треннего дескриптора Содержимое внешнего дескриптора

Рис.  2.6.  О б р а б о т к а  тела  д е с к р и п т о р а

После  выполнения  метода  doAf terBody  внутреннего  дескриптора  p r i n t B o d y ,  но перед  выполнением  метода  doEndTag  этого  дескриптора  контейнер  сервлетов  вызывает  метод  P a g e C o n t e x t . popBody.  Метод  popBody  извлекает  из  стека  текущий  объект BodyContent,  в результате  чего восстанавливается  состояние 2  (см.  рис.  2.6). Поскольку  контейнер  сервлетов  вызывает  PageContext  .popBody  между  вызовами  методов  doAf  t e r B o d y  и  doEndTag  дескриптора,  метод  doAf  t e r B o d y  внутреннего дескриптора  p r i n t B o d y  выполняется  в  состоянии  3,  а  метод  doEndTag  этого  дескриптора —  в состоянии  2.  Не  зная  этой особенности,  трудно  организовать обработку тела  дескриптора. Наконец,  после  выполнения  метода  doAf  t e r B o d y  внешнего  дескриптора  контейнер сервлетов  выбывает метод  P&geContext. popBody. Теперь  становится  ясно,  почему  в  методе  PrintBodyTag.  doAf  terBody  данные  выводятся  в  выходной  поток  предыдущего дескриптора.  Так  происходит потому,  что  при вызове  doAfterBody  объект  BodyContent  еще  находится  в  стеке.  В|гутреи1ШЙ  дескриптор  p r i n t B o d y  записывает данные  в  объект  BodyContent  внешнего  дескриптора. Внешний  p r i n t B o d y  записывает  свое  содержимое  и  данные,  переданные  внутренним printBody,  в  объект  JspWriter,  Если  бы  метод  PrintBodyTag. doAfterBody  записывал данные с  помощью  предопределенной  переменной  out,  они  бы  попали  в текущий объект и несколько позже были бы  вместе с ним удалены из стека. На  первый  взгляд  может  показаться,  что  в  классе  PrintBodyTag  удобнее  было  бы переопределить  метод doEndTag  и  выводить данные,  пользуясь  предопределенной  переменной out. Действительно, к момент)' вызова doEndTag объект BodyContent, соответствующий  текущему  дескриптору,  уже удален  из  стека,  к  переменная  o u t  ссылается

60

Глава 2. Дополнительные вопросы...

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

Совет Не следует обращаться к объекту BodyContent из метода doEndTag В тот  момент,  когда  контейнер  сервлетов  вызывает  метод  doEndTag,  объект  BodyContent,  согласно  спецификации JSP  1.1,  доступен для  повторного использования. Поэтому  при  обращении  к  содержимому  дескриптора  из  метода  doEndTag  есть опасность обратиться  к телу другого дескриптора или к объекту n u l l . Обработку  тела  дескриптора  лучше  выполнять  в  методе  doAf  terBody,  который вызывается  до  того,  как  объект  BodyContent  станет  доступным  для  повторного испол ьзован 11 я.

Генерация JavaScript-кода В  данном  разделе  рассматриваются  пользовательские  дескрипторы,  которые оформляют  содержащиеся  в  них  данные  в  виде  HTML-элементов  и  добавляют  к  ним фрагменты  JavaScript-кода.  Возможность  пользовательских  дескрипторов  генерировать  JavaScript-фрагменты  позволяет  объединить  преимущества  программ,  выполняющихся  на стороне  клиента  и  сервера. На  рис.  2.7  показан JSP-документ,  в  котором  тело  пользовательского  дескриптора представляется  в  виде  HTML-элемента  SELECT.  Дескриптор  генерирует  элемент SELECT  и  после выбора пункта списка передает данные  на сервер.  Подобным образом удобно  оформлять  набор  ссылок. В окне,  представленном слева на рис.  2.7,  показан  процесс выбора пункта списка,  а в правом окне — результат выбора. .  -Ю1 х! flkt

ylew 

Fjrt>rlr«  Ioolt

View Documentation for

a Doc* 

"  f""' 

-v

11  It»  JMOI  As».  MeotdtWtmet  E  tftaa  -iDlxl j £ib frill Klew Ffcuornn  1»Ь  №*Щ

OocumBrrtalionforJNDl

J

Рис.  2.7.  Использование дескриптора  l i n k s

Тело дескриптора 

61

Код JSP-документа представлен в листинге 2.5,а. Листинг2.5,a. /teat.jsp Java Api Document a tion

  View  Documentation  for servlets swing JDBC "  + body  +  " < / s e l e c t > " ) ; b o d y C o n t e n t . p r i n t ( b u f f e r . t o S t r i n g ( )  )  ; bodyContent.writeOut(getPreviousOut()); } c a t c h ( J a v a . i o . l O E x c e p t i o n  ex)  { t h r o w  new  J s p E x c e p t i o n ( e x . g e t M e s s a g e ( ) ) ; ) r e t u r n  SKIP_BODY;

Подобно  HTML-элементу  s e l e c t ,  дескриптор  l i n k s  может  содержать  атрибут name  (атрибуты  s i z e  и  m u l t i p l e  в  дескрипторе  l i n k s  не  поддерживаются).  Для поддержки  этого атрибута  в  классе LinksTag  содержится свойство,  соответствующее соглашениям JavaBeans. Вызывая  BodyContent. g e t S t r i n g ,  метод  doAf terBody  получает  содержимое дескриптора, представленное в виде строки. Затем объект BodyContent очищается и формируется  буфер,  содержащий  старое  тело  дескриптора,  помещенное  между  открывающим  и закрывающим HTML-дескрипторами  s e l e c t .  После этого содержимое буфера помещается  в BodyContent,  а затем  записывается  во  включающий  выходной поток. В результате генерируется следующий фрагмент кода: < s e l e c t 

n a m e = ' a p i ' 

onChange='this.form.submit()'>

s e r v l e t s < / o p t i o n > jsp swing

Б  предыдущем  фрагменте  кода  в  качестве  значения  свойства  p rop e rty  указан символ  '*'.  При  этом  используется Java-отражение  и  устанавливаются  свойства  компонента, соответствующие параметрам запроса. Например, при получении параметра с именем name вызывается метод компонента setName, В  последующих  двух  разделах  описываются  особенности  применения  действия j sp: useBean для хранения состояния формы  в компоненте bean. Далее будет обсуждаться хранение состояния элементов формы  в отдельных компонентах; этот подход более сложен в реализации, но допускает повторное использование кода.

Совет Формы, компоненты bean и образ Memento В соответствии с образом разработки Memento состояние сохраняется вне объекта, что позволяет восстановить его. Реализовать образ разработки Memento для HTMLформ можно, помещая данные формы в один или несколько компонентов bean.

Поля редактирования, текстовые области и переключатели опций В данном  разделе рассматривается работа с полями  редактирования,  текстовыми областями  и  переключателями опций.  Эти  элементы  объединены здесь  потому,  что при активизации формы каждый из них генерирует единственный параметр запроса. Элементы,  которые  могут генерировать несколько  параметров,  будут  рассмотрены  в следующем  разделе. В JSP-доку менте, показанном на рис. 3.1, содержатся поля редактирования, пере ключатели опции и текстовая область. В форме, которая находится в этом документе, отсутствует  атрибут  action,  поэтому  после  щелчка  на  кнопке  Submit  Query  форма повторно отображается на экране. В левом окне на рис. 3.1 показана Web-страница до активизации формы,  а в правом окне— эта же страница после того,  как форма была активизирована.

Формы и компоненты bean 

•• and Had»ВЛ«и-Мсго»пм. . |П| х| Qk 

pftt 

^lew 

favoritej 

ioolj 

Utlp

Мате- [Ralph

69

I. TBJ Ai«t. «id Had» Buawn • МсютП InL.. . lOl xj £ile

Bill

VFB»

FawrltK

loot

blip

Name: [Ralph

t~viss  f~ master catd

^vtsa ^ mastej card

^  discovery  ^  amencari

^ dtscovefy

^ amerJcan

1 Submit Query

Submit Queiy

I

name: Ralph commanls: Enter comments cradle, disc

name: ca mm ants: Enlur comments credit *-;  Lottrf  » w

Tt Locot n

Рис.  3. f.  Сохранение данных формы  в компоненте bean

При  повторном  отображении  формы  ее  элементы  инициализируются  значениями,  хранящимися  в  компоненте  bean.  JSP-код  документа,  показанного  на  рис. 3.1, представлен в листинге 3.1,а. Листинг3.1,a. /form.jsp Textfields,  Text  Areas,  and  Radio  Buttona



Name:





Как  и  документ,  показанный  в  листинге  3.1,  данный JSP-код  создает  компонент bean типа beans . Form и инициализирует его в соответствии с параметрами запроса. После  этого  данный  компонент  используется  для  определения  состояния  флажков опций и пунктов списка. Как  и  в  рассмотренном  ранее  примере,  в  состав  данного  документа  включается JSP-файл,  в  котором  содержится  код,  предназначенный  для  отображения  значений формы. Этот файл представлен в листинге 3.2,6. Листинг 3.2,6. /showForm. j if[form.getCategories()  !=  null)  (  %> find: dates : 



76 

Глава 3. HTML-формы

  F i r s t  Name: f Last Name: E-mail Address:





| I  d o t E d u . t e s t ( s ) ) ;

В  качестве  значения  атрибута  onSubmit  формы  указано  выражение  ' r e t u r n v a l i d a t e ()  ', т.е. при активизации формы вызывается JavaScript-функция v a l i d a t e . Если  функция  v a l i d a t e  возвращает  значение  true,  данные  формы  передаются  на сервер, в противном случае отображается диалоговое окно с сообщением об ошибке. Как и в предыдущих примерах, значения формы сохраняются в составе компонента bean. Поскольку этот компонент не используется при проверке корректности данных и так как подобные компоненты были рассмотрены ранее, код bean здесь не приводится. При необходимости вы можете скопировать код компонента вместе с кодами других примеров,  рассмотренных  в  данной  книге,  обратившись  по  адресу  http://www. phptr. сотп/advj sp.

Проверка корректности данных 

77

Документ,  представленный  в  листинге  3.3,  демонстрирует  совместное  использование JSP  и JavaScript,  Поскольку JSP-контейнер  передает  HTML-кол  в  неизменном виде,  текст  JavaScripL-сценария  можно  непосредственно  включать  в  состав  JSPдокумента.

Проверка на стороне сервера с помощью JSP-д окументов Проверка  корректности  данных  на  стороне  сервера  выполняется  с  помощью JSPдокументов  или  сервлетов.  Здесь  рассматривается  использование  JSP-документов,  а проверка  посредством  сервлетов  будет  обсуждаться  в  следующем  разделе. На  рис, 3,4  показан  JSP-документ,  содержащий  форму,  аналогичную  форме  на рис, 3,3.  В левом окне на рис. 3.4 показано состояние документа перед щелчком на кнопке Submit Query, а в правом окне — состояние документа после активизации формы. 0  I S H V »  SWsVAiatiwi  v«i JSP - Micunoft lite ™чЕч>ки1  i  [lie

£dll

Firtl  Name: 

yitw

fffrtjrlm

ЕЭ

Itotr  t№V

|  j £ii>

Edit

in*

Favorittt

loois

.lalx|

|

; ; A * * » H | « ] |tp49iN.i».o>aoItl«IN«H.l

Please fill in all fields. Email addles; must contain @ and end in  .cum sr.edu

Igeorge

L>!t  Hum  Г

ii

E-mail Address: Г



First Name: \ gen  где La  si  Name  |

Submit  Query  |

Б-mail Address:  [ SubmitQueiy  |

d Г Local inli«nd

Рис, З.4.  Использование  JSP-документов  дли  проверки  корректности  данных  на  стороне сервера По  сравнению с кодом,  представленным  в листинге  3.3,  данный  код имеет два отличия.  Во-первых,  в  нем отсутствует JavaScript-сценарий,  а во-вторых,  в качестве значения  атрибута a c t i o n  формы  задано  значение  v a l i d a t e ,  jsp.

При  активизации  формы  запрос  передается  документу  v a l i d a t e , jsp,  код  которого показан в листинге 3.4. Листинг 3.4.  /validate.jsp String  first  =  request.getParameter("firstName")  , String  last  «  request.getParameter{"lastName");

78 

Глава 3. HTML-формы

String email = request.getParameter("emailAddress"); String  errorMsg  =  ""; boolean errorDetected =  false; if(first.equals["")  II  last.equals("")  M  email.equals(""))  { errorMsg +=  "Please fill  in all  fields."; errorDetected = true; } if(email.indexOf("g")  = = - 1  || (!email.endsWith(".com") fiS lemail.endsWith(",edu"))}  ( if(errorMsg.length()  > D) errorMsg +=  "
"; errorMsg +=  "Email  address must contain 8  and  " + "end in  .com or  .edu"; errorDetected =  true;

} if(errorDetected)  { %>



Рассматриваемый JSP-докумспт получает данные формы посредством предопределенной  переменной  request  и  использует  для  проверки  те  же  критерии,  что  и JavaScript-сценарий, приведенный в листинге 3.3. Если данные формы корректны, запрос перенаправляется документу registrationComplete . j зр, в противном случае отображается Web-страница с сообщением об ошибке, включающая форму ввода, которая присутствовала в исходном документе.

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

Сервлет, на который ссылается данный фрагмент кода, приведен в листинге 3.5. Листинг 3.5./WEB-INF/clasaes/ValidationServlet.java import  Java.io.lOException; import j avax.servlet.RequestDispatcher; import  javax.servlet.ServletException;

Проверка корректности данных 

79

import  javax.servlet.http.HttpServletRequest; import  javax.servlet.http.HttpServletResponse; import  javax.servlet.http.HttpServlet; public class ValidationServlet extends HttpServlet  ( public void  service(HttpServletRequest  req, HttpServletResponse  res) throws IOException,  ServletException { String first = req.getParameter("firstName"), last « req.getParameter("lastName"), email = req.getParameter("emailAddress"), errorMsg =  "",  nextStop =  "/registrationComplete.jsp"; boolean errorDetected =  false; if(first.equals("")  II last.equals("")  II email.equals("")) { errorMsg +=  "Please fill  in all  fields."; errorDetected =  true;

1

if(email.indexOf("@")  =- -1  |[ (!email.endsWith(".cam") £fi !email.endsWith(".edu"))) { ifferrorMsg.length() > 0) errorMsg += "
"; errorMsg += "Email address must contain @ and " + "end in .com or ,edu"; errorDetected = true; > if(errorDetected) ( res.getWriter().print(errorMsg); nextStop = "/form.jsp"; ) RequestDispatcher rd; rd - getServletContext().getRequestDispatcher(nextStop); if[nextStop.equals("/form.jsp")) rd.include[req, res); else rd.forward(req, res) ;

СераяC-T,  представленный  в листинге  3.5,  выполняет те  же  функции,  что  и JSPдокумент,  код которого был приведен  в листинге 3.4. Для того чтобы  исключить Javaкод  из JSP-документа,  желательно,  чтобы  проверка  корректности  данных  выполнялась посредством  сервлетов или  компонентов bean. Дополнительную  информацию о проверке корректности и использовании для этой цели сервлетов и JSP вы найдете в конце данной главы.

80 

Глава 3. HTML-формы

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

Использование сервлетов и JSP-документов для проверки на стороне сервера В  предыдущих  разделах  рассматривалась  проверка  данных  на  стороне  сервера, выполняемая либо с помощью сервлетов, либо с помощью JSP. Ни одно из этих решений нельзя назвать идеальным, потому что в первом случае сервлет должен генерировать  HTML-код,  а  во  втором  случае JSP-документ должен  содержать  код,  выполняющий  проверку.  Поскольку сервлеты  используются  как  контроллеры,  а jSP-документы выполняют  роль  просмотра,  сервлеты  не  должны  заниматься  отображением  содержимого, а JSP-докумснты не должны содержать бизнес-логику. Гораздо лучшим будет решение,  при котором сервлеты содержат логику проверки, а JSP-документы используются для отображения сообщений об ошибках. Сервлет, показанный в листинге 3.5, легко модифицировать так, чтобы он вместо вывода сообщения об ошибке сохранял его в области видимости запроса. Фрагменты кода модифицированного сервлета показаны ниже. public  class  ValidationServlet  extends  HttpServlet  { public  void  service(HttpServletRequest  req, HttpServletResponse  res) throws  IOException,  ServletException  { String  f i r s t  =  req.getParameter("firstName"), last  =  req.getParameter("lastName"), email  «  req.getParameter("emailAddress"), errorMsg  »  "",  nextStop  •  'VregistrationComplete.jsp"; boolean  errorDetected  =  false; if(errorDetected)  { r e q . s a t A t t r i b u t a ( " v a l i d a t e - e r r o r " ,  nextStop  «  "/form.jsp"; )

)

errorMsg);

Проверка корректности данных 

81

Поскольку сервлет сохраняет сообщение об ошибке в области видимости запроса, JSP-документ имеет доступ  к этому сообщению  и  может  извлечь  его с  помощью следующего скриптлета: Server  Side  Validation







Если  вы  хотите  удалить  данный  скриптлет  из  состава JSP-документа,  то  должны реализовать  пользовательский  дескриптор,  отображающий  сообщение  об  ошибке. Этот дескриптор использовался бы следующим  образом: Server  Side  Validation

ќ«validate: showValidateError/>

Если  в  качестве  значения  атрибута  property  дескриптора  j sp : s e t P r o p e r t y  задан  символ  '*', JSP-контейнер  использует Java-отражение  для  установки  свойств  компонента  bean  в  соответствии  с  параметрами  запроса.  Например,  для  параметра  запроса  с  именем  category JSP-контсйнср  обращается  к  методу  setCategory.  Если этот  метод  существует, JSP-контейнср  вызывает  его  и  передает  значение  параметра category. Такие же действия предпринимаются для остальных параметров запроса. Применение Java-отражения - простой способ установки свойств beans, однако он имеет  существенный  недостаток:  если  а  разных  формах  используются  различные имена элементов  (а обычно дело обстоит  именно так),  для  каждой  из форм  необходимо создавать свой Java-класс. Несмотря на то что для разных форм создаются различные JSP-классы, коды этих классов  во многом  совпадают.  Совпадающие фрагменты  кода  могут  использоваться  в различных компонентах bean. Рассмотрим следующий фрагмент кода (он входит в состав компонента, представленного в листинге 3.2,с): ... private  String!]  categories; щ

public  String  categorySelectionAttr(String  category)  1 if [categories  != null)  ( for{int i=0;  i  Далее  следует  создать  документ  v a l i d a t e , j sp,  код  которого  содержится  в  листинге 3.8,а.

92 

Глава 3. HTML-формы

l  '

NVM:  I ™ "

Pr&grammlng  Ljnguaoa  E a p e i ^ n t t

The  form  was  not  filled  out  correctly  because

Г  Him  r j m T  Pari  Г  jip Ntma  caw.fll  contj.n Commims

С radii  Cditf must  Ьв  li

Credd  Cird:

r  vii

i

1"  maste»  Call



ЕирМплО»! fttyW * ода

Г  UntJ  Г J^«  r p d T J i p CDmmenls Cndil  Card:

discovery

SitM i Fnft r

 

r

 

O-VtD • ЕБ/C-D  *

matter  card

j

dtaccvary

EKpirsiiu^Data.

5 F * C  a  Prun

oppl»  il owo_ ow SutifrJUXmy  |

Рис. З.8. Проверка данных формы ЛистингЗ.в.а. /validate.jsp



The  form  was  not  f i l l e d  out  correctly  because:





В  дшсументе  v a l i d a t e , j s p  создается  компонент  bean  с  именем  form.  Свойства этого компонента устанавливаются в соответствии со значениями параметров запроса.  Созданный  компонент  используется  при  проверке  корректности  данных,  содержащихся  п элементах формы.  Если данные формы  недопустимы,  отображаются  сообщение об ошибке,  полученное из bean,  и  форма ввода.  Если данные  формы  корректны, управление передается другомуJSP-документу.

Базовый  набор  классов  для  работы  с  формами  93 Поскольку документ  v a l i d a t e . j sp  использует  дескриптор  setProperty,  реализовать проверку с помощью сервлета достаточно сложно. Ссрвлеты, в отличие от JSPдокументов,  не  могут  применять Java-отражение  для  установки  свойств  bean  в  соответствии со значениями параметров. Код компонента bean,  использованного  в рассматриваемом  документе,  представлен влистинге 3.8,6. ЛИСТИНГ3.8,6.  /WEB-INF/classes/beans/Form.  Java //  Пропущенные  фрагменты  кода  были  представлены //  в  листинге  3.7,6 public  class  Form  implements  ValidatedElement  ( private  CreditElement  c r e d i t  =  new  CreditElement() private  NameElement  name  =  new  NameElement(); private  String  e r r o r ; public  boolean  validate ()  1 error  =  ""; if(Iname.validate(})  { error  +=  name.getValidationError [ ) ; }

i f ( ! c r e d i t . v a l i d a t e ( ) )  { if(error.length!)  >  0) error  +=  "
"; error += credit.getValidationError(}; ) return error ==  ""; } public String getValidationError()  { return error;

Компонент bean, приведенный в листинге 3.8,6, похож на компонент, код которого  содержится  в  листинге  3.7,6.  Отличие  состоит  в  том,  что  вместо  объектов TextElement  и  CheckboxElement  используются  экземпляры  классов  NameElement и  CreditElement.  Кроме  того,  в  компоненте  реализован  метод  v a l i d a t e ,  выполняющий проверку имени и типа платежной карты.  Если данные формы не выдерживают проверку, создается сообщение об ошибке. Классы  NameElement  и  CreditElement  представляют  собой  подклассы  классов TextElement и CheckboxElement. В них переопределены методы, объявленные в интерфейсе ValidatedElement. Код класса CreditElement показан влистинге 3.8,в.

94 

Глава 3. HTML-формы

Листинг 3.8,в. /WEB-INF/claasea/beans/CreditElement. Java package beans.html; public class CreditElement extends RadioElement  { private  String error; public boolean validate ()  ( boolean valid =  true; String  value = getValue(); error =  ""; if(value — null  ||  value.length{)  == 0)  ( valid = false; error  =  "Credit card must be selected"; ) return valid; } public String getValidationError()  ( return error;

Метод validate класса CreditElement возвращает значение true, если кнопка переключателя выбрана, и false — в противном случае. Код класса NameElement показан в листинге 3,8,г. Листинг3.8,г. /WEB-INF/claases/beans/NameElement.Java package beans.html; public class NameElement extends TextElement  ( private  String error; public boolean validated  { boolean valid = true; String  value = getValueO; error =  ""; if[value.length О  == 0)  { valid - false; error -  "Name  field must be  filled  in"; 1 else ( forfint i=0; i b1 large websTfes identical formats, eirch a mechanism is valuable bacauef it localize Б changes to That  mechfnitm  is JSP templates

Колонтитул

Рис. 4.1. Размещение элементов Web-страницы

101

102 

Глава 4. Шаблоны

Документ,  представленный в листинге 4.1, использует для включения данных действие  j sp: include. Благодаря этому появляется  возможность изменять содержимое документа,  заменяя  включаемые файлы,  при  этом код основного документа не  изменяется.  Однако  в  данном  случае  расположение  элементов  определяется  структурой основного документа, и для перекомпоновки Web-страницы надо изменять ее код. Если  на Web-узле содержится большое число Web-страниц одинакового формата, то даже для  незначительного  изменения  их  внешнего  вида  приходится  затрачивать  большие усилия по модификации кода. Автор может не только отделять содержимое от Web-страницы, в которой оно отображается, как это было показано в листинге 4.1, но и отделять от Web-страницы распомжение элементов.  При этом появляется  возможность изменять компоновку документа, не внося изменений eJSP-файлы.

Разделы, области и шаблоны Для отделения компоновки содержимого от документа можно применить один из самых старых приемов программирования— косвенные обозначения.  Мы разделяем JSP-документ,  содержащий  данные  и  реализующий  их  размещение  (листинг 4.1),  на два документа: область,  которая определяет содержимое, и шаблон, с помощью которого задается размещение. Область показана в листинге 4.2,а. Листинг 4.2, а. JSP-документ, определяющий область taglib  uri= f regions'  prefix^'region'  %>



Документ, код которого представлен выше, использует пользовательские дескрипторы для определения области. Область содержит четыре раздела, показанные в листинге 4.1. Каждый  раздел  связан  с  шаблоном,  который  задается  посредством  атрибута template  дескриптора  region: render.  Открывающий  дескриптор  region : render создает область  (region)  и  помещает ее в область видимости приложения.  Подробно вопросы создания области будут рассматриваться далее в этой главе. Дескрипторы  region:put  сохраняют  пару  имя-значение  в  области,  созданной  с помощью  открывающего  дескриптора  region: render.  Эти  имя  и  значение  представляют имя раздела и его содержимое; например,  в  коде,  приведенном  в листинге 4.2,а,  помимо  прочих  разделов,  определяется  раздел  с  именем  header,  содержимое которого находится в файле /header . j sp. При  обработке  закрывающего  дескриптора  region: render  включается  шаблон,  заданный ранее с помощью атрибута template. Код шаблона представлен в листинге 4.2,6.

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

103

Листинг 4.2,6. Шаблон, который используется областью, определенной •листинге 4.2,а.

< / t i t l e >

5



f

•ctdximg  згс='  graphics/templates.gif  />


Поскольку код в файле  /header. j sp  является включаемым содержимым, нет необходимости  повторять его в каждом документе.  Заметьте также,  что  в  /header. j sp  отсутствуют  привычные  HTML-дескрипторы  html,  head,  body  и  т.д.  Эти  дескрипторы содержатся в шаблоне. Код в файле /header . j sp прост и удобен для сопровождения. В  данном  разделе  вы  ознакомились  с  пользовательскими  дескрипторами  r e gion: render  и  r e g i o n : p u t  из  библиотеки,  предназначенной  для  создания  областей.  Данная  библиотека  предоставляет  и  другие  средства,  например,  поддержку  необязательного содержимого и содержимого, определяемого ролями. Эти вопросы будут рассмотрены  в  последующих разделах. ЮТ

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

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

Необязательное содержимое 

Specifying All  Content: SIDEBAR

Omitting Content: HEADER

SIDEBAR

HEADER

CONTENT

FOOTER

FOOTER

Рис. 4.2. Необйзательное содержимое раздела Код}5Р-документа, показанного на рис. 4.2, представлен в листинге 4.3,а.

Листинг 4.3,а. Содержимое одного из разделов пропущено



Specifying All  Content:



< % —  Содержимое  этой  области  пропущено  — % >

105

106 

Глава 4. Шаблоны





Для  приведенного  выше  JSP-документа  JSP-файлы,  определяющие  содержимое разделов, очень просты. Например, файл /sidebar, j sp имеет следующий вид: SIDEBAR Остальные JSP-файлы,  соответствующие  содержимому  разделов,  отличаются  от /sidebar, jsp  только текстом,  расположенным  между дескрипторами.  Например,  в файле  /header. j sp находятся следующие данные:

«cregion: define  id='SIDEBAR_REGION'  scope='application' template='hscf.jsp' >



  • ; *Я*« |Й  Wlp //bulhg1r«OH!"lv>ni/nja.«.w«h 

SiDEBAR

TO 

UOljEl ^J

о

OVERRIDDEN HEADER



CONTENT ::•

OVERRIDDEN FOOTER J Рис.  4.6.  Переопределение разделов

Листинг 4.6, а. Расширение существующей области t a g l i b  u r i = ' r e g i o n s '  prefix='region'  %>

Объединение различных подходов к созданию областей 

113

JSP-код,  в  котором  определены  области  SIDEBAR_REGION  и  EXTENDED_SIDEBAR REGION, приведен в листинге 4.6,6. Листинг 4.6,6.  /regionDefinitions-override.







Как правило,  при определении областей указывается шаблон,  как в случае с областью SIDEBAR_REGION, определенной в листинге 4.6,6.  Однако область может определяться  посредством другой  области;  так,  например,  при  определении  EXTENDED_ SIDEBAR_REGION указывается область SIDEBAR_REGION. При определении одной области посредством другой  вновь определяемая область становится  дочерней  областью  по  отношению  к указанной.  Например,  EXTENDED_ SIDEBAR_REGION  определена  в  терминах  SIDEBAR_REGION,  поэтому  EXTENDED_ SIDEBAR_REGION наследует содержимое SIDEBAR_REGION. Кроме того, посредством дескрипторов  region:put  в EXTENDED_SIDEBAR_REGION  переопределено  содержимое двух разделов,

Объединение различных подходов к созданию областей На  практике  вам,  наверное,  никогда  не  придется  создавать Web-страницы,  которые были бы настолько сложны, как страница, показанная на рис.  4.7. Данный раздел призван лишь проиллюстрировать возможности библиотеки по созданию вложенных областей, использующих расширение. JSP-код документа, показанного на рис. 4.7, представлен в листинге 4.7,а. Листинг 4.7,a. /index, jep

< % @  include  file='/regionDefinitions-override.jsp'  %> «region:render region='EXTENDED_SlDEBAR_REG!ON'/>

114 

Глава 4. Шаблоны

£j hUp7A>caliaifc8G8Q/regiora/ - MBIDUH Internet Expfcnr Ella

£dir

yiw

F^^rltes

Took

._. 1 D| _xj

|jv^

jf±ut |4Jj kirp //lucalTosLaOSO/iegou/

OVERRIDDEN HEADER

TOP

OVERRIDDEN TOP LEFT

RIGHT LEFT

Direct 

Сnnrent

HEADER CONTENT

|

FOOTER

1

BOTTOM

OVERRIDDEN BOTTOM

FOOTER #] Done

TsLoodf*«l

Рис. 4.7. Использование расширений и вложенных областей Содержимое включенного JSP-файла приведено в листинге 4.7,6. Листинг4.7,6. /regionDefinitions-override.isp taglib uri='regions1  prefix-'region1  %>



D i r e c t  Content

Количество областей,  представленных  в листинге  4.7,6,  и  зависимость  их друг от друга настолько велики,  что такая  структура скорее помешает,  чем поможет в работе. Однако целью данного примера является лишь демонстрация возможностей библиотеки по поддержке областей. Область  EXTENDED_SIDEBAR_REGION  отображается  при  просмотре  документа, показанного на рис. 4.7. Данная область расширяет область SIDEBAR_REGION и переопределяет  разделы  header,  s i d e b a r  и  c o n t e n t .  В  разделах  s i d e b a r  и  c o n t e n t EXTENDED_SIDEBAR_REGION содержатся соответственно области BORDER_REGION И SIDEBAR_REGION. В области  SIDEBAR_REGION в качестве содержимого  раздела  s i d e b a r  используется область EXTENDED_BORDER__REGION. Эта область расширяет BORDER_REGION и переопределяет  разделы  top,  bottom  и  r i g h t .  Заметьте,  что  содержимое  раздела r i g h t  EXTENDED_BORDER_REGION  определяется  в  теле  дескриптора  r e g i o n : p u t . Такая возможность подробно обсуждается далее в этой главе.

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

Компоненты  bean Компоненты  bean,  которые  входят  в  состав  библиотеки  пользовательских  дескрипторов,  предназначенных  для  поддержки  областей,  перечислены  в  табл. 4.1.  Все эти  компоненты  принадлежат пакету beans . r e g i o n s .

Таблица 4 . 1 . Компоненты bean в составе библиотеки поддержки областей (все указанные компоненты принадлежат пакету beans. regions) Компонент 

Описание

Content  Содержимое,  которое  воспроизводится  в  JSP  PageContext Section 

Содержимое,  которое входит в состав области

Region 

Контейнер, содержащий разделы

RegionStack 

Стек  областей  (region),  поддерживаемых  в  области  видимости (scope) приложения

116 

Глава 4. Шаблоны

Диаграмма классов для beans, приведенных в табл. 4.1, показана на рис. 4.8. • абстрактный газе с. в котором лен единственный  абстрактный  метод

Content

*Contern[)

•toSlrinaO

#comeni 

String

/

j

05л  >  tr  к  с  аде  pet  jr  С*

\ Section

Region

\ L

sections ^I&n[j9rQ ^loStringO

Has hi able (Turn krtlfj

•guSectiomO

Рис.  4.8.  Компоненты bean для поддержки областей

Класс Content представляет собой абстрактный класс, являющийся суперклассом классов  Section  и  Region.  Поскольку абстрактный  класс Content  представляет как примитивы (разделы), так и их контейнеры, указанные выше три класса соответствуют образу разработки Composite. Классы  Section  и  Region  реализуют  единственный  абстрактный  метод  класса Content — метод render. Класс Region содержит хэш-таблицу разделов. Код класса Content  приведен в листинге 4.8,а. ЛИСТИНГ 4.8,а.  /«EB-INF/cl».../b. U package beans.regions; import javax.servlst.jsp.JspException; import javax.servlet.jsp.PageContext; public abstract class Content  implements  Java.io.Serializable protected final String content,  direct; // Воспроизведение содержимого в JSP-документе abstract void render(PageContext pc)  throws JspException; public Content(String content)  { this(content,  "false");

1 public Content(String content,  String direct)  { this.content » content; this.direct  = direct;

Реализация дескрипторов поддержки областей 

117

public  String  getContent ()  { return  content; }

public  String  getDirectO  ( return  direct; ) public  boolean  isDirectO  { return  Boolean.valueOf(direct).booleanValue[); ) public  String  toStringO  ( return  "Content:  "  +  content,-

J

Абстрактный  класс  Content  поддерживает  два  свойства:  content  и  d i r e c t . Свойство content  представляет содержимое, воспроизводимое посредством  метода render, а свойство d i r e c t указывает, как содержимое должно воспроизводиться. Если значение d i r e c t равно t r u e , содержимое отображается непосредственно, а если direct принимает значение false, воспроизведение осуществляется посредством включения. Код класса Section приведен в листинге 4.8,6. Листинг 4.6,6. /WEB-iNF/clasaes/beans/templates/Section. Java package beans.regions; import javax.servlet.jsp.JspException; import javax.servlet.jsp.PageContext; // Класс Section представляет собой подкласс класса Content, // в котором предусмотрено имя и реализован метод // Content.render. Метод Content.render воспроизводит содержимое, // осуществляя включение либо выполняя непосредственный вывод. // Выбор конкретного способа отображения зависит от значения // параметра direct,  передаваемого конструктору Section. // // Заметьте,  что содержимое раздела может представлять собой // область;  в этом случае Region.render вызывается из // Section.Render(). public class Section extends Content  { protected final String name; public Section(String name,  String content,  String direct)  ( super(content, direct); this.name = name;

I public String getNameO  { return name; ) public void  render(PageContext pageContext) throws JspException ( if {content ! = null) (

118 

Глава 4.  Шаблоны

// Является ли содержимое раздела  областью Region  region =  (Region)pageContext. findAttribute(content); if(region ! = null) ( //  Воспроизведение содержимого как области RegionStack.push{pageContext,  region); region.render(pageContext) ; RegionStack.pop(pageContext); } else { if (isDirectO ) { try { pageContext.getOut().print(content.toString()) } catchfjava.io.lOException ex}  ( throw new JspExceptionfex.getMessage()); else { try { pageContext.include(content.tostring()); } catch(Exception ex)  { throw  new  JspException(ex.getMessage()I

I

) public String toStringO  { return "Section:  " + name + ",  content= " + content.toString();

Класс  Section  является  подклассом  класса  Content  и  реализует  метод  render. Если  значение  атрибута  d i r e c t  равно  true,  для  вывода  содержимого  используется предопределенная  переменная  out.  Если  значение  d i r e c t  равно  false,  класс Section использует для включения содержимого кон текст JSP-документа. Если  содержимое  раздела  представляет  собой  область,  класс  Section  помещает эту  область  в  стек  и  воспроизводит  ее  (реализация  стека  показана  в  листинге  4.8,г). После воспроизведения области  класс  Section  извлекает ее  из стека. Код класса Region  приведен  в листинге 4.8,в. ЛИСТИНГ4.8,S. /WEB-INF/classes/beans/templates/Region. Java package beans.regions; import  Java.util.Enumeration; import  Java.util.Hashtable; import  javax.servlet.jsp.PageContext; import  javax.servlet.jsp.JspException;

Реализация дескрипторов поддержки областей 

119

// Область представляет собой содержимое,  включающее // набор разделов. public class Region extends Content  { private Hashtable sections = new Hashtable (); public Region(String content)  { this(content,  null);  // content - имя шаблона } public Region(String content,  Hashtable hashtable)  { super(content); if(hashtable  != null) sections =  (Hashtable)hashtable.сlone ();

} public void put(Section section)  { sections.put(section.getName(),  section);

J

public Section get (String name)  ( return  (Section)sections.get(name); } public Hashtable getSections()  { return sections; } public void  render[PageContext  pageContext) throws JspException { try { pageContext.include(content);

J I

catch(Exception ex)  (  // lOException или ServletException throw new JspException(ex.getMessage()); )

public String  toStringO  { String s = "Region:  " + content.toString()  + "
"; int indent = 4; Enumeration e - sections.elements(); while(e.hasMoreElements())  { Section section =  (Section)e.nextElement(); for(int i=0; i  s += section.toStringO  +  "
";

I return s; >

I Подобно  классу  S e c t i o n ,  класс  Region  является  подклассом  класса  C o n t e n t  и  реализует  метод  r e n d e r .  Дополнительно  класс  Region  поддерживает  хэш-таблицу  разделов. Для  доступа к  разделам  области  могут  быть  использованы  методы  Region . g e t  и Region. g e t S e c t i o n s .  Первый  из этих методов возвращает  раздел по его  имени,  а  второй предоставляет хэш-таблицу разделов.

120 

Глава 4.  Шаблоны

В  области  видимости  (scope)  приложения  области  (region)  содержатся  в  стеке. Этот  стек  реализуется  с  помощью  класса  RegionStack,  код  которого  представлен  в листинге 4.8, г. ЛИСТИНГ4.8,г. /HEB-INF/classaa/beans/templates/RegionStack. Java package  beans.regions; import  javax.servlet.jsp.PageContext; import  java.util.Stack; public  class  RegionStack  { private RegionStack()  {  }  //  экземпляр класса не создается public  static  Stack  getStack(PageContext pc)  ( Stack  s  »  (Stack)pc.getAttribute("region-stack", PageContext.AFPLICATION__SCOPE); if (s ќ"»- null) { s  =  new  Stack[); pc.setAttribute("region-stack",  s, PageContext.AFPLICATION_SCOPE); ) return  s; ) public  static  Region  peek(PageContext  pc)  { return  (Region)getStack(pc).peek(); } public  static  void  push(PageContext  pc,  Region  region){ getStack(pc).push(region); } public  static Region pop(PageContext pc)  { return  (Region)getStack(pc).pop();

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

Классы  поддержки дескрипторов Классы  поддержки дескрипторов,  предназначенных для  работы  с  областями,  перечислены в табл, 4.2.

Реализация дескрипторов поддержки областей 

121

Таблица 4.2.  Пользовательские дескрипторы,  предназначенные для поддержки областей (все дескрипторы, приведенные здесь, принадлежат пакету tags. regions) Компонент

Описание

RegionTag

Базовый  класс для RegionDefinitionTag  и  RenderTag

RegionDefinitionTag 

Создает область (region)  и сохраняет в указанной области видимости (scope)

RenderTag

Воспроизведение области или раздела

PutTag

Создание раздела и сохранение его в области  (region)

Диаграмма классов для этих дескрипторов показана на рис. 4.9.

BodyTagSuppnrt

Region (Ваш гц1яи

(fregion

>0 •get Sections!) •tenderf)

9findftegionByKeyO i*creale RegionF tomTeivtfilataO ^ c r e ale Regi о nFro mRe gion 0 •pvtO



•SodyTagSupportQ •doAflenSodyO •doEndTagO



gO

•geLBodyConlentO •geLPreviousOutO •leleasaQ •setBodyContentO

ъ

RegionDefinrtionTag •setScopeQ •doStartTagg *dEfTO getScop •released

•set Se

PuiTag •selSeclionQ •selDiraclO •setContentQ •geiSectionO •getRoteQ •gatContentfl •getDirsclO •doMerBocfyO

РИС. 4.9. Классы поддержки дескрипторов, предназначенных для работы с областями

122  Глава 4. Шаблоны

Для создания областей предназначены дескрипторы region; render и region: define,  классами  поддержки  которых  соответственно  являются  RenderTag  и RegionDef initionTag. Функции по созданию областей инкапсулированы в классе RegionTag, который представляет собой суперкласс RenderTag и RegionDefinitionTag. Класс RegionTag, в свою очередь, является подклассом TagSupport. Класс PutTag представляет собой класс поддержки дескриптора region:put. Суперклассом этого класса является BodyTagSupport, т.е. данный дескриптор обрабатывает содержимое. Код класса RegionTag представлен в листинге 4.9,а. Листинг4,9,a. /WEB-IKF/claases/tags/regions/RegionTag. package tags.regions; import  javax,servlet.jsp.JspException; import  javax,servlet.jsp.PageContext; import  javax.servlet.jsp.tagext.TagSupport; import  beans.regions.Section; import  beans.regions.Region; public class RegionTag extends TagSupport  { protected Region  region =  null; protected  String  template = null; private  String  regionKey = null; public void setTemplate(String template)  { this.template =  template; } public void setRegion)String regionKey}  { this,regionKey -  regionKey; } protected boolean findRegionByKey()  throws JspException  { if [regionKey l= null)  { region =  (Region)pageContext.findAttribute(regionKey); if (region = null)  ( throw new JspException("can't  find page definition  "  + "attribute with this key:  " + regionKey); > } return region  != null; } protected void createRegionFromTemplate()  throws JspException  ( if[template  =  null) throw new JspException("can't  find template"); region = new Region(template); } protected void createRegionFromRegion()  throws JspException  { findRegionByKey(); if(region  =  null) return;

Реализация дескрипторов поддержки областей  123

region = new Region(region.getContent(),  // шаблон region.getsections[)); // разделы ) public void put(Section section)  { region.put(section); ) public void release()  ( super.release(); region = null; regionKey = null; template = null;

Область создается на базе шаблона или другой области, определяемых соответственно с помощью атрибутов template и region. Класс RegionTag предоставляет setметоды для этих атрибутов, реализует методы для создания областей и, кроме того, содержит метод для поиска существующей области по имени. Указанные средства используются подклассами RegionTag. Код класса RegionDefinitionTag приведен в листинге 4,9,6. ЛИСТИНГ 4.9,6. /WEB-INF/ classes/ tags/regions /RegionDefinitionTag. Java package tags.regions; import javax.servlet.j sp.JspExcept ion; import j avax.servlet.j sp.PageContext; import  javax.servlet.jsp.tagext.TagSupport; import beans.regions.Content; import beans.regions.Region; public class RegionDefinitionTag extends RegionTag  { private String scope = null; public void setscope(String scope)  ( this.scope = scope; ) public int doStartTagO  throws  JspException  { if(region  != null && template  t= null) throw new JspException("regions  can be  created  from "  + "a template or another region,"  + "but  not  both"); createRegionFromRegion(); if(region == null) createRegionFromTemplatet);

J

return EVAL_BODY_INCLUDE;

public int doEndTag()  throws JspException  ( pageContext. setAttribute (id, region, getScope {))ќ; return EVAL PAGE,-

124 

Глава 4.  Шаблоны

protected int getScope()  { int  constant  =  PageContext.FAGE_SCOPE; scope -  (scope — null)  ?  "page"  :  scope; if("page".equalsIgnoreCase(scope)) constant  =  PageContext.PAGE_SCOPE; else  if("request".equalsIgnoreCase(scope)) constant  =  PageContext.REQUESTJ3COPE; else  if{"session",equalsIgnoreCase(scope)) constant  =  PageContext.SESSION_SCQPE; else  if("application".equalsIgnoreCase(scope) constant  ќ  PageContext.APPLICATION_SCOPE; return constant; } public void released  { super.release() ; scope ќ "page";

Класс RegionDefinitionTag является  подклассом  RegionTag и создает область либо на базе шаблона, либо на базе другой области. Заметьте, что если одновременно заданы  атрибуты  region  и  template,  то  при  выполнении  метода  RegionDefinitionTag. doStartTag  генерируется  исключение.  Это  происходит  потому,  что  область не может быть создана и на основе шаблона, и на основе другой области. Код класса RenderTag приведен в листинге 4.9,в. ЛИСТИНГ4.9,В. /WEB-INF/classes/tags/ragion3/RenderTag.Java package tags.regions; import  javax.servlet.http.HttpServletRequest; import  javax.servlet.jsp.JspException; import  javax.servlet.jsp.PageContext; import  javax.servlet.jsp.tagext.TagSupport; import beans.regions.Content; import  beans.regions.Section; import  beans.regions.Region; import  beans.regions.Regionstack; public class RenderTag extends RegionTag  ( private String sectionName=null,  role=null; public void setSection(String s)  (  this.sectionName - s;  ) public void setRole(String s)  (  this.role = s;  ) protected boolean renderingRegion()  { return sectionName == null; } protected boolean  renderingSectionO  {

I

Реализация дескрипторов поддержки областей  125 return  sectionName  !=  null; public  i n t  doStartTag()  throws  JspException  { HttpServletRequest  request  =  (HttpServletRequest) pageContext.getRequest(); i f ( r o l e  !«•  n u l l &£ [request. istJserlnRole (role) ) return SKIP_BODY; if(renderingRegion() ) ( if(!findRegionByKey()} [ createRegionFromTempiate[); ) Regionstack.push(pageContext, r e g i o n ) ; } return EVAL_BODY_INCLUDE; } public i n t doEndTag() throws JspException ( Region region = RegionStack.peek(pageContext); i f ( r e g i o n == null) throw new JspException("Can't find r e g i o n " ) ; if(renderingSection(}) f Section section = region, get(sectionName); i f ( s e c t i o n == null) return EVAL_PAGE;

//  пропущенные  разделы  игнорируются

section.render(pageContext); ) e l s e  if(renderingRegion())  ( t r y  < region.render(pageContext); RegionStack.pop(pageContext); I catch (Exception ex)  {  //  IOException или ServletException throw new  JspException(ex.getMessage()); }

I

return  EVAL_PAGE; I public void released  { super.release[); sectionName ќ= role = null;

Класс  RenderTag  отображает  как  разделы,  так  и  области.  Если  задан  атрибут section,  воспроизводится  раздел,  в  противном  случае  воспроизводится  область. При  воспроизведении  области  метод  doStartTag  помещает  ее  в  стек,  а  метод doEndTag  извлекает  из  стека.  Особенности  реализации  стека  областей  были  рассмотрены ранее в этой главе. Код класса PutTag представлен в листинге 4.9,г.

126  Глава 4. Шаблоны

Листинг 4.9,г. /WEB-INF/classes/tags/regions/PutTag. Java package tags.regions; import  javax.servlet.http.HttpServletReguest; import  javax.servlet.jsp.JspException; import  javax.servlet.jsp.PageContext; import  javax.servlet.jsp.tagext.BodyTagSupport; import  javax.servlet.jsp.tagext.TagSupport; import  beans.regions.Content; import  beans.regions.Section; public class PutTag extends BodyTagSupport  [ private String section,  role,  content,  direct = null; public void  setSection(String  section){this.section  =  section;} public  void  setRole  (String role)  {this.role  = role;  \ public  void setDirect  (String direct)  {this.direct  = direct;  } public  void setContent(String cntnt)  {this.content = cntnt;  } public  String getSection ()  { return section; } public String getRole()  {  return role;  } public  String getContent0  (  return content; ) public String getDirect()  f  return direct;  ) public int doAfterBody()  throws JspException  { HttpServletRequest  request  = (HttpServletRequest)pageContext.getRequest(); if(role  != null SS  Irequest.isUserlnRolefrole)) return EVAL__PAGE; RegionTag regionTag «ќ  (RegionTag)getAncestor( "tags.regions.RegionTag"); if(regionTag  —  null) throw new JspException("No RegionTag ancestor"); regionTag.put(new Section(section,  getActualContent(), isDirectO ) ) ; return SKIP_BODY; ) public String isDirectO  { if(hasBody[))  return "true"; else  return direct == null  ?  "false"  :  "true"; } public void release()  { super.release(); section = content = direct =ќ role = null; ) private String getActualContent()  throws JspException  { String  bodyAndContentMismatchError  = "Please specify template content in this  tag's body  " + "or with the content attribute,  but not both.", bodyAndDirectMismatchError  =

Реализация  дескрипторов  поддержки  областей  127 "If  content  is  specified  in  the  tag  body,  the  "  + "direct  a t t r i b u t e  must  be  t r u e . " ; boolean  hasBody  =  hasBodyО; boolean  contentSpecifled  =  (content  !=  n u l l ) ; if([hasBody  f i S  contentspecified}  | | UhasBody  S&  IcontentSpecified)) throw  new  JspException(bodyAndContentMismatchError); if(hasBody  is  direct  !=  null  ss direct.equalsIgnoreCase("false")) throw  new  JspException(bodyAndDirectMismatchError); return  hasBody  ?  bodyContent.getString()  :  content; ) private  boolean  hasBody()  { if  (bodyContent  ==  null) return  (false); return  !  bodyContent.getString().equals[""); } private  TagSupport  getAncestor(String  className) throws  JspException  ( Class  klass  =  n u l l ;  //  переменная  не  может  иметь  имя  class try  < klass  »•  Class.forName(className); } catch(ClassNotFoundException  ex)  ( throw  new  JspException(ex.getMessage()); > return  (TagSupport)findAncestorWithClass(this,  klass);

1

Класс  PutTag  создает раздел  и  сохраняет  его  в  области,  созданной  посредством дескриптора  region .'define  или  region; render.  Класс  PutTag  является  подклассом BodyTagSupport; благодаря  этому есть  возможность  непосредственно  задавать содержимое раздела в теле дескриптора.  Например, дескриптор  region: put может иметь следующий вид:

f

А*»™ [а  мц'.'л>ы1>)0'в11№и1тл1н/м

Please Login





Name: Password:



Mote:  valid name is wtell and valid password is william

260 

Глава 9. Защита

Данные, введенные в форме, передают* я сервлету a u t h e n t i c a t e , выполняющему аутентификацию,  Если  попытка  а; гснтификапии оканчивается  неудачно,  сервлет генерирует сообщение об ошибке и сохраняет его в области  видимо* ги  сеанса. Данное сообщение  отображается  посредством  дескриптора  securityishcwErrors  в  верхней  част  \\ eb-стратщы  регистрации.  ( Отображение имени a u t h e n t i c a t e в сервлет аутентификации  задастся  вфанлсюеЬ.хгп!,  код которого приведен  в/пнтштЛИСТИНГ 9.5,Д.  /WEB-INF/web.xml ; stmt.executeC'INSERT  INTO Orders VALUES '(2, 2, 49.86) , " + "(IF 1, 29. 99), '(3, 4, 9 9 . 1 3 ) , "  + "(1, 3, 39. 99), '(3, 6, 1 1 2 . 2 2 1 , "  + "(1, 5, 24. в"?), "(8 f 7, 29. 99), '(2, 8, 4 9 . 8 6 ) , "  + " (3, 10, 9 9 . 1 3 ) , "  + "(1. 9, 39. 99), "(!ќ U , 24.87)," + " ( 3 , 12, 1 1 2 . 2 2 ) , " "(7, 13, 21.12}," + " ( 1 , 14, 2 7 . 4 9 ) " ) ; catch(SQLException  ex) { ex.printStackTrace( :ќ

private  void  loadJDBCDriver[) try {

270 Глава 10. Работа с базами данных Class.forName("COM.cloudscape.core.JDBCDriver"); ) catch(ClassNotFoundException e) { e.printStackTrace() ; private Connection getConnection(String dbMame) ( Connection con = null; try | con = DriverManager.getConnection( "jdbc:cloudscape:" + dbName + ";create=true"); ) catchfSQLException sqe) { System, err .p tint In ("Couldn' t access " + dbName) ; } return con;

Объем  кода данного  приложения достаточно  велик,  ко  разобраться  в  нем  несложно.  Конструктор  CreateDB  загружает JDBC-драйвер  »  создает  соединение  с  базой данных.  Затем,  вызывая  метод  C o n n e c t i o n . c r e a t e S t a t e m e n t ,  конструктор  получает  объект  S t a t e m e n t ,  инкапсулирующий  JDBC-выражение.  Этот  объект  затем  используется  для  создания  и  заполнения  таблиц  базы  данных,  после  чего  выражение  и соединение с базой закрываются  и  конструктор завершает работу с базой. Последние два  метода,  приведенные  в листинге  10.1,  предназначены  для  загрузки JDBC-драйвера и  получения соединения с  базой данных.  В  этих  методах указаны  класс драйвера и  URL базы; как тип драйвера, так  и  URI. различаются для  разных производителей.  В листинге  10.1  предполагается  работа  с  базой  Cloudscage,  если  вы  предпочитаете  использовать  базы  данных  других  производителей,  вам  придется  заменить  в тексте программы тип драйвера и URL базы.

Источники данных Для установления соединения с базой данных необходимо знать имя базы данных, имя JDBC-драйвсраи URLJDBC. Используя интерфейс DataSource, а также службу имен и каталогов, например Java Naming and Directory Interface (JNDI), вы можете обращаться к базе лишь по имени.  Например,  фрагмент кода, приведенный ниже,  иллюстрирует использование источника данных и JNDI для установления соединения с базой. Context context = new InitialContext(); DataSource dataSource =  (DataSource)context.lookup("sunpress_db"); Connection  connection  =  dataSource.getConnection("uname",  "pwd"); Механизм  источников  данных  позволяет  абстрагироваться  от  деталей  взаимодействия  с базой  данных,  и  изменения  в базе  не  потребуют  модификации  программного кода.  С другой  стороны,  при этом для обращения к базе требуются средства JNDI, устанавливать которые должен  системный  администратор. Для  того  чтобы успех  вашей работы  не  зависел  от решения  администратора,  в  этой  главе  мы  не  будем  рассматривать источники данных, а сосредоточим внимание на работе с JDBC-драйверами.

Пользовательские дескрипторы для работы с базами данных  271

Пользовательские дескрипторы для работы с базами данных На  рис.  10.1  показаны  два JSP-документа.  Один  из  них  выполняет  запрос  к  базе данных  {он  отображается  в  окне,  расположенном  слева  на  рисунке),  а  второй  выводит  результаты  запроса. в 1 Ojutma Eiersk • М а ж в  I ® .  W 'P  " l > c i ^ n ! b . B c a I ' t J W i » v ' -.[|тр 

-MiCHKO'tln'

Б]

Expkttr

Ad****  |fi]  ttp ^locatod eOffiydHa

z i  ^ ^

J

J

Customers CUSTOHER 10 

.

NAME 

PHONE_NUHBER

1

William  Oupant 

(652)4В2ШЭ1

г

!  Kris  Cromwell 

[652^492-0932

3

SussnRandM 

(К2Н82-09ЭЭ

4

Jim  Wilson 

(652М32-09Э4

Lynn  Seckingsr 

(652)432-0535

Б

Richard  T a l e n t !

[652>)B2-€936

Sabnslla Satin!la

(552)482-0937

7

в

; Lisa Hartwig

(652)482-0938

J

J

^ ]  Dare

T>: Local rtrawl

Рис. 10.1. Пользовательские дескрипторы, предназначенные для обращений к базе данных JSP-код  документа,  который  показан  в  окне  на  рис,  10.1  слева,  представлен  в  листинге  10.2,а. Листинг  10.2,a.  /test.jsp Database  Example

3how  Customers





272 Глава 10. Работа с базами данных В  JSP-документе,  показанном  в  листинге  10.2,а,  содержится  пользовательский  дескриптор query. Он применяется для установления соединения с базой данных и выполнения запроса,  в  результате  которого  выбираются  все  строки  (записи)  таблицы Customers.  В  результате  выполнения  данного  запроса  в  области  видимости  сеанса сохраняется  идентификатор  customers,  который  впоследствии  используется  для получения набора результатов, связанного с запросом. В дескрипторе  query  предусмотрены  атрибуты  id  и  scope.  Эти  атрибуты  выполняют те же  функции,  что  и  одноименные  атрибуты действия  jsp:useBean,  поэтому их назначение должно быть интуитивно понятно разработчику. JSP-документ,  приведенный  в  листинге  10.2,а,  содержит  ссылку  на  документ showCustomerQuery.  jsp.  Код  документа  showCustomerQuery .  j sp  представлен  в листинге  10.2,6. ЛИСТИНГ  10.2,6.  /showCustomerQuery.jap Customers

Custome ITS : 













JSP-документ, показанный в листинге  10.2,6, содержит три пользовательских дескриптора,  предназначенных  для  работы  с  запросом:  columnNames,  rows  и  columns. В  каждом  из  них  предусмотрен  обязательный  атрибут  query,  с  помощью  которого идентифицируется  запрос.  Указанные  дескрипторы  выполняют  перебор  данных  в наборе результатов (rows) либо работают с метаданными  (columnNames  и columns). Дескрипторы  columns  и  columnNames  создают  переменные  сценария  соответственно для  столбца и имени столбца.  Имена переменных выбирает разработчик и задает с  помощью  атрибута  id;  в  примере  в листинге  10.2,6  эти  переменные  используются при создании таблицы.  Дополнительная информация об именовании переменных сценария посредством атрибута id была приведена в главе 2.

Пользовательские дескрипторы для работы с базами данных 

273

В  JSP-документе  из  листинга  10.2,6  используется  также  дескриптор  r e l e a s e . Он  закрывает  соединение,  созданное  с  помощью  дескриптора  query.  Создание  соединения для каждого запроса и закрытие соединения по окончании его обработки — далеко  не  идеальное  решение  с  точки  зрения  производительности  приложения.  Гораздо  эффективнее  было  бы  получение  дескриптором  q u e r y  уже  открытого  соединения  из пула.  В  этом  случае  дескриптор  r e l e a s e  должен  был  бы  возвращать  соединение в пул.  Этот подход будет рассмотрен далее в этой  главе.

Дескриптор query Дескриптор  query,  который был использован в документе, показанном  в листинге 10.2,а,  интерпретирует  содержимое  как  SQL-выражение,  поэтому  в  файле  описания библиотеки  дескрипторов элемент b o d y c o n t e n t  содержит значение  t a g d e p e n d e n t .

query tags.jdbc.QueryTag tagdependent

JSP-коитейнер  не  обрабатывает тело дескрипторов,  объявленных  как  tagdependent. Такие дескрипторы,  как query, обычно содержат специализированные данные, которые обрабатываются  самим  дескриптором.  Дополнительную  информацию  о  дескрипторах, для которых элемент bodycontent содержит значение tagdependent, см, в главе 2, Класс  поддержки  дескриптора query  приведен  в  листинге  10.3,а. Листинг 10.3,a. /WEB-lMF/claeaee/tage/jdbe/QueryTag. Java package  tags.jdbc; import  Java.sql.Connection; import  java.sql.DriverManager; import  Java.sql,Statement; import  Java.sql.SQLException; import  Java.sql,ResultSet; import  javax.servlet.jsp.JspException; import  javax.servlet.jsp.tagext.BodyTagSupport; import  beans.jdbc.Query; public  class  QueryTag  extends  BodyTagSupport  ( private  String  scope  =  "page"; private  boolean  update  «  false,  driverLoaded  =  false; public  void  setScope (String  scope)  { this.scope  =  scope; > public  void  setupdate{boolean  update)  ( this.update  -  update;

274 Глава 10. Работа с базами данных public int doAfterBody() throws JspException ( Connection conn = getConnection("f:/databases/sunpress"); try { String query = bodyContent.getString(}; Statement statement = conn.createStatement[ ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); ResultSet rs = null; int rows = 0; if (update) I rows = statement.executeUpdate(query); Query.save(new  Query(statement,  rows), pageContext,  id,  scope); else { rs = statement.executeQuery(query) ; Query.save(new Query(statement, rs), pageContext,  id,  scope); catch(SQLException ex) { throw new JspException(ex,getMessage 0 ) ; > return SKIP_BODY; } private Connection getConnectionfString dbName) throws JspException { Connection con = null; t r y  ( if('driverLoaded)  { Class.forName("COM.cloudscape.core.JDBCDriver"); driverLoaded  =  true; ) con  =  DriverManager.getConnection( "jdbc:cloudscape;"  +  dbName  +  ";create=false"); }

c a t c h  (Exception,  ex)  { t h r o w  new  J s p E x c e p t i o n ( e x . g e t M e s s a g e ( ) ) ; ) r e t u r n  corw

Подобно  приложению,  код которого  приведен  в листинге  10.1,  дескриптор  query загружаетJDBC-драйвер  и  вызывает  метод  DriverManager . g e t C o n n e c t i o n ,  открывая  тем  самым  соединение  с  базой  данных.  Данное  соединение  используется  для  создания  выражения,  для  которого  набор  результатов  поддерживает  прокрутку,  но  не может  обновляться.  С  наборами  результатов  такого  типа  могут  работать  только  драйверы,  реализующие JDBC  2.0,  следовательно,  это же  ограничение  накладывается  и  на рассматриваемые  здесь  пользовательские  дескрипторы.  Это  ограничение  вполне приемлемо,  поскольку большинство  производителей  баз данных  предоставляют драйверы, совместимые с JDBC 2.0.

Пользовательские дескрипторы для работы с базами данных 

275

В дополнение к атрибутам id и scope в дескрипторе query предусмотрен также дескриптор update, который задает тип запроса. Если атрибут update имеет значение false (значение по умолчанию), вызывается метод Statement .executeQuery, возвращающий набор результатов. Если значение атрибута update равно true, вызывается метод Statement .executeUpdate, который воздействует на одну или несколько строк таблицы, как, например, выражение INSERT или UPDATE. После выполнения запроса компонент Query сохраняется в области видимости запроса и связывается с заданным идентификатором. Код класса Query показан в листинге 10.3,6. ЛИСТИНГ 10.3,6. /WEB-INF/classes/beans/jdbc/Query. Java package tags.jdbc.beans; import  Java.sql.ResultSet; import  Java.sql.Statement; import  javax.servlet.jsp.PageContext; import  javax.servlet.jsp.JspException; public class Query { public static  final  String QUERY_FREFIX =  "query-"; private static final  int UNASSIGNED = -1; private  final  Statement  statement; private final ResultSet  result; private final int updateCount; public Query(Statement statement,  ResultSet result)  ( this.statement = statement; this.result = result; this.updateCount  = UNASSIGNED; } public Query(Statement statement,  int updateCount)  { this.statement =  statement; this.updateCount = updateCount; this.result = null; ) public Statement getStatement()  { return statement;  } public ResultSet getResult()  (  return result;  } public  int getUpdateCount0  (  return updateCount;  ) public  static void  save[Query query,  PageContext pageContext, String name,  String scope)  { pageContext.setAttribute(QUERY_PREFIX + name,  query, getConstantForScope(scope)); ) public static  ResultSet  getResult(PageContext pageContext, String name)  throws JspException  { Query query = findQuery(pageContext,  name); return query.getResult(}; } public static  int  getUpdateCount(PageContext  pageContext, String name)  throws JspException  {

276  Глава 10. Работа с базами данных Query query «ќ findQuery(pageContext, name); return query.getUpdateCount();

I public  static Query  findQuery(PageContext pageContext, String name)  throws JspException  { Query query «ќ  (Query)pageContext.findAttribute  ( QUERY_PREFIX  +  name); if(query == null)  (  // сеанс отсутствует? throw new JspException("Query " + name +  " not found." + " Please retry the query");

} return query; | private static int getConstantForScope(String scope)  { int constant =» PageContext.PAGE_SCOPE; if(scope.equalsIgnoreCase("page"}} constant ќ* PageContext.PAGE_SCOFE; else  if(scope.equalslgnoreCase("request")) constant *  PageContext.REQUEST_SCOPE; else  if[scope.equalslgnoreCase("session")) constant =  PageContext.SESSION_SCOPE; else  if(scope.equalslgnoreCase("application")) constant «  PageContext.APPLICATION_SCOPE; return constant;

Класс  Query  содержит  выражение,  а  также,  в  зависимости  от  типа  запроса,  либо набор  результатов,  либо  счетчик  обновлений. Класс  Query  также  предоставляет  общедоступные  статические  методы.  Один  из этих  методов  сохраняет экземпляр  Query  в  указанной  области  видимости,  другой  извлекает  сохраненный  объект;  еще  два  метода  возвращают  либо  набор  результатов, либо  счетчик  обновлений,  связанный с  сохраненным  запросом.  Метод  save  вызывается  в  теле  метода  QueryTag.doEndTag  (см.  листинг  10,3,а),  а  метод  Q u e r y . g e t R e s u l t  используется  дескрипторами  columns,  columnNames  и  rows,  которые  обращаются к набору результатов, связанному с запросом.

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



Пользовательские дескрипторы для работы с базами данных 2 7 7

Дескриптор  columnNames  обращается  к запросу, указанному с  помощью  атрибута query,  и  создает переменную сценария,  представляющую  имя текущего  столбца.  Эта переменная  именуется  с  помощью  атрибута  i d .  В  приведенном  выше  фрагменте  кода переменная  экземпляра  используется  для  вывода  имени  каждого  столбца  в  запросе customers. Два из рассматриваемых в данной  главе дескрипторов — columnNames  и columns — осуществляют перебор столбцов  в  наборе  результатов.  Их  поведение  инкапсулировано в абстрактном  базовом классе  C o l u i m l t e r a t o r T a g ,  код которого  приведен  в листинге 10.4,а.  Данный  класс осуществляет  перебор  столбцов  набора результатов,  а после  окончания  набора  выводит  тело  дескриптора.  Несмотря  на  то  что  в  классе  ColuninI t e r a t o r T a g  не содержится  ни  одного  абстрактного  метода,  сам  класс  определен  как a b s t r a c t .  Это  означает,  что  в  реальном  приложении  должен  быть  использован  один  из подклассов  данного  класса. ЛИСТИНГ 10.4,а. /WEB-INF/classes/tags/3dbc/ColuiniiI.teratorTag. Java package tags.jdbc; import  java.sql.ResultSet; import  Java.sql.ResultSetMetaData; import  Java.sql.SQLException; import  javax.servlet.jsp-JspException; import  javax.servlet.jsp.tagext.BodyTagSupport; import beans.jdbc.Query; public abstract class ColumnlteratorTag extends BodyTagSupport  ( protected int columnCount,  currentColumn; protected ResultSet rs; protected ResultSetMetaData  rsmd; protected String  query; public void setQuery(String query)  { this.query ќ» query; } public int doStaxtTagf)  throws JspException { rs  =  Query.getResult(pageContext,  query); try { rsmd = rs.getMetaData(); columnCount = rsmd.getColumnCount(); currentColumn =  1;

1 catch(Exception ex)  ( throw new JspException(ex,getMessage()); } if(columnCount > 0)  return EVAL_BODY_TAG; else  return SKIP_BODY; } public int doAfterBody()  throws JspException  { if(++currentColumn  private  void  setflttribute()  throws  JspException  \ try  { pageContext.setAttribute(getldf),  rsmd.getColumnNameI currentColumn]); }

catch(SQLException  ex)  { throw  new  JspException(ex.getMessage());

Класс ColumnNamesTag является подклассом ColumnlteratorTag; в нем переопределены  методы  doInitBody  и  doAfterBody.  Эти  методы  сохраняют  в области  видимости документа имя текущего столбца, которое задается с помощью обязательного атрибута id.  В процессе работы класс ColumnNamesTag  применяет метаданные, связанные  с  набором  результатов  (в  методе  s e t A t t r i b u t e  используется  переменная rsmd, объявленная в классе ColumnlteratorTag как protected). Дескриптор  columnNames  создает  переменную  сценария;  чтобы  это  стало  возможным,  надо  реализовать подкласс класса TagExtralnfo.  В листинге  10.4,в  показана  реализация  такого  класса  для  дескриптора  columnNames.  Дополнительную  информацию о создании переменных сценария см. в главе 2. Листинг  10.4,в.  /WEB-INF/classea/tags/jdbc/ColumnNaiaesTaglnfo. Java package tags.jdbc; import javax.servlet.jsp.tagext.TagData; import javax.servlet.jsp.tagext.TagExtralnfo; import javax.servlet.jsp.tagext.Variablelnfo; public class ColumnNamesTaglnfо extends TagExtralnfo  { public Variablelnfo[]  getVariablelnfo(TagData data) return new Variablelnfo[]  { new  Variablelnfo(data.getld(), "Java.lang,String", true, Variablelnfo.NESTED)

В  классе,  представленном  в  листинге  10.4,  имя  переменной  сценария  (типл Java.lang. String)  определяется  значением  атрибута  id  дескриптора.  Параметр true,  передаваемый  конструктору  Variablelnfo,  указывает  на  то,.что  переменная должна  быть  создана.  И,  наконец,  параметр  Variablelnfo.NESTED  ограничивает область видимости переменной сценария телом дескриптора.

280  Глава 10. Работа с базами данных

Дескриптор  column Дескриптор column очень похож на columnNames; оба дескриптора осуществляют перебор столбцов набора результатов и оба создают переменные сценария. Отличие состоит в том, что в дескрипторе columns посредством переменной сценария становится доступным значение те куще го столбца, в дескрипторе columnNames — имя столбца. Подобно классу поддержки columnNames, класс поддержки дескриптора columns, показанный влистинге 10.5,а, создает переменную сценария. ЛИСТИНГ 10.5,a. /WEB-INF/alassea/tags/jdbc/ColumnsTag. Java package  tags.jdbc; import  Java.sql.ResultSetMetaData; import j ava.sql.SQLException; import j avax.servlet.j sp.JspException; public  class  ColumnsTag extends  ColumnlteratorTag  { public void doInitBody{)  throws JspException  { setAttribute О; ) public  int doAfterBody()  throws JspException  ( int whatNext = super.doAfterBody(} ; if{whatNext  =  EVAL_BODY_TAG) setAttribute  (); return whatNext; } private void  setAttribute0  throws  JspException  { try ( pageContext.setAttribute(getId(),  rs.getstring( currentColumn)); } catch (SQLException ex)  [ throw new JspException(ex.getMessage()); ) )

Класс ColumnsTag, код которого приведен выше, использует набор результатов с  DO  с  го  суперкласса  для  получения  значений  столбцов.  Подобно  дескриптору columnNames, для дескриптора columns реализован класс TagExtralnfо, определяющий переменную сценария. Код класса ColumnsTaglnfo, представляющего собой подкласс TagExtralnfo, приведен влистинге 10.5,6. Листинг 10.5,6. ColumnlteratorTaglnfo. Java package  tags.jdbc; import  javax.servlet.jsp.tagext.TagData;

Пользовательские дескрипторы для работы с базами данных 

281

import  javax.servlet.jsp.tagext.TagExtralnfo; import  javax.servlet.jsp.tagext.VariableInfo; public class ColimnsTaglnfo extends TagExtralnfo  ( public Variablelnfо[]  getVariablelnfo(TagData data) return new Variablelnfо[]  { new Variablelnfo(data.getld(), "Java.lang.String", true, Variablelnfo.NESTED)

Как  и  для  дескриптора  columnNames,  имя  переменной  сценария  дескриптора columns  задается  с  помощью  атрибута  id.  Тип  переменной  определен  как  Java, lang. String; переменная должна быть создана и доступна только в теле дескриптора.

Дескриптор rows Дескриптор  rows  осуществляет  перебор  строк  набора  результатов,  связанного  с запросом.  Приведенный  ниже  фрагмент кода демонстрирует использование данного дескриптора.



В данном случае дескриптор rows перебирает строки набора результатов для запроса customers. В составе дескриптора rows содержится дескриптор columns, который перебирает столбцы в составе каждой строки. Класс поддержки дескриптора rows приведен в листинге 10.6. Листинг 10.6./WEB-INF/classes/tags/jdbc/RowsTag.Java package  tags.jdbc; import  Java.sql.ResultSet; import  Java.sql.Statement; import  Java.sql.SQLException; import  javax.servlet.jsp.JspException; import j avax.servlet.jsp.tagext.BodyTagSupport; import  beans.jdbc.Query; public class RowsTag extends BodyTagSupport  ( private ResultSet  rs; private boolean  keepGoing; private  String  query; public void setQuery[String query)  {

282 Глава 10. Работа с базами данных t h i s . q u e r y  =  query; ) public  int  doStartTag[)  throws  JspException  { rs  =  Query.getResult(pageContext,  query); try j keepGoing = rs.next();  // Указывает на первую строку } catch(Exception ex) { throw new JspException(ex.getMessage О) ; ) if(keepGoing)  return EVAL_BODYJTAG; else  return SKIP_BODY;

I public int doAfterBody() throws JspException ( try ( if(rs.isLast()) { rs.beforeFirst() ; try  { bodyContent.writeOut(getPreviousOut()); > catch(Exception e) { throw new  JspException(e.getMessage()}; ) return SKIP_BODY; } rs.next[); } catch[Java.sql.SQLException ex)  { throw new JspException(ex.getMessageО); } return EVAL_BODY_TAG;

Подобно  дескриптору  column  Names,  показанному  в  листинге  10.4,а,  дескриптор rows  получает  в  теле  метода  d o S t a r t T a g  ссылку  на  набор  результатов.  После  получения ссылки вызывается метод n e x t набора результатов,  в результате чего курсор набора перемещается  на  первую  строку  (первоначально  курсор  размещается  перед  первой строкой).  Метод  R e s u l t S e t . n e x t  возвращает значение типа boolean,  которое  позволяет  выяснить,  указывает  ли  курсор  на  существующий  столбец.  Если  в  методе  RowsTag. d o S t a r t T a g  метод  R e s u l t S e t . n e x t  возвращает  значение  f a l s e ,  это  означает, что  в  наборе  результатов  пет пи  одной  строки;  в  результате  метод  d o S t a r t T a g  возвращает значение SKIP_BODY. В противном случае тело дескриптора обрабатывается. В  методе  RowsTag  .  doAf  terBody  проверяется  положение  курсора.  Если  он  указывает на последнюю строку,  он перемещается на первую строку и  перебор прекращается.  В противном случае вызывается метод R e s u l t S e t . n e x t  и перебор продолжается. Подобно  ColumnNames  . doAf terBody,  RowsTag. doAf terBody  выводит  данные  в выходной  поток предыдущего дескриптора.

Пул соединений  233

Дескриптор  release Дескриптор release, класс поддержки которого показан в листинге 10.7, закрывает соединение, открытое дескриптором query. Листинг 10.7./WEB-INF/elas3ea/tag3/jdbc/ReleaseTag.Java package tags.jdbc; import java.sql.Connection; import Java.sql,Statement; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext. TagSupport; import beans.jdbc.Query; public class ReleaseTag extends TagSupport  { private String query =  "null"; public void setQuery(String query)  { this.query = query;

I public int doEndTag()  throws JspException ( Query q = Query.findQuery(pageContext,  query); try  { Connection con = q.getStatement(}.getConnection() if(con != null &£ ! con. isClosedO ) { con.close (); catch(java.sql.SQLException sqlex) ( t h r o w new J s p E x c e p t i o n ( s q l e x . g e t M e s s a g e ( ) ) ;

t pageContext.removeAttribute(query); return EVAL_PAGE; \

)

После закрытия соединения метод ReleaseTag. doEndTag удаляет атрибут Query из соответствующей области видимости.

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

284 Глава 10. Работа с базами данных Добавить  пул  соединений  к Web-приложению  можно  различными  способами.  Вопервых,  вы  можете  воспользоваться  бесплатной  реализацией  пула  соединений.  Одним  из  таких  продуктов  является  PoolMan,  который  можно  скопировать,  обратившись  по  а д р е с у h t t p : / / p o o l m a n . s o u r c e f o r g e . n e t / P o o l M a n / i n d e x . s h t m l . Во-вторых,  средства для  работы  с  пулом  соединений  содержатся  в  составе JDBC  2.0 Optional  Package  API,  но  для  их  использования  необходим  источник данных,  связанный с JNDI. (Источники данных упоминались в начале этой главы.) В-третьих,  вы  можете  создать  собственный  пул  соединений.  Пул  соединений  реализуется  достаточно  просто,  поэтому данному  подходу следуют  многие  разработчики. Реализовав  пул  соединений,  вы  получаете  полный  контроль  за  его  работой.  В  этом разделе  рассматриваются  средства  поддержки  пула  соединений,  которые  вы  можете использовать  и  развивать при создании вашего приложения.

Использование  пула  соединений Дескрипторы  для  работы  с  базами  данных,  которые  обсуждались  в  данной  главе, удобны  в  использовании,  но  дескрипторы  q u e r y  и  r e l e a s e ,  коды  которых  приведены  в листингах  10.3,а и  10.7,  неэффективны,  поскольку  query  каждый  раз открывает новое  соединение,  a  r e l e a s e  закрывает его.  Эти  дескрипторы  несложно  модернизировать для  работы с пулом соедив!ений. Фрагмент  кода,  представленный  ниже,  демонстрирует  изменения,  вносимые  в класс  поддержки дескриптора query, для использования пула соединений. • 

Л 

Л

import beans.jdbc.DbConnectionFool; import beans,util.ConnectionException; public class QueryTag extends BodyTagSupport { public int doEndTag() throws JspException { DbCormectionPool pool = (DbConnectionPool) pageContext.getServletContext(). getAttribute("db-connection-pool") ; Connection  conn; try ( conn = (Connection)pool.getResource(); } catch(ConnectionException cex) { throw new JspException(cex.getMessage(});

1

Метод  QueryTag  получает  ссылку  на  пул  соединений  из  области  видимости  приложения  и  вызывает  метод  g e t R e s o u r c e  пула,  который  возвращает свободное  в данный момент соединение. Дескриптор  r e l e a s e  возвращает  соединение  в  пул  с  помощью  метода  r e c y c l e Resource.  Фрагмент  листинга  модернизированного  класса  ReleaseTag  приведен ниже.

Пул соединений  285

import  beans.jdbc.DbConnectionPool; public class ReleaseTag extends TagSupport  { public  int doEndTagO  throws  JspException  { Query q =  (Query)pageContext.findAttribute(query); ServletContext app  « pageContext.getServletContext(); DbConnectionPool pool =  (DbConnectionPool) pageContext.getServletContext  (). getAttribute("db-connection-pool"); try { pool.recycleResource(q.getStatement().getConnection()); } catch{Java.sql.SQLException ex)  [ throw new JspException(ex.getMessage(}) ;

Создание пула соединений Пул соединений, с которым работают дескрипторы query и release, создается сервлетом. Сервлст строит пул соединений и помещает его в область видимости приложения. Код сервлета приведен в листинге 10.8,а. Листинг 10.8,3. /WEB-INF/classes/SetupServlet.Java import  javax.servlet.ServletConfig; import  javax.servlet.ServletContext; import  javax.servlet.ServletException; import  javax.servlet.http.HttpServlet; import  beans.jdbc.DbConnectionPool; public class SetupServlet extends HttpServlet  { private  DbConnectionPool pool; public void init(ServletConfig  config)  throws  ServletExceptionl super.init(config) ; ServletContext app = config.getServletContext(); pool  = new DbConnectionPool( config.getlnitParameter("jdbcDriver"), config.getInitParameter("jdbcORL") , config.getlnitParameter("jdbcUser"}, config.getlnitParameter("jdbcPwd")}; app.setAttribute("db-connection-pool",  pool); ) public void destroy()  { pool.shutdown(}; pool - null; super.destroy();

286 

Глава  10.  Работа  с базами данных

Сервлет,  представленный  в  листинге  10.8,а,  создает  экземпляр  класса  DbConnectionPool.  Конструктору класса  DbConnectionPool  передаются  четыре  инициализационных параметра сервлета: имя JDBC-драйвера, JDBC URL, имя и пароль. После создания  пула соединений он сохраняется  в области видимости  приложения под именем db-connection-pool. Метод  destroy,  который  вызывается  перед  завершением  сервлета,  прекращает работу пула соединений и закрывает все открытые соединения. Для того  чтобы  гарантировать,  что  пул соединений будет присутствовать  в области  видимости  приложения  уже  при  первом  обращении  к  нему,  сервлст загружается при запуске Web-приложения. Загрузка сервлета производится посредством элемента load-on-startup  дескриптора  доставки.  Соответствующий  фрагмент  дескриптора доставки приведен ниже.

setup SetupServlet

jdbcDriver C0M.cloudscape,core.JDBCDriver

jdbcURL

jdbc:cloudscape:f:/databases/sunpress;create=false

jdbcUser  roymartin

jdboPasgword royboy

p/>

Дескриптор доставки  задает также  инициализационные  параметры,  в  частности указывае^ПВС-драйвер  и  URL. Теперь,  когда  вы  умеете  использовать  пул  соединений,  рассмотрим  процесс  его создания.

Пул  соединений 

287

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

Реализация простого пула соединений Соединения  с  базой  данных  —  не  единственный  ресурс,  пригодный  для  организации пула.  Существенно  повысить  производительность  можно,  создавая  пул  ресурсов,  инициализация  которых  занимает много  времени,  например  гнезд  (socket)  или  потоков. Рассматриваемый  здесь  пул  соединений  реализуется  на  основе  базового  класса,  подклассы  которого  пригодны  для  любых  ресурсов.  Частным  случаем  такого  подкласса  является  пул  соединений  с  базой  данных.  Код базового  класса  приведен  в листинге  10.8,6. Листинг  10.6,6.  Базовый  класс,  предназначенный  для  организации пула ресурсов package  bearis . u t i l ;

import Java.util.Iterator; import Java.util.Vector; public abstract class ResourcePool  { protected final Vector availableResources = new Vector (),protected final Vector inUseResources = new Vector(); // Подкласс данного класса должен реализовывать // следующие три метода: public abstract Object  createResource0 throws ResourceException; public abstract boolean isResourceValid(Object resource); public abstract void  closeResource(Object resource); public Object getResource()  throws ResourceException ( Object resource = getFirstAvailableResource(); if(resource = null)  // Доступных ресурсов нет resource = createResource(); inUseResources .adclElement (resource) ; return resource; } public void recycleResource(Object resource)  { inUseResources.removeElement(resource); availableResources,addElement(resource); } public void shutdown!)  { closeResources(availableResources);

288  Глава 10. Работа с базами данных closeResources(inUseResources); availableResources.clear() ; inUseResources.clear(] ;

} private Object getFirstAvailabLeResource()  ( Object  resource = null; if(availableResources.size[)  > 0)  { resource  =  availableResources.firgtElement(); availableResources.removeElementAt(0); } if(resource  != null  &&  !isResourceValid(resource)) return  getFirstAvailableResource();

I

return  resource; } private  void closeResources(Vector  resources)  { Iterator it = resources.iterator(); while(it.hasNextО > closeResource(it.next() ) ; }

В абстрактном классе ResourcePool  объявлены три абстрактных метода:  c r e a t e Resource,  isResourceValid. и  closeResource.  Эти  методы  существенно  зависят от типа ресурса, поэтому реализуются в конкретных подклассах класса ResourcePool. В состав класса ResourcePool  входят два вектора, один  из них содержит свободные  ресурсы,  а  другой—  ресурсы,  используемые  в  данный  момент.  Второй  вектор применяется  для  отсоединения  ресурсов  при  завершении  работы  пула  посредством метода shutdown. Этот метод вызывает сервлет, приведенный в листинге 10.S,а. Методы  getResource  и  recycleResource  предназначены  для  извлечения  доступных ресурсов из пула и возвращения освободившихся ресурсов в пул. Метод getResource пытается получить ресурс из списка доступных ресурсов, вызывая  метод getFirstAvailableResource.  Если  ресурс доступен,  метод  g e t F i r s t AvailableResource  проверяет допустимость  данного  ресурса,  используя  для  этого метод  isResourceValid,  объявленный  в  классе  ResourcePool  как  абстрактный. Особенности проверки на допустимость зависят от типа ресурса, например, соединение с базой данных может быть разорвано по тайм-ауту; это происходит, если соединение не используется в течение заранее определенного промежутка времени. Если ресурс  не является допустимым,  он удаляется  из списка доступных ресурсов и  метод getFirstAvailableResource  продолжает  поиск  ресурсов,  используя  для  этого  рекурсивный вызов. Если доступные  ресурсы  отсутствуют,  метод  getResource  создает новый  ресурс, добавляет к списку используемых ресурсов и возвращает его. Метод  recycleResource  освобождает ресурс, удаляя  его из списка используемых и включая в список доступных ресурсов. Заканчивая  обсуждение  базового  класса,  необходимо  сделать  несколько  замечаний. Во-первых,  класс ResourcePool обеспечивает требуемую надежность при работе с потоками.  Переменные типа  j ava. u t i l . Vector синхронизированы  и объявлены как final. Итераторы устойчивы к ошибке; если при переборе элементов вектор он изменяется, генерируется исключение.

Пул соединений  289 Во-вторых,  свободные  и  используемые  ресурсы  доступны  подклассам  R e s o u r c e Pool.  Это,  помимо  прочего,  позволяет  подклассам  данного  класса  создавать  начальный кэш ресурсов. В-третьих,  если  при  создании  ресурсов  возникнет  ситуация,  препятствующая  нормальной  работе,  методы  c r e a t e R e s o u r c e  и  g e t R e s o u r c e  могут  сгенерировать  исключение  R e s o u r c e E x c e p t i o n .  Данное  исключение  представляет собой  расширение E x c e p t i o n ,  показанное ниже. package beans,util; public class ResourceException extends Exception { public ResourceException(String s) 1 super(s) ;

Расширение возможностей пула ресурсов Для  простоты  рассмотрения  класс  ResourcePool  чрезвычайно  прост.  При  желании вы  можете расширить его,  реализовав следующие  возможности. 1. Ограничение числа ресурсов. Большое количество открытых ресурсов может снизить производительность приложения. 2. Ограничение времени ожидания свободного ресурса. По истечении определенного времени  поток  перестает  ожидать,  когда  освободится  требуемый  ресурс.  Без такого ограничения  время ожидания может стать слишком большим. 3. Создание ресурса в отдельном потоке с одновременным ожиданием освобождения ресурса.  Ресурс  может  освободиться  раньше,  чем  создастся  новый.  Если  программа способна  использовать  освободившийся  ресурс,  производительность  ее повысится. Новая  версия  класса  R e s o u r c e P o o l ,  реализующая  перечисленные  выше  возможности,  приведена в листинге  10.8,в. Листинг 10.8,в.  Базовый класс для создания пула ресурсов с расширенным набором возможностей package beans.util; import java.util.Iterator,import Java.util.Vector; public abstract class ResourcePool implements Runnable { protected final Vector availableResources - new Vectorf), inUseResources  =  new Vector(); private final int maxResources; private final boolean waitIfMaxedOut; private ResourceException error = null; // устанавливается // методом run() // Подкласс данного класса должен II реализовывать следующие три метода:

290  Глава 10. Работа с базами данных public abstract Object  createResource  () throws  ResourceException; public abstract boolean isResourceValid(Object resource); public  abstract  void  сloseResource  (Object  resource); public ResourcePool()  ( this(10,  // По умолчанию максимальное //  число ресурсов в  пуле равно  10 false);  // Не ожидать ресурс,  если превышено // максимальное значение } public ResourcePool[int max,  boolean waitlfMaxedOut)  { this.maxResources = max; this.waitlfMaxedOut  = waitlfMaxedOut; } public Object getResource()  throws ResourceException  { return getResource(0); } public  synchronized Object  getResource(long  timeout) throws  ResourceException Object resource ќ= getFirstAvailableResource(); if(resource == null)  (  // Доступных ресурсов нет if[countResources[)  0)  { resource = availableResources.firstElement(); availableResources.removeElementAt(0); } if(resource  !ќ= null Sfi  !isResourceValid(resource)) resource = getFirstAvailableResource(); return resource;

i private  void  waitForAvailableResource[) throws ResourceException  ( Thread thread = new Thread(this); thread.start();  // Поток создает ресурс: см. run() try { wait[);  // Ожидать, пока ресурс будет создан }  //  или освобожден catch(InterruptedException ex)  (  ) if(error  != null)  // Исключение перехвачено в run() throw error;  // Сгенерировать  исключение, // перехваченное в run() } private void closeResources(Vector resources)  { Iterator it = resources.iterator[); while (it. hasSextO ) closeResource(it.next());

I

private int countResources()  { return availableResources . size () -f-inUseResources.size [) ;

Отличия межд)- кодами, представленными в листингах  10.8,6 и  10.8,в, в основном сводятся к методам getResource,  waitForAvailableResource  и  run. Если свободных ресурсов нет и лимит ресурсов не исчерпан,  метод getResource ожидает  доступного  ресурса;  когда  ресурс  становится  доступным,  getResource  пытается получить его, выполняя рекурсивный вызов.

292  Глава 10. Работа с базами данных Если  лимит ресурсов  исчерпан  и  доступных ресурсов  нет,  метод  g e t R e s o u r c e  ожидает освобождения ресурса либо  генерирует исключение.  В  классе  ResourcePool  предусмотрен  вариант  метода  getResource,  при  вызове  которого  задается  тайм-аут—  максимальное время  ожидания ресурса при заполненном пуле.  Значение тайм-аута задается в миллисекундах и хранится в целочисленной переменной. Метод  w a i t F o r R e s o u r c e  запускает  поток,  в  котором  создается  новый  ресурс. В  новом  потоке  запускается  экземпляр  класса  ResourcePool  (этот  класс  реализует интерфейс  Runnable),  Таким  образом,  при  обращении  к  t h r e a d , s t a r t  вызывается метод  run  пула. Метод  run  вызывает  метод  c r e a t e R e s o u r c e ,  реализованный  и  подклассе.  Метод c r e a t e R e s o u r c e  может  генерировать  исключение  ResourceException,  которое должно быть обработано  в методе  run. Делегировать обработку исключения  невозможно,  поскольку  метод  run  определен  в  интерфейсе  Runnable  без  ключевого  слова throws.  Поэтому  метод  run  сохраняет  исключение  в  переменной  экземпляра,  оповещает ждущие  потоки  о случившемся  и завершает выполнение.  Один  из зкдущих потоков впоследствии обработает исключение в теле метода w a i t For A v a i l ableRe s o u r ce. Имея  базовый  класс  для  создания  пула  ресурсов,  мы  можем  организовать  пул  соединений с базой данных  как  подкласс  базового  класса.  Код класса,  реализующего пул соединений с базой данных,  приведен  в листинге  10.8,г. Листинг 10.8,г.  /WEB-INF/claeses/beans/jdbc/DbCormectionPool. Java package  beans.jdbc; import  j ava.sql.Connection; import  Java.sql.DriverManager; import  j ava.sql.SQLException; import  beans.util.ResourcePool; import  beans.util.ResourceException; public  class  DbConnectionPool  extends  ResourcePool  { private  final  String  driver,  url,  user,  pwd; private  final  int  initialConnections  =  5; private  boolean  driverLoaded  =  false; public  DbConnectionPool(String  driver,  String  url)  ( this(driver,  url,  null,  null); } public DbConnectionPool[String driver, String url, String user. String pwd)  ( this.driver = driver; this.url  = url; this.user  = user; this.pwd  = pwd; try ( for [int i=0;  i 













Для выполнения предварительно подготовленного выражения JSP-документ, представленный  в листинге  10.9,6,  использует дескриптор  executePreparedStatement. В составе данного дескриптора указываются три атрибута. 1. Идентификатор предварительно подготовленного выражения. 2.  Область видимости, в которой сохраняются результаты запроса. 3.  Массив объектов, каждый из которых соответствует знаку вопроса в предварительно подготовленном выражении. После  выполнения  предварительно  подготовленного  выражения JSP-документ, показанный в листинге 10.9,6, использует для отображения результатов запроса дескрипторы columnNames, columns и rows (эти дескрипторы обсуждались ранее в данной главе). Класс поддержки дескриптора prepareStatement приведен в листинге 10.9,в.

Предварительно подготовленные выражения  297

Листинг 10.9,в. /KEB-INF/clagses/fcags/jdbe/PrepareStatementTag. Java package tags.jdbc; import  java.sql.Connection; import  Java.sql.PreparedStatement; import  Java.sql.ResuitSet; import  Java.sql.SQLException; import  javax.servlet.jsp.JspException; import  javax.servlet.jsp.FageContext; import  javax.servlet.jsp.tagext.BodyTagSupport; import  beans.jdbc.DbConnectionPool; import  beans.util.ResourceException; public class  PrepareStatementTag extends BodyTagSupport  { private String scope = "page"; public void setScope(String scope)  { this.scope = scope; } public int cioAfterBody ()  throws JspException { DbConnectionPool pool  =  [DbConnectionPool) pageContext.getServletContext(). getAttribute("db-connection-pool"); Connection con; try { con =  (Connection)pool.getResource(); } catch(ResourceException ex)  ( throw new JspException(ex.getMessage{)); } PreparedStatement ps = prepareStatement(con); pageContext.setAttribute(id,ps,getConstantForScope(scope)); return SKIP_BODY; } private  PreparedStatement  prepareStatement[Connection  con) throws JspException  { PreparedStatement pstate = null; try ( pstate = con.prepareStatement(bodyContent.getstring(), ResultSet.TYFE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); } catch(SQLException ex)  { throw new JspException(ex.getMessage()); } return pstate; } private int getConstantForScope(String scope)  { int  constant =  PageContext.PAGE_SCOPE; if(scope.equalsIgnoreCase("page"))

298 Глава 10. Работа с базами данных

constant ќ= PageContext. PAGE__5COPE; else  if(scope.equalsIgnoreCase("request")) constant =  PageContext.REQUEST_SCOPE; else  if(scope.equalsIgnoreCase("session")) constant = PageContext.SESSION_SCOPE; else  if(scope.equalsIgnoreCase("application")) constant = PageContext. APPLICATIONS COPE; return constant; ) ! Метод  prepareStatement. doAfterBody  получает  из  пула  соединение  с  базой данных (вопросы организации пула соединений обсуждались ранее в этой главе). Соединение  используется  для  создания  предварительно  подготовленного  выражения, которому  соответствует  набор  результатов,  допускающий  прокрутку,  но  не  допускающий обновлеЕшя. Созданное  предварительно  подготовленное  выражение  сохраняется  в  области  видимости,  определяемой атрибутом  scope дескриптора. Для  идентификации  используется  значение  атрибута  id.  Впоследствии  выражение  обрабатывается  дескриптором executePreparedStatement, класс поддержки которого показан в листинге 10.9,г. ЛИСТИНГ 10.9, г. /WEB- IMF/classes /tags/ jdba/Execu tePreparedSta tementTag.Java package 

tags.jdbc;

import  Java.sql.SQLException; import  Java.sql.PreparedStatement; import  Java.sql.ResultSet; import  import 

javax.servlet.jsp.JspException; javax.servlet.jsp.tagext.TagSupport;

import  beans.jdbc.Query; public  class  ExecutePreparedStatementTag  extends  TagSupport  ( p r i v a t e  Object[]  variables  =  n u l l ; p r i v a t e  String  scope  =  "page"; public  void  setScope(String  scope)  { t h i s . s c o p e  =  scope; \ public  void  setVariables(Object[]  variables)  { this.variables  =  variables; ] public  int  doEndTag()  throws  JspException  [ PreparedStatement  ps  =  (PreparedStatement) pageContext.findAttribute(id); if(ps  ==  null)  ( throw new JspException("No prepared statement " + id); > try  {

Предварительно подготовленные выражения 

299

setVariables(ps,  v a r i a b l e s ) ; ResultSet  rs  =  ps.executeQuery0; Query  query  =  new  Query(ps,  r s ) ; Query.save(query r  pageContext,  id,  scope);

} catch(SQLException  sqlex)  { throw  new  JspException(sqlex.getMessage[));

1

r e t u r n 

EVAL_FAGE;

}

private void setVariables(PreparedStateHient ps, Object[] variables) throws SQLException, JspException { for(int i=0; i  INSERT  INTO  C u s t o m e r s  VALUES  (100,  ' B i l l ' ,  '  ( 8 8 2 ) 8 6 3 - 4 9 7 1 ' ) ; INSERT  INTO  C u s t o m e r s  VALUES  ( 1 0 1 ,  ' R o y ' ,  ' ( 8 8 2 ) 8 6 3 - 4 9 7 2 ' } ; INSERT  INTO  C u s t o m e r s  VALUES  (102,  ' R o n ' ,  ' ( 8 8 2 ) 8 6 3 - 4 9 7 3 ' }

Дескриптор  t r a n s a c t i o n  также  допускает  задание  транзакции  в  файле.  В  этом случае дескриптор выглядит следующим образом:

Код JSP-документа,  содержащего  дескриптор  t r a n s a c t i o n ,  приведен  в  листинге 10.10,а. Листинг  10.10,a.  /test_transaction. Database  Example



SELECT *  FROM Customers





«™и», 

ift= »'  ft^etL'jfo-KnlUi  aoKJ

1

КАМЕ

ADDRESS

WHiam Duponl (в5!)482-0Ю1 Kfi; Cromwell

.3

"l»l

О

Customer:

Customers. CUSTOMERJD 

_^  .

| John Smith

CUSTOMER..ID  - •

*

(652)482-0932 (652H82-OB33

.6

NAME

ADDRESS

Jim Wtleon

(852)482-0634

Lynn Seckinger

(652)482-0935

Richard Tatersan (652ИВ2-0936

•in™



1

Рис.  ТО.З.  Прокрутка набора результатов

Рассматриваемое  приложение  одновременно  отображает  три  строки  набора  результатов  и  содержит ссылки  на  следующую  и  предыдущую  страницы. Запрос,  результаты  которого  показаны  на  рис.  10.3,  создастся  с  помощью  JSPдокумента,  код  которого  представлен  в листинге  10.11,а.

Листинг10.11,а,/test  s c r o l l ,

Database Example



SELECT * FROM Customers

304  Глава 10. Работа с базами данных

Show Customers

Данный JSP-документ использует дескриптор query для выполнения запроса и сохраняет результаты в области видимости приложения. В этом документе также содержится ссылка на файл scrollQuery. jsp, содержимое которого представлено в листинге 10.11,6. ЛИСТИНГ 10.11,6. /scrollQuery.jep Customers



Customers:











slt; >

Прокрутка набора результатов 

305

В документе, приведенном влистинге 10.11,6, содержится модифицированный дескриптор  rows,  в  котором  объявлены  начальная  и  конечная  строки,  предназначенные для отображения. Для контроля за строками в документе используется компонент ScrollBean,  который  отслеживает  позицию  прокрутки  и  размер  страницы.  В  составе  компонента ScrollBean содержится метод s c r o l l , которому при вызове передается строка "f wd" или "back". Метод s c r o l l вычисляет позицию, которая зависит от размеров страницы и направления прокрутки. Код класса ScrollBean приведен в листинге 10.11, в. ЛИСТИНГ 10.11,В. /WEB-INF/classes/beans/util/ScrollBean . Java package  b e a n s . u t i l ; public  class  ScrollBean  { private  i n t  position,  pageSize; public  ScrollBean[)  { t h i s d ,  4); } public ScrollBean(int start,  int pageSize)  { this.position = start; this.pageSize ќ pageSize; ) public synchronized int scroll(String direction)  ( if("fwd".equalsIgnoreCase(direction))  ( position += pageSize + If ) else if("back".equalsIgnoreCase(direction))  ( position -= pageSize; position = (position 

>Anttque breo fri»m the early 170Q's  12 38.99  «name > Gum by л nit Рокеу-г/иагти?? р11лЫя ettion naurBSi/descri№an> 2.99 -С/РГЙ*?

. excellent s

One of the first gds -engine 499Д5

Рис.  11.3.  Генерация  XML-кода  с  помощью  JSP  и JavaBeans Код JSP-документа приведен в листинге 11.2,а. Листинг 1.2,a. /inventory.jap











T, код которого представлен в листинге 11.2,а, ос>тцествляет перебор объектов Item,  полученных с помощью компонента bean с именем i t e r a t e .  Заметь-

Генерация  XML-доку  ментов  313 те,  что JSP-документ  отвечает  за  XML-представление  объектов.  Вопросы  генерации объектами собственного XML-кода будут рассмотрены далее. Классы  I n v e n t o r y  и  Item  не  содержат  ничего  примечательного  и  приведены здесь только для того,  чтобы  приложение  выглядело завершенным. Листинг  11.2,6.  /WEB-INF/classes/beans/Inventory.  Java package  beans; import  j a v a . u t i l . I t e r a t o r ; import  java.util.Vector; public  class  Inventory  implements  Java.io.Serializable  { private  Vector  items  =  new  Vector(); public  Inventory()  { items .addElement (new  itemfAntique  broach", "from  the  early  1700's", (float)1238.99))  ; //  Остальные  пункты  здесь  не  указаны. public  I t e r a t o r  getltems() return  items.iterator()

Листинг 11.2,в. /WSS-INF/classes/beans/Item. Java package beans; public class Item implements Java,io.Serializable  ( private String description,  name; private float price; public ItemfString name,  String description,  float price)  { this.description = description; this.name - name; this.price - price; } public  String getName()  ( return name; } public  float  getPriceO  { return price; } public  String getDescription{)  { return description; }

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

314  Глава 11. XML

Совет При генерации XML-документов следует указывать тип содержимого text/xral В jSP-документах,  предназначенных  для  генерации  XML-кода,  желательно  указывать тип содержимого text/xml, но иногда в этом нет необходимости. Например, JSP-документ. показанный в листинге  11,1 и на рис. 11.2, будет выглядеть одинаково, независимо от того, задан тип содержимого или нет. Если JSP-д оку мент генерирует XML-данные, которые предназначены для преобразования  в  формат,  отличный  от  XML,  указывать  тип  содержимого  не  следует. В качестве примера рассмотрим следующий фрагмент кода:



В  данном  случае  xslt:apply—  это  пользовательский  дескриптор,  который  применяет  листы  стилей  XSLT  к  XML-данным.  XML-код  генерируется  посредством документа genXML. jsp,  в котором  не надо указывать тип  содержимого  text/xml, поскольку конечным форматом, получаемым в результате применения листов стилей, является HTML.

Компоненты bean,  генерирующие XML-код Генерация XML-кода средствами JSP — довольно простая задача. Однако, как можно заметить из листинга 11.2,а, JSP-л оку менты, генерирующие такой код, изобилуют JSP-выражениями и скриптлетами, что, возможно,  идет вразрез с вашим подходом к разработке  Web-приложений. В качестве альтернативы  непосредственному созданию XML-кода с помощью JSPдокумента  можно  использовать  компоненты  bean,  генерирующие  собственное  XMLпредставление,  Достоинством  таких  компонентов  является  тот  факт,  что  они  сами ответственны за созданный ими XML-код, В качестве примера рассмотрим компоненты  Inventory и Item, представленные в листингах  11.3,а и  11.3,6. Вместо того чтобы создавать  XML-данные  средствами JSP,  как  это  показано  в  листинге  11.2,а,  классы Inventory и Item сами создают требуемый код. Листинг  11.3,a.  /WEB-INF/classea/beans/Inventory.Java //  Остальные  части  кода  данного  класса //  представлены  в  листинге  11.2,6. public  class  Inventory  implements  java.io.Serializable  ( public  void  prirttXml  (PrintStream  s)  ( s.println["")  ; Iterator  it = getltemsf); while(it.hasNext[))  { Item item =  [Item]it.next();

Генерация XML-документов 

315

item.printXml[s,  1) ; s.println("")  ; J

Листинг 11.3,6. /WEB-INF/classes/beans/Item, Java //  Остальные  части  кода  данного  класса //  представлены  в  листинге  11.2,в. public  class  Item  implements  Java.io.Serializable  { public  void  printXml[PrintStream  s)  { printXml(s, 2) ; } public void printXml(PrintStream s,  int  indent)  I dolndent(s,indent);  s.printIn("");

I

doIndent(s,indent,  2) dolndent(s,indent,  3) dolndent(s,indent,  2)

s.println(""); s.println(getName  О ) ; s.println("");

dolndent(s,indent,  2) dolndent(s,indent,  3) dolndent(s,indent,  2)

s.printIn(""); s.printIn(getDescription()) , s.printIn(" price: 





Дескриптор  iterateEleraents  использует  класс  SAXParserBean,  приведенный  в листинге  11.5,а,  для  разбора  XML-документа  и  перебора  элементов,  a  ifElementNameEquals  включает тело дескриптора в зависимости от имени текущего элемента. Дескриптор  iterateElements  создает для текущего  элемента переменную  сценария;  имя  этой  переменной  определяется  атрибутом  id  данного дескриптора.  Таким образом,  каждый  из  XML-элементов,  содержащихся  в  файле  inventory.xml,  становится доступным посредством  переменной  сценария element. Переменная  сценария,  сгенерированная  дескриптором  iterateEleraents,  передается  дескриптору  ifElementNameEquals.  Этот дескриптор  включает  содержащиеся в нем данные в том случае, если имя элемента совпадает с именем, указанным посредством  атрибута  names. На  рис.  11.5  показано  более  сложное  приложение,  которое  использует  рассмотренные выше дескрипторы для просмотра файла описания библиотеки дескрипторов (TLD — Tag Library Descriptor).

324  Глава 1 1 . XML -lOlx '.flip  Edit 

И«*

IOOIJ 

ц«1р

ffl hlip: //loeelhotl: BlMO/mlrtBLMi(.lld. itp

Sun Microsystems  Press  Database Tag  Library You can execute database queries and iterate over the results witri the tags in this library. This library also includes tags for prepared statements and transactions. Tag  Class

TsgNama transaction

19 g s. jtJbc. TransactionTag

p re p з г e 3l a [ e m e nt t a g s.j d be PrepareStatementTag

Body  Content

Attributes

tagdependent

file

tagdependent

scope  id

э xscutePrepare d 5t at e ment 11 ags.jdbc. EzxecutePreparedSt 3t в т в ntTa gNone

id  scope variables

query

tagdependent

id  scope  update

JSP

query startRow endRow

с  о  1  umnNames  jtags.jdbc.  ColumnNamasTag

JSP

query id

columns 

sTag

JSP

query id

tags. jdbc. RalaaseTa g

JSP

query

lags.jdbc. OueryTag

raws 

release

tags.jdbc. 

lags 

jdbc. 

Rows 

Column 

Tag

;*fc  Local i t t m l

J

Puc.  11.5.  Разбор  TLD-файла  с  применением  пользовательских дескрипторов  JSP В листинге  П,7,а представлено  содержимое TLD-файла,  разбор  которого  осуществляется с помощью JSP-документа, показанного на рис. 11.5. Листинг  11.7,a.  database. t l d version="1.0"  encoding-"!SO-a859-l"  ?>

l.0 l.l

Sun Microsystems Press  Database Tag Library

You can execute database queries and iterate over the results with the tags  in this  library.  This  library also includes tags  for prepared statements and transactions.

transaction tags.jdbc.TransactionTag tagdependent

file false true

Разбор XML-кода 

...  Определения других дескрипторов пропущены  ...

Код}5Р-документа, показанного на рис. 11.5, приведен в листинге 11.7,6. Листинг 11.7,6. /test SAX  Example









< % — Отображение таблицы,  содержащей  значения дескрипторов name,  tagclass,  bodycontent и attribute — % >

TagName Tag  Class Body  Content Attributes





325

326  Глава 11. XML





JSP











JSP-документ,  показанный  в листинге  11.7,6,  дважды  перебирает элементы  файла d a t a b a s e . t l d ;  один  раз —  для  обработки  элементов  shortname  и  i n f o ,  а  второй — для  построения  HTML-таблицы.  Однако  разбор  XML-файла  ( d a t a b a s e  .  t l d )  выполняется  только  один  раз. Тело  i t e r a t e E l e m e n t s  можно  сравнить  с  оператором  s w i t c h ,  реализованным средствами  пользовательских  дескрипторов;  особенности  обработки  элемента  зависят  от  его  имени.  Например,  для  отображения  значения  элемента  shortname  используется  размер  шрифта,  равный  5. В листинге  11.7,в приведен код класса поддержки дескриптора  i t e r a t e E l e m e n t s . Листинг  11.7,в.  /WEB-INF/classes/tags/xml/sax/SAXParserTag.  Java package  tags.Kinl.  sax; import  Java.util.Collection; import  Java.util.Vector;

Разбор XML-кода 

327

import  javax.servlet.jsp.JspException; import  javax.servlet.jsp.PageContext; import  beans.xml.sax.SAXParserBean; import  tags.util.IteratorTag; import  оrg.xml.sax.SAXException; public class SAXParserTag extends  IteratorTag  ( private String xmlFile; public void setXmlFile(String xmlFile)  { this.xmlFile = xmlFile; } public int doStartTag()  throws JspException  { setCollection(getCollection{)); return super.doStartTag(); } public void release{)  { xmlFile = null; ) private Collection getCollection[)  throws JspException  ( Collection collection =  (Collection) pageContext.getAttribute( xmlFile, PageContext.FAGE_SCOFE); if {collection == null)  { try { SAXParserBean parserBean = new SAXParserBean(); collection = parserBean.parse(xmlFile); setCollection(collection); pageContext.setAttribute(xmlFile, collection, PageContext.PAGE_SCOPE); ) catch(java.io.IOException ex)  { throw new JspException("10 Exception:  " + ex.toString()); catchfSAXException ex)  { throw new JspException("SAX  Exception"  + ex.toString()};

t

} return collection;

Класс  SRXParserTag,  представленный  в  листинге  11.7,в,  является  подклассом класса  IteratorTag,  который  был  рассмотрен  и  главе  2.  Дескриптор  i t e r a t e Elements  создает две  переменные сценария,  соответствующие текущему и  следующему объектам в наборе. Класс  SAXParserTag  использует  компонент,  предназначенный  для  SAX-разбора {он был рассмотрен ранее в этой главе). Метод parse компонента возвращает набор, содержащий  все  элементы  XML-файла.  Этот  набор  устанавливается  в  теле  метода SAXParserTag. doStartTag; перебор организуется методами суперкласса.

328 Глава 11. XML Как уже было сказано выше, SAXParserTag производит разбор XML-файла лишь один раз. После окончания разбора набор данных сохраняется в области видимости документа.  Впоследствии  к  этому  набору  могут  обращаться  другие  дескрипторы iterateElements, содержащиеся в том же документе. Для определения переменных сценария используется подкласс класса TagExtraInfo, который рассматривался в главе 2. Код класса SAXParserTaglnfo приведен в листинге 11.7,г. Листинг 11.7,г. /WEB-INF/classes/tage/aaru/saji/SAXParserTagXnf о. Java package  tags.xml.sax; Import  javax.servlet.jsp.tagext.TagData; import  javax.servlet.jsp.tagext.TagExtraInfo; import  javax.servlet.jsp.tagext.VariableInfo; import  beans.xml.sax.SAXElement; public class  SAXParserTaglnfo extends TagExtralnfo  { public Variablelnfol]  getVariableInfo(TagData data}  ( return new Variablelnfо[]  { new Variablelnfo(data.getld(), "beans.xml.sax.SAXElement", true, Variablelnfo.NESTED), new Variablelnfo("next",  // Имя переменной сценария "beans.xml.sax.SAXElement",  II Тип переменной true,  // Должна ли переменная создаваться Variablelnfо.NESTED)  П  Область  видимости

Класс SAXParserTaglnfo определяет две переменные сценария,  представляющие текущий и следующий элементы набора. Имя переменной сценария для текущего элемента задается значением обязательного атрибута id дескриптора iterateElements. Класс поддержки дескриптора sax: ifEleraentNameEquals  приведен в листинге

11.7л ЛИСТИНГ 11,7,Д. /WEB-INF/classes/tags/xml/aax/ IfElementNameEqualsTag.Java package tags.xml.sax; import  java.util.StringTokenizer; import  java.util.NoSuchElementException; import  javax.servlet.jsp.JspException; import  javax.servlet.jsp.tagext.TagSupport; import  beans.xml.sax.SAXElement; public class  IfElementNameEqualsTag extends TagSupport  { private  SAXElement  element = null; private String names = null;

Разбор XML-кода 

329

public void setElement(SAXElement element)  ( this.element = element; } public void setNames(String names)  [ this.names = names; } public int doStartTag()  throws JspException { StringTokenizer tok « new StringTokenizer(names); String nextName - null, elementName = element.getLocalName(); boolean nameFound =  false; try 1 while(!nameFound) nameFound  =  elementName.equals(tok.nextToken ) catch(NoSuchElementException ex)  { // Больше лексем нет } return nameFound ? EVAL_BODY_INCLUDE  :  SKIP_BODY; ) public void  released  { names - null; element = null;

В классе IfElementNameEqualsTag предусмотрена поддержка двух атрибутов:  экземпляра класса SAXElement и строки, содержащей одно или несколько имен, разделенных  пробелами.  Тело дескриптора включается  только  в  случае,  если  одно  из  заданных имен совпадает с именем элемента. Класс  поддержки  дескриптора  sax: ifElementNaraeNotEquals  приведен  в  листинге П.7,е. ЛИСТИНГ 11.7,е. /WEB-INF/classes/tags/. . ./ IfElementNameNotEqualsTag.Java package  tags.xml.sax; import  Java.util.StringTokenizer; import  Java.util.NoSuchElementException; import  javax.servlet.jsp.JspException; import  javax.servlet.jsp.tagext.TagSupport; import  beans.xml.sax.SAXElement; public  class  IfElementNameNotEqualsTag  extends IfElementNameEqualsTag  { public int doStartTag()  throws JspException  ( int  rvalue = super.doStartTagO; return rvalue == EVAL_BODY_INCLUDE ?  SKIP^BODY  : EVAL_BODY_INCLUDE;

330  Глава  1 1 .  XML Класс  IfElementNameNotEqualsTag  является  подклассом  IfElementNameEqualsTag.  Метод  doStartTag  возвращает  значение,  обратное  по  сравнению  со своим суперклассом. Итак,  рассмотренные  выше  классы  взаимодействуют  следующим  образом.  Дескриптор  iterateElements  использует экземпляр  класса  SAXParserBean  для  разбора указанного XML-файла.  Этот дескриптор создает переменную сценария,  которая является экземпляром SAXElement. Переменная сценария затем используется дескрипторами  ifElementNameEquals  и ifElementNameNotEquals.

Document Object Model (DOM) Document Object Model — это интерфейс, обеспечивающий доступ к документам и их обновление. Программа разбора, созданная на базе этого интерфейса, строит Древовидную  структуру,  к узлам  которой  можно обращаться  и  изменять их содержимое. Средство  просмотра  XML,  построенное  с  использованием  Swing,  показано  на рис. 11.6; в окне отображается визуальное представление построенного дерева. -inlx 1 inventory О  book 9  •  ISBN D  0393040009 |j  Points  Unknown 9  •  price

9  • book 9  •  ISBN •  1579550088 9  •  title Q  A New Kind  of Science 9  C3  price D  Л  0.75

©-•tonok «-•book

l ~ l  Узлы, представляющие элементы [_|  Узлы,  представляющие текст Рис,  T f . e ,  Дерево,  построенное  средствами  DOM

Дерево, изображенное на рис.  11.6, соответствует следующему XML-коду, version="1.0"  encoding="ISO-6859-l"?> //  Полностью код  представлен  в  листинге  11.5,в

0393040009

Разбор XML-кода 

331

Points Unknown $23.96

157 9550088 A New Kind of  Science $10.75

Java-приложение,  показанное  на  рис.  11.6,  преобразует  XML-код  в  дерево  DOM  с помощью  компонента  bean  DOMParserBean.  Этот  компонент,  код  которого  содержится  в  листинге  11.8,а,  использует средство разбора Apache  Xerces2. ЛИСТИНГ 11.8,a. /WEB- INF/ c l a s s e s /bean s/wnl/dom/DQMParserBean. Java package  beans.xml.dom; import  import  import  import  import  import 

Java.io.IQException; Java.io.FileInputStream; org.apache.xerces.parsers.DOMParser; org.xml.sax.InputSource; org.xml.sax.SAXException; org.w3c.dom.Document;

p u b l i c  c l a s s  DOMParserBean  { p r i v a t e  DOMParserBean()  U  //  Экземпляр  класса //  создать  невозможно. p u b l i c  s t a t i c  Document  getDocument(String  f i l e ) throws  SAXException,  IOException  { DOMParser  p a r s e r  =  new  DOMParser(); p a r s e r . p a r s e ( n e w  InputSource(new  F i l e I n p u t S t r e a m ( f i l e ) ) ) ; r e t u r n  p a r s e r , g e t D o c u m e n t ( ) ;

Код,  ориентированный  на  конкретное  средство  разбора,  сосредоточен  в  статическом  методе  DOMParserBean. getDocument,  который  создает  документ  DOM.  При вызове  этому  методу передается  имя  файла.  Класс  DOMParserBean  существует  исключительно ради  реализации  метода getDocument,  следовательно,  создавать  экземпляр данного  класса  нецелесообразно.  По  этой  причине  конструктор  класса  объявлен  как p r i v a t e .  (Поскольку  создать  экземпляр  класса невозможно  DOMParserBean  не является "полноценным" компонентом bean.) Имея  документ,  созданный  в  результате  работы  метода  DOMParserBean. getDocument,  вы  можете  обращаться  к  любой  его  части  и  при  необходимости  вносить  требуемые  изменения.  После  окончания  работы  с  документом  вы  можете  снова создать  на  его  основе  XML-файл.  На рис.  11.7  показано  приложение,  которое  позволяет изменять значения цен в XML-файле, показанном на рис.  11.6. 2 Java-приложение, показанное на рис. 11.6, ж будет обсуждаться в данной книге, но вы можете скопировать его, обратившись по адресу http://-unma.phplr.mm/advisp. - Прим. авт.

332  Глава 1 1 . XML Приложение  на  рис.  11.7  состоит  из  двух JSF-документов  и  иллюстрирует  три  алгоритма, реализуемых большинством DOM-прилжоений. •  Разбор XML-кода и создание DOM-документа. •  Модификация  существующего документа. •  Создание XML-файла на базе существующего документа. $>JDQM ExHrffe-MEmoft Intend Еиркая

n J

Book  Inventory:

pie

£dlr

page import=rorg.w3c.dom.ModeListf  %> page import='beans.xml.dom.DOMParserBean'  %> page  import='javax.servlet.http.HttpServletRequest' page import='Java.util.Enumeration'  %>

Разбор XML-кода 

 0)  { for[int i=0; i  String value = getElementText(next); if (value != null is value.charAt(0)  !=  l\n')  ( try { out.print(value  +  "
"); } catch(Exception ex)  (} printXML(next, out); if(next.getNodeTypef)  — Node.ELEMENT_NODE)  { String name - next.getNodeNamef); try ( out.print("filt;" + "/" + name + "Sgt;
") if(name.equals("book")) out.print ("
");

} catch{Exception  ex)  О }

I private String getElementText(Node element)  ( Node child - element.getFirstChildt); String text ќ null; iflchild  != null) text = child.getNodeValue(); return text;

Подобно  showDocument  и  u p d a t e P r i c e s ,  метод printXML  осуществляет перебор дерева документа  и  выполняет  рекурсивные  вызовы.  Продвигаясь вниз  по дереву,  метод  выводит  открывающие  дескрипторы,  а  возвращаясь  по  стеку  рекурсивных  вызовов, выводит закрывающие дескрипторы. В  большинстве  случаев  желательно  освобождать JSP-документы  от  скриптлетов  и деклараций.  Примеры,  приведенные  в  данном  разделе,  представляют  собой  отступление от общего правила; это сделано для того, чтобы изложить основы DOM в более доступной  форме.  В  следующем  разделе  мы  обсудим  набор  пользовательских  дескрипторов, которые инкапсулируют весь Java-код, упрощая работу авторов Web-страниц.

338  Глава 11. XML

Пользовательские дескрипторы для DOM-разбора Простой JSP-файл, представленный в листинге 11.9,а, функционально идентичен громоздкому документу, который был показан в листинге 11.8,6. Данные, выводимые обоими документами, показаны в левом окне на рис. 11.7. Листинг 11.9,a. test_dom_books_with_tags. jap DOM  Custom  Tags  Example f taglib uri= /WEB-INF/tlds/dom.tld'  prefix='dom'%> taglib uri='/WEB-INF/tlds/app.tld'  prefix='app'l>



Book  Inventory:









i Addretl |a hBnVyiocAosLSOTOAmWert^^deta  №

о

Today's Date is: Fri Dec 1511:46:45 MST 2000

i Рис.  11.8.  Использование  XSLT  для  преобразования XML-документа  в  HTML-формат Листинг 11.10,6. /date.xsl

Processing XML Generated with JSP

Today's Date is:  Otsl:apply-templatas/> 





XSLT  —  это  декларативный  язык,  основанный  на  правилах-шаблонах.  Каждое  правило-шаблон  состоит из  обртца (pattern)  и  действия (action),  которые задаются  в составе  дескриптора  x s l :  t e m p l a t e . Например,  влистинге  11.10,а  используются  два  правила-шаблона: ... ... В  данных  правилах-шаблонах  используются  образцы  "/"  (корневой  элемент)  и " d a t e "  (элемент  d a t e ) .  Действия  задаются  в  теле  дескриптора. Для  правила,  распознающего  корневой  элемент  ("/")>  создается  следующий HTML-код.

Processing XML Generated with JSP



lnventory

Item Bescription Price



:template> •cxsl:template match="item">

352  Глава 11. XML



:stylesheet> С помощью листа стилей, представленного в листинге 11.1,в, создается HTMLтаблица. Основная часть HTML-кода, включая дескриптор TABLE и заголовки таблицы, генерируется при обработке корневого элемента. Строки таблицы создаются для каждого элемента item, а данные включаются в таблицу при обработке элементов name, description и price. Класс поддержки для пользовательского дескриптора xslt: apply приведен в листинге 11.11,г. Листинг 11.11,г. /WEB-INF/classes/tags/xml/xslt/XSLTApplyTag. Java package tags. xml, xslt; import  Java,io.StringReader; import  javax.servlet.ServletContext; import  javax,servlet.jsp.JspException; import  javax.servlet.http.HttpServletRequest; import  javax.servlet.http.HttpServletResponse; import  javax,servlet.jsp.tagext.BodyTagSupport; import  beans.xml.xsit.XSLTProcessorBean; public class XSLTApplyTag extends BodyTagSupport  ( private String salt public void setXsl(String xsl)  ( this.xsl = xsl;

J

public int doAfterBody(}  throws JspException  { XSLTProcessorBean xslBean = new XSLTProcessorBean(); ServletContext context ќ= pageContext.getServletContext() String body = bodyContent.getstring().trim(); try { if(body.equals("")) { throw new JspException("The body of this tag  "  + "must be XML."); ) xslBean.process(new StringReader(body), context.getResourceAsStream(xsl), getPreviousOut()) ; } catch(Exception ex)  ( throw new JspException(ex.getMessaget)); } return  SKIP_BODY;

Преобразование XML-документов 

353

Класс поддержки, представленный в листинге  11.11,г,  использует для выполнения XSLT-преобразования  компонент bean.  Если  тело дескриптора отсутствует,  генерируется  исключение,  в противном  случае  класс поддержки  вызывает метод p r o c e s s  компонента  bean,  который  и  осуществляет  преобразование.  Код  компонента  приведен  в листинге 11.1,д. Листинг11.11,д XaltProcessorBean.Java package beans.xml.xslt; import  Java.io.InputStream; import Java.io.Reader; import  javax.servlet.ServletException; import  javax.servlet.http.HttpServletRequest; import  javax.servlet.http.HttpServletResponse; import  javax.servlet.jsp.JspWriter; import  org.apache.xalan.xslt.XSLTInputSource; import  org.apache.xaIan.xslt.XSLTProcessor; import  оrg,apache.xaIan.xslt.XSLTProcessorFactory; import  org.apache.xalan.xslt.XSLTResultTarget; public class XSLTProcessorBean implements  Java.io-Serializable  { public void process(Reader xmlrdr,  InputStream xslstrm, JspWriter  writer) throws Java.io.IOException,  ServletException  ( process(new XSLTInputSource(xmlrdr), new  XSLTInputSource[xslstrm),  writer); } public void process(XSLTInputSource xmlsrc, XSLTInputSource xslsrc, JspWriter  writer) throws Java.io.IOException,  ServletException { try  { XSLTProcessorFactory.getProcessor().process( xmlsrc, xslsrc, new XSLTResultTarget(writer)); } catch(Exception ex)  { throw new ServletException(ex[ ;

В  представленном  выше  компоненте  используется  XSLT-процессор  Apache  Xalan, таким  образом,  данный  компонент  инкапсулирует  непереносимый  код.  Используя  фабрику XSLT-процессоров,  метод  p r o c e s s  получает XSLT-процессор,  использует  его  для выполнения XSLT-n ре образования и записывает результаты в поток J s p W r i t s r .

354  Глава 1 1 . XML

Применение XSI-Тдля генерации JSP-кода на  этапе компиляции Только  что  мы  рассмотрели  пример  применения  пользовательского  дескриптора для  XSLT-преобразования  в  процессе  выполнения  программы. В  данном  разделе  XSLT-преобразованию  будет  подвергнут  тот  же  XML-файл,  однако  преобразование  будет  осуществляться  на  этапе  компиляции,  а  результатом  его будет JSP-документ.  Этот документ,  в свою  очередь,  генерирует  HTML-код,  который был показан на рис. 11.10. Лист  стилей,  применяемый  при  обработке  файла  i n v e n t o r y ,  xml,  показан  в  листинге  II.12,а. Листинг  11.12,а.  Лист  стилей,  используемый  для  генерации  JSP-файла



Inventory

Inventory as  of  new  java.util.Date()

ltem
Desctiption Price



:template match="item">



Файл inventory, xsl, представленный в листинге 11.12,а, задается в командной строке следующим образом; >  java  org.apache.xalan.xslt.Process  -in  inventory.xml  -xsl inventory.xsl  -out  inventory.jsp

Преобразование  XML*  доку  ментов  355

Файл  inventory. xird  6ЬЕЛ  показан  ранее  на  рис. 11.3.  Содержимое JSP-файла {inventory. j sp), полученного в результате преобразования, приведено в листинге 11.12,6. Листинг 11.12,6. JSP-документ, полученный в результате JSP-преобразования

Inventory Inventory as of

new  java.util.Date() :expression>

item Description Price Antique broach from the  early  17OO's 1238.99 Gumby  and  Pokey pliable action  figures 2.99 ...  Остальные пункты пропущены ...

JSP-файл, представленный в листинге 11.12,6, генерирует тот же HTML-код, что и файл, показанный на рис. 11.10, однако сам документ отличается тем, что в нем используется  альтернативный  XML-синтаксис.  Это  необходимо  потому,  что  XSLTпреобразовапие может осуществляться только над XML-данными, (Согласно спецификации JSP  1.2, контейнер сервлетов должен поддерживать альтернативный JSPсинтаксис.)

Совет Использование XML-формата при построении JSP Вместо того чтобы применять скриптлеты и выражения, вы можете составить JSPдокумент в XML-формате. Спецификация JSP 1.1 определяет XML-запнсь всех элеMeHTOBjSP,  например, дескриптор j s p : s c r i p t l e t  можно использовать вместо обычной записи скриптлета.

356  Глава  11.  XML

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

Два  подхода  к  выполнению  XSLT-преобразования Выше  рассматривалось  выполнение XSLT-преобразования  на этапе  компиляции  и во  время  работы  приложения.  Для  преобразования  в  процессе  работы  применяется пользовательский дескриптор; в этом случае создается HTML-код. Преобразование на этапе  компиляции  выполняется  над  XML-файлом,  и  в  результате  генерируется JSPдокумент; этот документ,  в свою очередь,  генерирует HTML-код, При  использовании  обоих  способов  работа  начинается  над  одним  и  тем  же  XMLфайлом,  а в  результате  получается  один  и  тот же  HTML-код.  Преобразование  на этапе  выполнения  приложения  обеспечивает  большую  степень  гибкости,  поскольку  в этом  случае  нет  необходимости  в  компиляции  кода.  Однако  за такую  гибкость  приходится  платить  снижением  производительности,  поскольку XSLT-преобразование  выполняется  с  очень  малой  скоростью.  Чтобы  повысить  производительность,  приходится прибегать к преобразованию на этапе компиляции.

Использование  XPath XPatli —  это  язык,  используемый  в  процессе  XSLT-преобразования  для  проверки соответствия  XML-элементов  образцам.  Ранее  в  данной  главе  встречалось  правилошаблон,  в котором образец " / " обозначал  корневой  элемент: ... Приведенное  выше  правило  применяется  к  элементу,  который  соответствует XPath-выражению  " / " ,  т.е.  к  корневому  элементу.  Данное  XPath-выражение  чрезвычайно  простое,  однако другие  выражения  могут быть достаточно сложными. Средства  XPath  удобно  применять  для  поиска  XML-элементов,  но  их  использование  снижает  производительность при  XSLT-преобразовании, XPath —  независимый  язык,  поэтому  большинство  XSLT-процессоров  позволяет использовать  XPath  без  XSLT.  В  данном  разделе  вы  узнаете  об  использовании  XPath при  работе  с  XSLT-процессором Apache  Xalan. JSP-документ,  показанный  на  рис.  11.11,  содержит  пользовательский  дескриптор, предназначенный  для  выбора элементов  из  XML-файла.  Этот дескриптор  использует компонент  bean,  который,  в  свою  очередь,  применяет XPath  для  поиска  элементов  в XML-файле.

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

File 

Edit  XKW 

357

FjjvorjiB  Took  Jjc|)

Conference Attendees Cslesline. Horace Graves.  Samuel Lopez.Jose Martin.  Roy Royat,  Stanley Woodard, Daniel

2] Oa» 

 ;

' I  • ']Sla«IHMnir

Ряс.  11.11.  Применение  пользовательских  дескрипторов для работы с XPath

Код|5Р-документа, показанного на рис. 11.11, приведен в листинге 11.13,а. Листинг  11.13,a.  test_xpath.jsp XPath  Example



В  листинге  11.13,в  содержится  класс  поддержки  пользовательского  дескриптора selectNodes. Листинг  11.13,s.  /WEB-INF/cIasses/tags/soni/xpath/XPathTag.java package  tags.xml.xpath; import  java.util.Collection; import  Java.util.Vector; import  javax.servlet.jsp.JspException; import  javax.servlet.jsp.PageContext; import  org.w3c.dom.Document; import org.w3c.dom.Node; import  org.w3c.dom.NodeList; import  beans.xml.dom.DOMParsecBean; import  beans.xml.xpath.XPathBean; import  tags.util.IteratocTag; public class XPathTag extends  IteratorTag  { private String file,  expr; private boolean force = false; public void setXmlFile{String file)  { this.file = file;  } public void setExpr(String expr)  (  this.expr = expr;  } public void setForce(boolean force)  ( this.force = force;  } public int doStartTagO  throws JspException  { Document document =  (Document)pageContext.getAttribute{file, PageContext.SESSION_SCOPE); NodeList list = null; if(force ]| document -= null)  { try  { document - DOMParserBean.getDocument(file); } catch[Exception ex)  ( throw new JspException(ex.getMessage()); } pageContext.setAttribute(file, document, PageContext.SESSION^SCOPE);

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

359

try { setCollection( collection(XPathBean.process(document,  expr})); } catch(Exception ex)  { throw new JspException(ex.getMessage[)); } return super.doStartTag(); } public void release!)  ( file = expr = null; force ќ» false; ) private Collection collection(NodeList list)  ( Vector vector = new Vector (); int length - list.getLength(); for(int i=0;  i   "витрина"—> выбор  продукта—>  покупка. •  Архитектура MVC:  модель,  просмотр  и  контроллеры. •  Прочие  средства:  П8п,  аутентификация,  HTML-формы,  формы,  чувствительные к повторной активизации, SSL и XML. Поскольку  используемые  здесь  понятия  обсуждались  на  протяжении  всей  книги, данная  глава в основном  посвящена рассмотрению  программного  кода.

Интерактивный магазин Как  показано  на  рис.  12.1,  приложение,  реализующее  интерактивный  магазин,  позволяет пользователю сделать покупку,  выбирая  до  10  различных  фруктов.  На исходной странице  раскрыта  основная  цель  создания  данного  приложения,  т.е.  описаны  JSPтехнологии,  использованные  при  его  создании.  Исходная  страница  позволяет  пользователю обратиться к "витрине",  выбрать фрукты, а затем проверить свой выбор. На  рис.  12.1  представлен  сценарий  развития  событий,  который  можно  назвать "регистрация  пользователя,  который  собирается  сделать  покупку".  Страница,  содержащая  "витрину",  также  может служить  исходным  пунктом  для  других сценариев  развития от "выбора  пользователем языка взаимодействия" до "создания  новой учетной записи". Сценарий  развития  событий,  представленный  на  рис.  12.1,  можно  описать  следующим  образом. 1.  Пользователь  обращается  к  исходной  странице  и  активизирует  кнопку Go  Shopping. 2.  Управление  передается документу,  представляющему  "витрину".  Этот документ извлекает данные  из  базы,  предоставляет  пользователю  возможность  выбирать продукцию  и  отображает  данные  о  выбранных  товарах  в  "корзинке",  роль  которой  выполняет левая  часть Web-страницы. 3.  Пользователь  активизирует  кнопку  Checkout,  расположенную  в  левой  части Web-страницы. 4.  Пользователь  уже  зарегистрирован  {вопросы  регистрации  будут  рассмотрены далее  в  этой  главе),  и  приложение  передает  управление  документу,  позволяющему  пользователю  проверить  свой  выбор.  Этот  документ  отображает  счет, соответствующий  текущему  содержимому  "корзинки". 5.  Пользователь  активизирует кнопку Purchase  the  Items  Listed  Above  на  странице проверки. 6.  Приложение  передает  управление  документу,  предназначенному  для  приобретения  выбранных товаров,  и  выводит дату выполнения  заказа. В  следующем  разделе  мы  начнем обсуждение описанного  выше  сценария  развития событий,  но  перед  этим  рассмотрим  структуру  каталогов  приложения,  представленную на рис.  12.2.

Интерактивный магазин  365

Ш

•J—h  - ,  — .

шва °"—•  i

-

Welcome  to  FmitStand.oom

Weteome  to  FruitStand.cam

Л MMil 2 JSP Л и й Ш К * T-I 

т.

«•Mb It |HI*I

:--.

И  ^  :,  i

"""^

« M M

рп-ч i'.^JJ

-КЙ Г

t

ш  •

'-—

link  F"3

FBI

fit-

•an FruilStancl.com

FrurtSlane.com

^.«« r i  ,•  ..••  •..  h - 

B D 

  r

I 4  вв  2 Л J 0 '

IBM  РП 

UP

Рис.  12. Т.  Основной  сценарий  развития:  исходная  страница,  "витрина",  проверка выбранных  товаров  и  покупка  (соответствующие  Web-страницы  расположены  по часовой  стрелке,  начиная  с верхнего левого угла)

366 Глава 12. Приложение на базе JSP

(  Содержимое каталога  Л -  -  i  верхнего  уровня



ЁН

case_studv  -' ;  graphics:

ь

:  О  flags £j fruit Б £j Web-inf ' ' B O classes Й- О  action :--Ц] actions fti  i_J  beans' Н - Ш З  tags  ^  , ^ ' _ +

|sp

  '

J

J

Содержимое данного каталога и его подкаталогов Защищеноотнепосредственногодостуга(данное правило поддерживается не всеми контейнерами) Основу данного приложения составляет базовый набор классов. Этот набор включает средства, реализующие модель (beans и база данных), просмотры (JSP-документы] и контроллеры (сервлетыи действия)

В этой книге было рассмотрено около 50 пользовательских дескрипторов. Многие из них вошли в состав данного приложения

util J  Все JSP-документы размещены в этом каталоге;  \ для каждого документа выделен отдельный

V  подкаталог 

J

М - модель, V — просмотры. С — контроллеры Рис.  12.2.  Структура каталогов приложения и  назначение содержащихся  в них файлов

Почти  все  средства,  использованные  при  создании  приложения,  за  исключением каталога  g r a p h i c s  и  нескольких  файлов  в  корневом  каталоге,  сосредоточены  в  подкаталогах  /WEB-INF.  В  соответствии  со  спецификацией  сервлетов  прямой  доступ  к файлам  в  подкаталогах.  /WEB-INF  предоставляться  не  должен,  таким  образом,  броузер  может  непосредственно  обратиться  лишь  к  небольшой  части  средств  в  составе приложения. При  создании  приложения,  реализующего интерактивный магазин, был использован  базовый  набор  классов  Model  2,  обсуждавшийся  в  главе  6.  Этот  базовый  набор классов  представляет  собой  реализацию  архитектуры  "модель-просмотр-контроллер"  (MVC)  и  позволяет создавать  приложения  из  компонентов,  причем  компоненты допускают  замену.  Модель  состоит  из  базы  данных  и  компонентов  bean,  роль  просмотров  выполняют JSP-документы,  а  контроллерами,  реализующими  сценарии  развития событий,  являются сервлеты и действия. JSP-файлы,  входящие  в  состав  приложения,  содержатся  в  подкаталогах  каталога /WEB-INF/jsp,  причем  для  каждого  документа  выделяется  отдельный  подкаталог. Например,  средства,  реализующие  исходную  страницу,  содержатся  в  каталоге  /WEBINF/  jsp/homepage. Приложение  было  проверено  при  работе  с  серверами  Resin  1.2.1  и  Tomcat  3.2  final. Организовать выполнение приложения можно одним из двух способов.  Самый  простой способ—  создать JAR-файл  приложения  и  разместить  его  в  каталоге  ?ТОМСАТ_НОМЕ/ webapps  (для  сервера  Tomcat)  или  $RESIN_HOME/webapps  (для  сервера  Resin).  Здесь $ТОМСАТ_НОМЕ  и  $RESIN_HOME  — каталоги,  в которых были инсталлированы  соответственно  контейнеры  Tomcat  и  Resin.  Если  же  вы  собираетесь  модифицировать  прило-

Интерактивный магазин 

367

жение, следует указать с веления о нем в конфигурационных файлах Tomcat и Resin, Так, например,  при  работе  с  сервером  Tomcal  3.2  final  в  конфигурационный  файл $TOMCAT_HOME/conf/server.  xml  надо  поместить  следующую  информацию:

Для  сервера  Resin  в  файл  $RESIN_HOME/conf/resin.conf  следует  включить приведенные  ниже данные.

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

Исходная страница Набор  файлов,  предназначенных для  формирования  приветственного  сообщения, состоит  из  одного  файла  / i n d e x ,  j s p .  Этот  файл  указам  в  /WEB-INF/web.xml  следующим  образом:









1 iwy21,  2001-

*| Рис. /2.5. Web-страница, предназначенная для проверки выбора Как  и  документ,  реализующий  "витрину"  {листинг  12.3,а),  рассматриваемый  документ воспроизводит область, В данном случае используется область CHECKOUT_REGION, определенная следующим образом: < r e g i o n : d e f i n e  id='CHECKOUT_REGIONf  region-'LOGIH_REGION'> < r e g i o n : p u t  s e c t i o n = ' c o n t e n t ' c o n t e n t = '  /WEB-INF/jsp/checkout/content.jsp'/>

Область CHECKOUT_REGION является расширением LOGIM_REGIOW и переопределяет раздел содержимого.  Это  означает,  что область,  предназначенная для  проверки  выбора,  идентична  области  регистрации,  за  исключением  содержимого  Web-страницы. Раздел  содержимого  реализован  с  помощью  документа  /WEB-INF/jsp/checkout/ c o n t e n t , jsp,  код которого приведен в листинге  12.5,в.

382 

Глава 12. Приложение на базе JSP

Листинг  12.5,в.  /WEB-XNF/jsp/cheekout/content. jsp









Интерактивный магазин  383



 





Подобно полосе в левой части Web-страницы  (листинг 12.4,6), документ, определяющий  содержимое  области  проверки,  применяет  для  перебора  товаров,  находящихся  в  "корзинке",  пользовательский  дескриптор  C a r t l t e r a t o r ,  На  основании данных, извлекаемых посредством данного дескриптора, формируется счет. Для  формирования  счета  используется  также  компонент  User,  содержащийся  в текущем  сеансе.  Вопросы,  связанные с созданием  компонента User  и сохранением его в области видимости сеанса, будут подробно рассмотрены далее в этой главе. В состав страницы,  предназначенной для  проверки  выбора пользователя,  также входит форма с кнопкой. При активизации этой кнопки формируется запрос к ресурсу purchase-action, do, который перенаправляется действию a c t i o n s . PurchaseAction. Код класса PurchaseAction приведен влистинге  12.5,г. ЛИСТИНГ 12.5,Г.  /WEB-INF/classes/actions/Purchas&Acfcion . Java package  actions; import  import  import  import  import 

javax.servlet,ServletException; javax.servlet.http.HttpServlet; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse; javax,servlet.http.HttpSession;

import  beans.app.ShoppingCart; import  beans.app.User; import  action.ActionBase; import  action.ActionRouter; public  class  Purchaseftction  extends  ActionBase implements  beans.app.Constants, tags.security,Constants  ( public  ActionRouter  perform(HttpServlet  servlet, HttpServletRequest  req, HttpServletResponse  res} throws  ServletException  I HttpSession  session  =  req.getSession(); ShoppingCart  cart  =  [ShoppingCart)session.getAttribute( 5H0PPING_CART_KEY) if(cart  ==  null)  {

384  Глава 12. Приложение на базе JSP throw  new  ServletException("Cart  not  found"); return  new  ActionRouter("purchase-page");

Как и действие, приведенное в листинге 12.5,а,  PurchaseAction проверяет наличие "корзинки" покупателя.  После этого действие перенаправляет запрос,  используя для перенаправления логическое имя purchase-page.

Приобретение товара Приобретение  товара  осуществляется  с  помощью  простого JSP-документа,  который  отображает  благодарность  пользователю  за  покупку  и  выводит дату  выполнения заказа. Внешний вид этого документа приведен на рис.  12.6. • №ronft Irtsrat F*pla« fcjli )Mw fewtlie Tool!

Zi

FruitStand.com Tfiank уеш  Гог your  piir:ha5e. Veur  OfiaervMill  be  shipped  on  2/21/01 , * i  Thanks  for  9[opp?ng  Jiy.

, 200t.

Рис.  12.6. Документ, соответствующий приобретению товара Логическое  имя  purchase-page,  на  которое  ссылается  действие,  приведенное в листинге  12.5,г,  отображается  в  документ  /WEB-INF/jsp/purchase/content. jsp, код которого представлен в листинге  12.5.Д. Листинг12.5,д. AJEB-INF/isp/purchase/page.jsp

Подобно исходной странице, "витрине" и странице проверки, документ page. j sp использует область, описанную ниже.





В данном документе для отображения текста используется дескриптор  i 16n :message, кроме того, форматирование даты выполнения заказа осуществляется посредством дескриптора  i l 8 n :  format. На этом мы заканчиваем обсуждение главного сценария развития. Остальная часть данной  главы  посвящена  рассмотрению  базового  набора  классов  MVC,  а  также средств интернационализации и аутентификации.

Базовый набор классов Model 2 Вам уже,  вероятно,  стало ясно,  что  рассматриваемое  в данной  главе  приложение представляет  собой  небольшие  фрагменты  кода,  реализующие  отдельные  функциональные возможности,  которые объединяются посредством базового  набора классов. Базовый  набор  Model  2,  который  обсуждался  в  главе  6,  позволяет реализовать  Webприложение  в рамках архитектуры "модель-просмотр-контроллер"  (MVC).  В данном разделе  мы обсудим базовый  набор  классов; вначале рассмотрим модель,  а затем  просмотры и контроллеры.

Модель Модель  включает базу данных  и  компоненты  bean,  показанные  на  рис.  12.7.  Компоненты,  находящиеся  в  каталоге  WEB-INF/classes/beans/app,  и  понятия,  которые они представляют, перечислены ниже. •  User: покупатель, пользующийся услугами магазина. •  Users: набор пользователей, инициализируемый посредством базы данных. •  Item: товар, предназначенный для продажи. •  Inventory: набор товаров. •  ShoppingCart: совокупность товаров, выбранных потребителем.

386 

Глава 12. Приложение на базе JSP

Кроме  перечисленных  выше  компонентов  bean  в  приложении  также  определен ряд констант. •*"*"!  case_study i+  J  graphics

Б  Gj  Web-inf 1 В  СЛ  classes "СЗ  action •Cl  actions

В-Gill beans

ShoppingCart Item [nventoiy Constants

Г-1-1ЙД  app —•{аЯ  f«ms _J  email

Ej  html : - £ j iiBn

:-i:a |dbc Sj templates Й-СЗ  tags

util 

- ( f 5  CreateDB

Java

Рис.  J2.7. Модель: компоненты bean и база данных

В  процессе  работы  приложения  используется  база данных,  содержащая  описание набора  товаров  и  список  пользователей.  База  данных  создается  посредством  Javaприложения C r e a t e DB,  которое находится  в каталоге  /WEB- I N F / c l a s s e s / u t i l .

База  данных На  рис.  12.8  показана таблица  I n v e n t o r y  базы  данных.  Таблица  состоит  из  строк  (записей),  каждая  из  которых  включает код, наименование товара и цену. В  базе  данных  также  находится  таблица  User,  представляющая  набор  пользователей.  Эта  таблица  включает  14  столбцов, поэтому  представить  ее  на  рисунке  затруднительно.  В  таблице User  содержится  имя  и  фамилия  пользователя,  его  адрес,  информация о платежной карточке,  регистрационное имя,  пароль и роль,  которой  принадлежит пользователь. Код  приложения  CreateDB,  которое  создает  базу  данных, приведен  в листинге  12.6.

Рис.  12.8.  Таблица  inventory

JSKU 11001

NAME

PRICE  j

apple

0.29

banana

0  69

cantaloupe

0.19

grapefruit

049

1005

grapes

a79

1006

fcnri

0.99

1007

peach

039

100В

pear

0.89

1003

pineapple

0.29

1010

strawberry

0.B8

1011

watermelon

0.29

1002

J1003 -I004

Базовый набор классов Model 2 387

Листинг 12.6. /WEB-INF/util/CreateDB.Java import  java.sql.Connection; import  Java.sql.DriverManager; import  Java.sql.SQLException; import  Java.sql.Statement; public class CreateDB  { private Connection  conn; private Statement stmt; public static void main(String args [])  { new CreateDB(); } public CreateDB()  { try { loadJDBCDriverО; conn = getConnection{"F:/databases/sunpress"); stmt ќ conn.createStatement(); createTables(stmt); populateTables(stmt) ; stmt.close() ; conn.close() ; DriverManager.getConnection( "jdbc:cloudscape:;shutdown=true")

I

} catch(SQLException ex)  I ex.printStackTrace(); )

private void createTables(Statement stmt)  { try ( 3tmt.execute!"CREATE TABLE Users  {" + "FIRST_NAME VARCHAR(IS),  " + "LAST_NAME VARCHAR[25), " + "ADDRESS VARCHAR(35) f  " + " C I T Y  V A R C H A R U 5 ) ,  "  + "STATE  VARCHAR







. , . . . ; , - , . . 

. . 



.-.-

Листинг 12.9,В. /WEB-IKF/classea/app^zh .properties click=\u70b9\u51fb here=\u8fd9\u91cc messages.Iogin-header-title=\u6b22\u8fce\u514 9\u4e34 messages.today=\u

< / t d x t d  height='25'>


JSP-файл, показанный в листинге 12.9,г, задает действие для каждого из флагов в соответствии с текущей Web-страницей  и страной,  которую представляет этот флаг. Например,  файл  flags, jsp  включается  в  документ  /WEB-INF/ j sp/storefront/ sidebar, поэтому, если пользователь щелкает на изображении китайского флага, вызывается следующее действие: http://localhost:80BQ/case-study/update-locale-action.do?page=/WEBINF/jsp/storefront/page,jsp&country=ZH Действие, связанное с флагом в файле flags.jsp, приводит к вызову класса UpdateLocaleAction, код которого представлен в листинге 12.9, д. Листинг 12.9,д. /WEB-INP/claaeee/actione/UpdateLocalaAction. java package actions; import java.util.Locale; import  javax.servlet.ServletException; import  javax.servlet.http.HttpServlet; import  javax.servlet.http.HttpServletRequest; import  javax.servlet.http.HttpServletResponse; import action.ActionBase; import action.ActionRouter; public class  UpdateLocaleAction extends ActionBase implements beans.app.Constants  ( public ActionRouter perform(HttpServlet servlet, HttpServletRequest  req, HttpServletResponse  res) throws ServletException { Locale  locale = new Locale(req.getParameter("country"),"") String forwardTo - req.getParameter("page");

Аутентификация  409 req.getSession(true).setAttribute(LOCALE_KEY,  locale); res.setLocale(locale); return new ActionRouter(forwardTo,  true,  false);

1 1 Действие  U p d a t e L o c a l e A c t i o n  получает  посредством  параметра  запроса  информацию  о  стране  и,  соответственно,  о  требуемом  языке  (запрос  генерирует JSPфайл,  представленный  в  листинге  12.9,д).  Имея  сведения  о  языке,  действие  устанавливает  атрибут  сеанса,  который  определяет  текущий  регион,  задает  регион  для  ответа и  перенаправляет запрос  исходному документу. После  того  как  исходный  документ отображается  повторно,  пользовательские  дескрипторы  i l 8 n  отображают  текст  на  выбранном  языке.  Эти  дескрипторы  используют информацию о регионе, связанную с сеансом конкретного пользователя.

Аутентификация Обращаться  к  рассматриваемому приложению  может каждый,  но только  зарегистрированные  пользователи  имеют  возможность  проверить  свои  покупки.  Ограничения,  связанные  с  защитой,  реализованы  в  рамках  сценария  развития  под  названием "аутентификация  пользователя",  В  данном  сценарии  развития  можно  выделить  следующие этапы. 1.  Если  пользователь  пытается  выполнить  проверку,  дескриптор  определяет,  зарегистрирован ли  пользователь. 2.  Если  пользователь  не  зарегистрирован,  дескриптор  передает управление  странице регистрации. 3.  На  странице  регистрации  пользователь  активизирует  ссылку,  соответствующую созданию учетной записи. 4.  Приложение отображает форму,  предназначенную для создания новой учетной записи. 5.  Пользователь заполняетформуи активизирует кнопку создания учетной записи. 6.  Приложение  повторно  выводит  форму  регистрации,  и  пользователь  регистрируется. Файлы,  участвующие  в  реализации  сценария  развития  "аутентификация  пользователя", показаны на рис.  12.14. После  активизации  кнопки  Checkout  на  странице  "витрины"  пользователь  перенаправляется к документу проверки.  Код этого документа показан в листинге  12.10,а.

410 Глава 12. Приложение на базе JSP

H l l  case_study Щ- Cj  graphics В  CJ  Web-inf 3  i"  1  classes 3 - £ 3 action _• J events j" I actions





H Cj beans В  CJ  tags £•••

validate-account

NewAccountAction ValidateAccountAction QueryAccountAction

] app

-ГП  email Cj  html

ш CJ idbc :

 - -i:"'"} logic 1 regions I  secuiity '  1 tokens •  LJ  util Ш-CJ  «ml j jsp • - Ш  accountCreated-  -  " Ш  checkout • Ci  createAccount  -  -  • - Ш  homepage - C J  login •-ГИ  loginFailed ••••{?Щ purchase

-Q  shared -^"1  showHint C3  storefront C3  templates J  lids jutii Java 

$  JSP

Рис.  72. /4.  Файлы,  участвующие в реализации  сценария  развития  "аутентификация пользователя" Листинг  12.10,a.  /WEB-INF/jsp/checkout/page.jэр



Класс  поддержки дескриптора  s e c u r i t y : enforceLogin,  присутствующего  в листинге 12.10,а, показан влистинге 12.10,6. Листинг  12.10,6.  /WEB-INF/jsp/classes/tags/security/ EnforcaLoginTag.  java  (фрагмент) package  tags.security; public class EnforceLoginTag extends TagSupport implements Constants { private String loginPage,  errorPage; public int doEndTag()  throws JspException { HttpSession session = pageContext.getSessionf); HttpServletRequest  req =  (HttpServletRequest)pageContext. getRequest(); String protectedPage - req.getServletPath(); if(session.getAttribute(USER_KEY}  = null}  ( session.setAttribute(LOGIN_FAGE_KEY,  loginPage); session.setAttribute(ERROR_PAGE_KEY,  errorPage); session.setAttribute(PROTECTED_PAGE_KEY, protectedPage);

I

try  { pageContext.forward(loginPage); return SKIP_PAGE; ) catch(Exception ex)  ( throw new JspException(ex.getMessage()); }

return EVAL_PAGE;

Дескриптор s e c u r i t y : enforceLogin проверяет, присутствует ли объект User в области видимости сеанса; при наличии объекта обрабатывается остальная часть документа, в противном случае управление передается документу регистрации. Содержимое документа регистрации приведено в листинге 12.10,в. ЛИСТИНГ 12.10.В. /WEB-INF/jsp/login/content.jsp

taglib uri-'il8n' prefix='il8n'  %>

412  Глава 12. Приложение на базе JSP



f

'/>






В  форме,  представленной  в  листинге  12.10.Л,  нет  ничего  примечательного.  Единственная  особенность,  достойная  внимания,  состоит  в  том,  что  поля  редактирования, соответствующие  имени  пользователя  и  паролю,  заполняются  данными,  хранящимися  в  области  видимости  сеанса,  поэтому  пользователю  нет  необходимости  повторно вводить  их.  Действие  NewAccountAction,  приведенное  в  листинге  12.10,и,  помещает имя  пользователя  и  пароль  в  область  видимости  сеанса. Атрибут  a c t i o n  регистрационной  формы  имеет  значение  a u t h e n t i c a t e ;  в  файле web.xml  это  логическое  имя  отображается  в  A p p A u t h e n t i c a t e S e r v l e t .  Код  сервлета A p p A u t h e n t i c a t e S e r v l e t  представлен  в листинге  12.10,м. Листинг 12.10,М-/WEB-lNF/claases/AppAuthenticafceServlet.Java import javax.servlet.ServletContext; import beans.app.Users; import beans.app.User; public class AppAuthenticateServlet extends AuthenticateServlet implements beans.app.Constants, tags.jdbc.Constants { public Object getUser(String username,  String password)  [ ServletContext ctx = getServletContext(); Users users = (Users)ctx.getAttribute(USERS_ECEY); return users.getUser(username, password};

Класс  A p p A u t h e n t i c a t e S e r v l e t  является  подклассом  абстрактного  класса A u t h e n t i c a t e S e r v l e t  и  реализует  метод  g e t U s e r .  Если  пользователь,  соответствующий  указанным  регистрационному  имени  и  паролю,  существует,  метод  g e t U s e r возвращает  объект  User,  в  противном  случае  возвращается  значение  n u l l .  Код  класса A u t h e n t i c a t e S e r v l e t  приведен в листинге  12.10,н.

Аутентификация 

Листинг 12.10,Н, /WEB-lNF/claases/AuthenticateServlet.Java import  javax.servlet.ServletException; import  javax.servlet.http.HttpServlet; import  javax.servlet.http.HttpServletRequest; import  javax.servlet.http.HttpServletResponse; import j avax.servlet.http.HttpSession; import  Java.io.lOException; public abstract  class AuthenticateServlet  extends  HttpServlet implements  beans.app.Constants, tags.security.Constants  ( abstract Object getUser(String username,  String pwd); public void service(HttpServletRequest  req, HttpServletResponse  res) throws lOException,  ServletException  { HttpSession session = req.getSession(); String  uname  =  req.getParameter("userNarae"); String  pwd  =  req.getParameter("password"); Object  user  = getUser(uname,  pwd) ; session.setAttribute(USERNAME_KEY,  uname); session.setAttribute(PASSWORD_KEY, pwd); if(user -= null)  {  // Неавторизованный доступ String loginPage =  (String)session. getAttribute(LOGIN_PAGE_KEY); String errorPage =  (String)session. getAttribute(ERROR_PAGE_KEY); String forwardTo = errorPage  != null ? errorPage  : loginPage; session.setAttribute(LOGIN_ERROR_KEY, "Username:  " + uname + " and " + "Password:  "  + pwd + " are not valid.");

I

getServletContext(}.getRequestDispatcher( res.encodeURMforwardTo)).forward(req,res);

else  (  // Авторизованный доступ String protectedPage =  (String)session. getAttribute(FROTECTED_PAGE_KEY); session.removeAttribute(LOGIN_PAGE_KEY); session.removeAttribute(ERROR_PAGE_KEY); session.removeAttribute(PROTECTED_PAGE_KEY); session.removeAttribute(LOGIN_ERROR_KEY); session.setAttribute(USER_KEY,  user); getServletContext().getRequestDispatcher( res.encodeURL(protectedPage)).forward(req,res);

419

420 Глава 12. Приложение на базе JSP Класс  A u t h e n t i c a t e S e r v l e t  отличается  от одноименного  класса,  обсуждавшегося в главе 6, лишь тем, что в сералете,  представленном в листинге  12.10,н, объявлен абстрактный  метод,  предназначенный  для  поиска  пользователя.  Метод  s e r v i c e  сервлета сохраняет новый объект, представляющий пользователя, в области видимости сеанса и передаст управление  защищенном^' документу,  в данном  случае  это—  /WEB-INF/jsp/ checkout/page, jsp,  содержимое  которого  было  приведено  в  листинге  12.10,а.  Теперь, когда объект User присутствует в области видимости сеанса, обрабатывается весь документ проверки и отображается соответствующая информация.

HTML-формы При  создании  практически  любого  Web-приложения  приходится  решать  задачу проверки данных, введенных посредством HTML-формы.  Если форма заполнена некорректно,  приложение  должно  повторно  вывести  форму  и  отобразить  соответствующее сообщение об ошибке.  Вопросы  проверки  содержимого  форм  обсуждались  в главе 3. В данном разделе мы рассмотрим проверку данных формы, предназначенной для создания новой учетной записи, — наиболее сложной формы в этом приложении. Внешний вид формы для создания новой учетной записи был показан на рис. 12.16,  а документ, осуществляющий поддержку формы,  представлен в листинге  12.11,а. Листинг 12.11,a. /WBB-INF/jsp/createAccount/contenfc.jap









422  Глава  12.  Приложение  на  базе  JSP









HTML-формы  423

f  0) error += "
"; error += lastName.getValidationError(); } return error ==  ""; } public String getValidationError()  { return error; } } Размеры  представленного  выше  компонента  bean  достаточно  велики,  однако структура  класса  очень  проста.  Большой  объем  кода  объясняется  размерами  формы. Компонент  bean  сохраняет  данные  формы  и  предоставляет  доступ  к  ним.  Кроме  того, в  данном  компоненте  реализован  метод  v a l i d a t e ,  который  проверяет  имя  и  фамилию,  введенные  посредством  формы.  При  необходимости  этот  метод  может  быть расширен  для  проверки  остальных  полей. Компонент,  представленный  в  листинге  12.11,г,  использует  для  представления  полей,  соответствующих  имени  и  фамилии,  экземпляры  класса  NameElement.  Класс NameElement  был  описан  в  главе  3.  Для  удобства  рассмотрения  код  класса  приведен в листинге 12.11,д. ЛИСТИНГ 12.11,Д.  /WEB-INF/classes/beans/html/NameElement.Java

package  beans.html; public  class  NameElement  extends  TextElement  { String  error,  fieldName; public  NameElement(String  fieldName)  { this.fieldName  =  fieldName; }

public  boolean  validate()  { boolean  valid  =  true; String  value  =  getValuef); error = " " ; if{value.length() == 0) { valid = false;

Повторная активизация чувствительных форм 

427

error = fieldName + " must be filled in"; ) else { for(int i=0; i  '0' &ь с