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
Глава 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-кода
Содержание Преобразование 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. Основы построения пользовательских дескрипторов
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
1
jspversion
?
shortname
1
Версия библиотеки дескрипторов Версия спецификации JSP, используемой при создании библиотеки. По умолчанию принимается спецификация JSP 1,1 Используется средствами авторизации для идентификации библиотеки
u r i
?
URI, однозначно идентифицирующий библиотеку
info
?
Описание, поясняющее порядок использования библиотеки
tag
+
Дескрипторы, содержащиеся в библиотеке
В столбце "Тип" использованы следующие обозначения: 1 — один элемент; ? — элемент может отсутствовать; • — один или более элементов. Для определения пользовательских дескрипторов служит элемент tag. Пример определения дескриптора counter см, в листинге 1.2,6. В нем заданы имя дескриптора, класс поддержки, сведения о том, что тело дескриптора отсутствует, а также информация о дескрипторе. Элементы, содержащиеся в составе tag, перечислены в табл. 1.2.
Таблица 1„2. Элементы в составе tag (перечислены в том порядке, в котором они включаются в данный элемент) Элемент
Тип Описание
name
1
Имя, следующее за префиксом, например
tagclass
1
Класс поддержки, реализующий интерфейс 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'%
F i r s t Name: f
< 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 ( ) ;
) 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
Класс 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
Как видите, код класса занимает всего несколько строк, но разобраться в нем непросто, поскольку непонятно, почему данные выводятся в выходной поток предыдущего дескриптора. Рассмотрим 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 , посредством которого выводится ответ на запрос.
Рис. 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
Код 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-страница до активизации формы, а в правом окне— эта же страница после того, как форма была активизирована.
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 :
Last Name:
76
Глава 3. HTML-формы
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;
Серая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
f
ЕирМплО»! 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 находятся следующие данные:
Листинг 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");
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
РИС. 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,-
Класс 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");
Класс 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;
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 дескриптора доставки. Соответствующий фрагмент дескриптора доставки приведен ниже.
Дескриптор доставки задает также инициализационные параметры, в частности указывае^ПВС-драйвер и 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(] ;
В абстрактном классе 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());
Отличия межд)- кодами, представленными в листингах 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,в.
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
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("");
Дескриптор 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 приведен в листинге
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
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
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 Еиркая
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 верхнего уровня
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,в.
Подобно полосе в левой части 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
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.
} 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
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
Рис. 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);
Дескриптор 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,н.
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; }