Top Banner
Файл взят с сайта http://www.natahaus.ru/ где есть ещё множество интересных и редких книг. Данный файл представлен исключительно в ознакомительных целях. Уважаемый читатель! Если вы скопируете данный файл, Вы должны незамедлительно удалить его сразу после ознакомления с содержанием. Копируя и сохраняя его Вы принимаете на себя всю ответственность, согласно действующему международному законодательству . Все авторские права на данный файл сохраняются за правообладателем. Любое коммерческое и иное использование кроме предварительного ознакомления запрещено. Публикация данного документа не преследует за собой никакой коммерческой выгоды. Но такие документы
435

Java Server Pages

Dec 03, 2014

Download

Documents

romagrd16
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Java Server Pages

• Файл взят с сайта • http://www.natahaus.ru/ • • где есть ещё множество интересных и редких книг. • • Данный файл представлен исключительно в • ознакомительных целях. • • Уважаемый читатель! • Если вы скопируете данный файл, • Вы должны незамедлительно удалить его • сразу после ознакомления с содержанием. • Копируя и сохраняя его Вы принимаете на себя всю • ответственность, согласно действующему • международному законодательству . • Все авторские права на данный файл • сохраняются за правообладателем. • Любое коммерческое и иное использование • кроме предварительного ознакомления запрещено. • • Публикация данного документа не преследует за • собой никакой коммерческой выгоды. Но такие документы

Page 2: Java Server Pages

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

Page 3: Java Server Pages

JAVASERVERPAG

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

Мощные средствааутентификациии интернационализации

Совместное использованиестандартов XML и XSLTи технологии JSP

Применение шаблонов JSP дляразработки приложений на базекомпонентов

Sunmicrosystems

ДЭВИД М. ГЕРИ

Серия Java™ 2 Platform, Enterprise Edition

Page 4: Java Server Pages

ББК 32.973.26-018.2.75Г37

УДК 681.3.07

Издательский дом "Вильяме"

Зав. редакцией С.Н. Тршуб

Перевод с английского и редакция ВВ. Вейтмана

По общим вопросам обращайтесь в Издательский дом "Вильяме"по адресу: [email protected], hitp://www.wilUamspubtishing.com

Герц Дэвид, М.

Г37 Java Server Pages. Библиотека профессионала. : Пер, с англ. - М.: Изда-

тельский дом "Вильяме", 2002. - 448 с.: ил. - Парал. тит. англ.

ISBN 5-8459-0290-8 (рус.)

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

Данная книга написана для разработчиков, имеющих опыт использования языка Java изнакомых с серояетамн и JSP.

Б Б К 32.973.26-018.2.75

Все названия программных продуктов являются зарегистрированными торговыми марками со-ответствующих фирм.

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

Authorized translation from the English language edition published by Prentice Hall, Ptr., Copyright © 2001A31 rights reserved. No part of this book may be reproduced or transmitted in any form or by any

means, electronic or mechanical, including photocopying, recording or by any information storageretrieval system, without permission from the Publisher.

Russian language edition published by Williams Publishing House according to the Agreement withR&I Enterprises International, Copyright О 2002

ISBN &-845ВД290-8 (рус.) © Издательский дом "Вильяме", 2002ISBN01M)3O7O4-l (англ.) © Prentice Hall Inc.,2001

Page 5: Java Server Pages

Содержание

Введение 10

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

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

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

Обработчики тела дескриптора 44Итерации 46Переменные сценария 49Тело дескриптора 54Вложенные дескрипторы 63

Глава 3. НТМЬформы 67

Формы и компоненты bean 67Проверка корректности данных 74Базовый набор классов для работы с формами 82Применение пользовательских дескрипторов 95

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

Инкапсуляция алгоритмов компоновки 100Необязательное содержимое 104Содержимое, зависящее от роли 107Раздельное определение областей 108Вложенные области 110Расширение областей 112Объединение различных подходов к созданию областей 113Реализация дескрипторов поддержки областей 115

Page 6: Java Server Pages

Содержание

i Model 2

7

131

132133134

149

149157162167169

17S

174178

197

197199201202212219

Глава 5. Разработка WEB-приложений

Model IModel 2: архитектура MVCПример использования архитектуры Model 2

Глава 6. Базовый набор классов для создания приложений Model 2

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

Глава 7. Поддержка событий

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

Глава 8.118N

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

Глава 9, Защита 235

Аутентификация 235Базовая аутентификация 239Дайджест-аутентификация 242Аутентификация на основе форм 242Использование SSL и сертификата клиента 246Настройка процедуры аутентификации 246Элементы защиты Х'УеЬ-приложений 251Программная аутентификация 254

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

Создание базы данных 268Источники данных 270Пользовательские дескрипторы для работы с базами данных 271Пул соединений 283Предварительно подготовленные выражения 294Транзакции 300Прокрутка набора результатов 303

Глава 11. XML 309

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

Page 7: Java Server Pages

8 Содержание

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

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

Интерактивный магазин 364Базовый набор классов Model 2 385Интернационализация 403Аутентификация 409НТМЬформы 420Повторная активизация чувствительных форм 427SSL 428XML и DOM 429

Приложение Л. Фильтры сервлетов 435

Пример фильтра 436

Предметный указатель 439

Page 8: Java Server Pages
Page 9: Java Server Pages

ВВЕДЕНИЕ

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

Page 10: Java Server Pages

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

Page 11: Java Server Pages

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

Page 12: Java Server Pages

Введение 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 tbufferLength

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

В табл. 0,2 приведены соглашения о представлении текста.

Page 13: Java Server Pages

14 Введение

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

Шрифт Использование

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

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

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

БлагодарностиНесмотря на то что на обложке указано только мое имя, в создании данной книги

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

Ведущий разработчик 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.

Page 14: Java Server Pages

Введение 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: infogwill iamspublishing. com

WWW: http://www.wi 11 iamspublishing.com

Page 15: Java Server Pages

основыПОСТРОЕНИЯ

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

В этой главе...

Применение пользовательских дескрипторов — JSP-файл.

Определение пользовательских дескрипторов — файлописания.

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

поддержки дескриптора.

Ссылка на описание библиотеки в WEB-INF/web. xml.

Жизненный цикл пользовательского дескриптора.

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

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

Доступ к информации о документе.

Обработка ошибок.

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

- Интерфейс Tag.

- Класс TagSupport: предки, значения и идентификаторы.

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

Page 16: Java Server Pages

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

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

<cd_collection><cd>

<artist>Radiohead</artist><title>OK Computer</title><priee>$14.99</price>

</cd>

</cd_collection>

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

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

<htmlxtitle>Database Example</title><head>

<4@ taglib uri='/WEB-INF/tlds/database,tld' prefix='database'4></head><body>

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

Page 17: Java Server Pages

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

<database:connect database= rF:/databases/sunpress'><database:query>

SELECT * FROM Customers, Orders</database:query>

<table border='2' cellpadding='5'><database: <зо1итпЫашвз columnName='columrwiame'>

<th><%= column_name %></th></database:columnNames>

<database:rows><tr><tr><database:columns columnValue='colurnn_value'>

<td><S= column_value %></td><7 database: columnsx/tr>

</database:rows></table>

</database:connect>

</body></html>

Код, приведенный в листинге 1.1 (он содержит как HTML-дескрипторы, так и поль-зовательские дескрипторы JSP), создает HTML-таблицу. Дескриптор connect устанав-ливает СОСДИНСЕШС с базой даш!ых, а дескриптор query выполняет запрос к базе. Деск-рипторы rows, columns и coluranNames предназначены для перебора результатов.

Как видно из листинга 1.1, пользовательские дескрипторы JSP могут выполнятьдостаточно сложные действия. Кол, содержащийся между открывающим и закры-вающим дескрипторами query, интерпретируется как SQL-запрос, кроме того,connect и query взаимодействуют с другими дескрипторами, присутствующими вданном документе. Дескрипторы columns и columnNames осуществляют перебор ре-зультатов запроса и создают переменные сценария, имена которых задает разработ-чик JSP. В документе, представленном в листинге 1.1, определены имена co l-umn_name и со1шпп_value.

Пользовательские дескрипторы обладают различными характеристиками, кото-рые перечислены ниже.

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

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

• Дескрипторы могут управлять потоком управления, в частности реализовыватьусловные операторы или циклы.

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

• Пользовательские дескрипторы могут взаимодействовать с другими дескрип-торами в составе документа.

Page 18: Java Server Pages

Применение пользовательских дескрипторов — JSP-файл 19

• Пользовательские дескрипторы имеют доступ к информации о документе {по-средством предопределенных переменных r e q u e s t , response, s e s s i o n и т.д.).

• Дескрипторы могут создавать переменные сценария.

Пользовательские дескрипторы JSP соответствуют спецификации XML; по сути,они являются XML-дескрипторами. Это очень важно, поскольку для обработкиJSP-дескрипторов могут применяться инструментальные средства XML и HTML.

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

<prefix:someTag/>

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

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

СоветПользовательские дескрипторы — мощное средство JSPНаличие пользовательских дескрипторов позволяет организовать работу так, что-бы авторы Web-страниц и разработчики программного обеспечения могли решатьсваи задачи практически независимо друг от друга. При этом авторы Web-страницориентируются на использование HTML-, XML- и JSP-дескрипторов, а разработ-чики программ сосредоточивают свое внимание на реализации низкоуровневыхсредств, предназначенных, например, для поддержки форматов или организациидоступа к базам данных. Результаты работы программистов становятся доступны-ми авторам Web-страниц в виде пользовательских дескрипторов.

Применение пользовательскихдескрипторов — JSP-файл

На рис. 1.1 показан JSP-документ, в котором применяется простейший пользова-тельский дескриптор. В нем отсутствуют атрибуты и тело дескриптора. Данный деск-риптор поддерживает счетчик обращений K J S P .

2 Подробнее см. Java Specification Request (JSR) на сервере http://java.sun.com. - Прим. авт.

Page 19: Java Server Pages

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

ii ] A Counter Tag - Mieiuiofl Intend Explow

Die £dll J i t * FartJfltes lool! tltlp

HtpTVIccillToitBCeO/countet/losl.isp J

This page has been accessed 14 times.

Рис, f. t. Пользовательский дескриптор реализуеггсчетчик обращений к JSP

Листинг 1.2,а. /teat, jep

<body>

<%@ t»glib uri='/WEB-INF/tIds/counter.tId' prefiac='util' l>

This page has been accessed <b><util:oounter/X/b> times.

< /body x/ html >

В листинге 1.2,а присутствует директива tag l ib , в которой указывается URI опи-сания библиотеки дескрипторов {TLD— tag library descriptor). Файл, содержащийописание библиотеки дескрипторов, обычно имеет расширение . t l d . В директивеt a g l i b также указывается атрибут prefix, который задает префикс, используемыйпри обращении к дескрипторам данной библиотеки. В листинге 1.2,а атрибут pref ixимеет значение u t i l , поэтому для обращения к дескриптору counter используетсявыражение <ut i l : counter/>.

Описание библиотеки дескрипторов для примера, приведенного в листинге 12,а,содержится в каталоге WEB-INF/tIds. Такое расположение описания рекомендуется,но не является обязательным.

Определение пользовательскихдескрипторов — файл описания

Описание библиотеки дескрипторов представляет собой XML-документ, в кото-ром определены библиотека и принадлежащие ей дескрипторы. Пример TLD-файлаприведен в листинге 1.2,6. Это описание используется в JSP-документе, показанном влистинге 1.2,а.

Page 20: Java Server Pages

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

Листинг 1.2,6. /WEB-INF/ tide /counter. tld

version="1.0" encoding="ISO-8859-l" 2><!DOCTYPE t a g l i b PUBLIC

"-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN""http://Java.sun.com/j2ee/dtds/web-jsptaglibrary_l_l.dtd">

<taglib><tlibversion>l.0</tlibversion><jspversion>l.1</jspversion><shortname>Sun Microsystems Press examples</shortname><info>Example 3.2 from the book</info>

<tag><name>counte r</name><tagclass>tags.CounterTag</tagclass><bodycontent>empty</bodycontent>

</tag></taglib>

Первая строка указывает на то, что содержимое файла является XML-документом.Вторая строка уточняет тип документа как t a g l i b и задает URL DTD (document typedefinition — определение типа документа), в котором описывается структура докумен-та taglib, DTD используется контейнерами сервлетов для проверки корректностидокумента.

Для определения библиотеки дескрипторов служит элемент tag l ib . В листинге1.2,6 задана версия библиотеки 1.0 и указано, что она может использоваться средст-вами поддержки JSP, которые соответствуют JSP 1.1 или более поздней версии спе-цификации.

Кроме приведенных выше сведений, для библиотеки задается имя и краткое описа-ние. Эти данные используются средствами поддержки общей информацш I о документе.

Дескрипторы определяются с помощью элемента tag, в состав которого входятдва обязательных элемента: name и tagclass . Элемент tagc lass задает Java-класс,реализующий функции пользовательского дескриптора. Этот класс называется клас-сом поддержки дескриптора.

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

В составе как tag l ib , так и tag могут содержаться дополнительные данные. Болееподробная информация об этих элементах будет приведена далее в этой главе.

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

Класс поддержки дескриптора реализует интерфейс Tag, принадлежащий пакетуjavax.servlet . j sp . tagext. Дополнительная информация об интерфейсе Tag бу-дет приведена далее в данной главе. В этом интерфейсе объявлены шесть методов;три из них, которые используются наиболее часто, представлены ниже.

Page 21: Java Server Pages

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

int doStartTaq() throws JspExceptionint doEndTag() throws JspExceptionvoid release()

Контейнер сервлетов вызывает методы интерфейса Tag в том порядке, в которомони перечислены здесь. Методы doStartTag и doEndTag вызываются соответствен-но при встрече открывающего и закрывающего дескрипторов. Каждый из методоввозвращает целочисленное значение; как правило, для этого используются констан-ты, определенные в интерфейсе Tag. Возвращаемое значение задает поведение кон-тейнера сервлетов после завершения метода.

После вызова метода doEndTag контейнер вызывает метод release. Этот методдолжен освободить ресурсы, которые использовались классом поддержки дескриптора.

В листинге 1.2,в показан пример реализации методов, объявленных в интерфей-се Tag.

Листинг 1.2,в. /WEB-INF/classes/tags/COTUiterTag. Java

package tags;

import java.io.File;import Java.io.FileReader;import java-io.FileWriter;import java.io.IOException;

import javax.servlet.jsp.JspException;import javax.servlet.jsp.tagext.TagSupport;import javax.servlet.http.HttpServletRequest;

public class CounterTag extends TagSupport [private int count = 0;private File file = null;

public int doStartTag() throws JspException {try (

checkFilef);readCount ();pageContext.getOut().print(++count);

)catch(java.io.lOException ex) f

throw new JspExceptionfex.getMessage [)) ;

return 3KIP_BODY;)public int doEndTagU throws JspException {

saveCount();return EVAWAGE;

}

private void checkFileO throws JspException, lOException {i f( f i le — null) {

file = new File(getCounterFilename());count = 0;

}

i f ( ' f i l e . e x i s t s ( > ) {file.createNewFile();saveCount О;

Page 22: Java Server Pages

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

private String getCounterFilename() {HttpServletRequest req •= (HttpServletRequest)pageContext.

getRequest(};String servletPath = req.getServletPath ();String realPath = pageContext.getServletContext[}.

getRealPath(servletPath);

return realPath + ".counter";)private void saveCount0 throws JspException (

try {FileWriter writer = new FileWriter ( f i le);writer.write (count);writer.close();

1catch(Exception ex) {

throw new JspException(ex.getMessage());

private void readCount() throws JspException {try {

FileReader reader = new FileReader(file);count = reader.read();reader.close();

}

catch(Exception ex) ithrow new JspException[ex.getMessage());

Показанный в листинге класс поддержки дескриптора подсчитывает число обращенийк дескриптору, а следовательно, и к содержащему его JSP-документу. Информация о числеобращений хранится в файле. Имя этого файла совпадает с именем соответствующего JSP-документа, кроме того, к нему добавляется суффикс . counter . Например, если в файле/index, j sp содержится пользовательский дескриптор, реализующий счетчик обраще-ний, информация о числе обращений хранится в файле / index, j s p . counter .

Подобно многим классам поддержки дескрипторов, класс t a g s . CounterTag реа-лизован как подкласс TagSupport. Класс TagSupport реализует интерфейс Tag,кроме того, он содержит ряд вспомогательных методов. Подробно класс TagSupportбудет обсуждаться далее в этой главе.

Метод CounterTag. doStar tTag выводит число обращений, используя для доступак объекту out переменную pageContext, определенную в классе TagSupport как p r o -tected . По окончании выполнения метод возвращает значение SKIP_BODY, указываю-щее на то, что тело дескриптора, если оно существует, должно игнорироваться. Послеэтого контейнер вызывает метод CounterTag.doEndTag, который возвращает значе-ние EVAL_PAGE. Данная константа означает, что контейнер сервлетов должен обраба-тывать остаток документа, который следует за закрывающим дескриптором.

Переменные экземпляра count и f i l e инициализируются при каждом вызове ме-тода doStar tTag, поэтому метод r e l e a s e в данном классе не переопределяется.

Page 23: Java Server Pages

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

Ссылка на описание библиотекиBWEB-INF/web.xml

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

// В JSP-файле . . .

<%@ tagl ib uri='counters ' p r e f i x = ' u t i l ' %>

Элемент tag l ib дескриптора доставки Web-приложения определяет URI countersи задает расположение TLD-файла.

// в файле web.xml . . .

<taglib><taglib-uri>counter3<ytaglib-uri><taglib-location>/WEB-lNF/tldg/counter,tld</taglib-location>

</taglib>

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

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

• Включить BjSP-файл, в котором используется дескриптор, директиву tag l ib .• Создать описание библиотеки дескрипторов (TDL-файл).• Реализовать класс поддержки дескриптора как подкласс класса TagSupport и

переопределить в нем методы doStartTag и doEndTag.

Элементы <taglib> и <tag>В данном разделе подробно описываются особенности применения элементов

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

Page 24: Java Server Pages

Элементы <taglib> и <tag> 25

Таблица 1.1. Элементы в составе tagl ib {перечислены в том порядке,в котором они включаются в данный элемент)

Элемент Тип Описание

t l i b v e r s i o n 1 Версия библиотеки дескрипторов

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

shortname 1 Используется средствами авторизации для идентификации

библиотеки

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

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

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

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

1 — один элемент;

? — элемент может отсутствовать;

• — один или более элементов.

Для определения пользовательских дескрипторов служит элемент tag. Пример оп-ределения дескриптора counter см, в листинге 1.2,6. В нем заданы имя дескриптора,класс поддержки, сведения о том, что тело дескриптора отсутствует, а также информа-ция о дескрипторе. Элементы, содержащиеся в составе tag, перечислены в табл. 1.2.

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

Элемент Тип Описание

name 1 Имя, следующее за префиксом, например <prefix: name ...>

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

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

bodycontent ? Описание тела дескриптора:

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

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

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

info ? Информация о дескрипторе

attribute * Атрибут дескриптора

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

1 — один элемент;

Page 25: Java Server Pages

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

? — элемент может отсутствовать;

* — нуль или более элементов.

Атрибуты определяются с помощью элемента a t t r i b u t e , который будет рас-смотрен далее в этой главе.

Элемент bodycontent указывает, какие действия должен выполнять контейнерсервлетов с телом дескриптора. По умолчанию предполагается значение JSP, в соот-ветствии с которым тело дескриптора должно обрабатываться контейнером. Еслиэлемент bodycontent содержит значение tagdependent, контейнер не обрабатыва-ет тело дескриптора; такую обработку выполняет класс поддержки. Значение emptyуказывает на то, что тело пользовательского дескриптора должно отсутствовать.

Жизненный цикл пользовательскогодескриптора

Класс поддержки пользовательского дескриптора представляет собой программ-ный компонент, включаемый в контейнер сервлетов. Контейнер создает экземпляркласса поддержки, инициализирует его и вызывает методы doStartTag, doEndTag иrelease. Для простых дескрипторов инициализация сводится к вызову методовsetPageContext и setParent. Поскольку контейнер вызывает метод re lease клас-са поддержки, этот класс может быть подготовлен к последующему использованию.

На рис. 1.2 показана диаграмма взаимодействия класса поддержки дескриптора,рассматриваемого в данной главе в качестве примера, с контейнером сервлетов.

J^P gpmamer

SKIP вот

(JoEncfTagO

PWWTTH

EVAL_PAGE

re lease £

Pflrj^Contert

gelujlQ

prin!0

1

.[spWrjler

3J

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

Page 26: Java Server Pages

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

Поскольку контейнер сервлетов может повторно обращаться к классу поддержкидескриптора, необходимо уделять внимание методу re lease и инициализации пере-менных в методе doStartTag. Например, класс поддержки, приведенный ниже, ра-ботает корректно.

public class TagHandler extends TagSupport (private Hashtable hashtable;

public irit doStartTag (} throws JspException {hashtable = new Hashtable 0;

Jpublic void released {

hashtable = null;

Page 27: Java Server Pages

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

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

public class TagHandler extends TagSupport (private Hashtable hashtable;

public TagHandler() {hashtable - new Hashtable();

public void release() {hashtable - nul 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^<fc= aCollection %>'>

Здесь атрибуту c o l l e c t i o n присваивается значение переменной.Для того чтобы пользовательский дескриптор поддерживал некоторый атрибут,

надо выполнить три дополнительных действия.

1. Добавитьтребуемый атр ибут к дес кр и пто ру в JSP-файле.

2. Добавить элемент, соответствующий этому атрибуту, к TLD.

3. Реализовать метод setAttra классе поддержки дескриптора. Здесь Лиг— имя ат-рибута, соответствующее соглашениям JavaBeans.

Обычно в классе поддержки реализуют также метод qetAttr, таким образом, вло-женные дескрипторы получают доступ ксвойствам.

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

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

Page 28: Java Server Pages

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

Please Register

Ftrat N»me: |raph

La«l Name:

E-mail Address:]

±1

Please Register

First Name:

Last Mams:

E-mail Address:

Please fill in all of the fields above

ч - - •

;.•' Refill atoiP*oj-Mb«o*l*!HMtEi(*i«i

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

Первоначально появляется соблазн решить эту задачу следующим образом:

<input t y p e = ' t e x t ' s ize=15 name='firstName 1

v a l u e = ' < % = request ,getParameter(" f i rs tName")%>•>

Атрибут v a l u e HTML-дескриптора inpflit устанавливается равным значению па-раметра в составе запроса, который соответствует данному полю редактирования.Теперь, если документ, показанный на рис. 1.3, будет отображен повторно, содержи-мое полей редактирования сохранится.

Однако описанное решение имеет суще-ственный недостаток. Если параметр за-проса, соответствующий полю редактиро-вания, отсутствует (такая ситуация возни-кает при первом обращении к форме),метод S e r v i e t R e q u e s t . g e t P a r a m e t e rвернет значение n u l l , которое и отобра-зится в поле.

Разрешить эту проблему можно, реализо-вав пользовательский дескриптор, которыйвозвращал бы параметр запроса, а при егоотсутствии — пустую строку. Фрагмент JSP-документа, в котором применяется такой де-скриптор, показан в листинге 1.3,а. Пользо-вательский дескриптор r e q u e s t P a r a m e t e rсодержит один обязательный атрибут, опре-деляющий имя параметра запроса.

Please Register

First Name: |null

Last Мэтпв: (null

E-mail Address, null

Are. 1,4. Недостаток непосредственного вызоваr e q u e s t . g e t P a r a m e t e r

Page 29: Java Server Pages

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

Листинг 1.3,a. / reg i s ter . jэр

tagl ib uri^'WEB-INF/tlds/htmL.tld' prefix='html'%

<table><tr>

<td> First Name: </td><tdxinput type=f t ex t ' size=15 narae=' firstMame'

value='<html:requestParameter property="</td>

</tr><tr>

<td> Last Name: </td><td><input type='text ' slze=l5 name='lastName'

value='<html:requestParameter property="lastName"/>'></td>

</tr><tr>

<td> E-mail Address: </td><tdxinput type='text ' si2e=25 name=' emailAddress'

value='<html:requestParameterpropertY="emaiLAddresa"/>'>

</td></tr>

</table>

Атрибуты объявляются в описании библиотеки дескрипторов. Фрагмент TLD-файла для библиотеки, содержащей дескриптор requestParameter, показан в лис-тинге 1.3,6.

Листинг 1.3,6. /WES-IMF/ t ide /html. t l d

<taglib>

<tag><name>requestPar ameter</name><tagclass>tags.GetRequestParameterTag</tagclass><bodycontent>empty</bodycontent>

<attribute>

<name>property</name><required>t rue</requi red><rtexprvalue>true</rtexprvalue>

</attribute></tag>

</taglib>

Атрибут property является обязательным, и его значение может быть определено в

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

перечислены в табл. 1.3 (данные элементы соответствуют спецификации JSP 1.1).

Page 30: Java Server Pages

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

Таблица 1.3. Элементы в составе attribute

Элемент Тип Описание

name ] Имя атрибута

required ? Если значение данного элемента равно t r u e , атрибут дол-жен присутствовать

rtexprvalue ? Если значение данного элемента равно t r u e , атрибут мо-жет определяться с помощью JSP-выражения

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

1 — один элемент;

2 — элемент может отсутствовать.

Класс поддержки для дескриптора requestParameter приведен в листинге 1.3, в.

ЛИСТИНГ 1.3,В. /WEB-INF/clasees/tags/GetRequestParameterTag.Java

package tags;

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

public class GetRequestParameterTag extends TagSupport {private String property;

public void setProperty(String property) {this.property = property;

)public int doStartTag(} throws JspException {

ServletRequest req = pageContext.getRequest();String value = req.getParameter(property);

try |pageContext.getOut().print(value == null ? "" : value);

)catch (Java.io.IOException ex) {

throw new JspException(ex.getMessage());}return SKIP_BODY;

Для каждого из атрибутов в классе поддержки должен быть определен set-метод всоответствии с соглашениями JavaBeans. При вызове set-методу передается значениеатрибута, которое обычно присваивается переменной, объявленной в классе под-держки. Вызовы set-методов осуществляются перед первым обращением к методуdoStartTag.

Page 31: Java Server Pages

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

В классе GetRe quest Parameter! ag определен set-метод для атрибута property.Значение атрибута сохраняется в переменной, объявленной как pr iva te , и исполь-зуется при выполнении метода doStartTag.

Диаграмма, представленная на рис. 1.5, описывает действия, которые выполняют-ся при обработке дескриптора requestParameter.

-lap Cz-mnm G ol Re qij 5 si P а гв mei e rTag PapsContort JflpWrrttr

MlPageConiextQ

celPirentQ U

s el Props riyO т

doSlartTagO V

doEncftagO

EVAL PAGE

retoaeefl

КЮИЗ

^ prlfllO

J

!!

Рис. /.5. Диаграмма взаимодействия для GetRequestParameterTag

СоветАтрибуты дескриптора и свойства класса поддержкиДля каждого атрибута пользовательского дескриптора в классе поддержки должносуществовать соответствующее JavaBeans-свойство. Кроме того, в классе поддерж-ки необходимо определить для каждого свойства set-метод; эти методы вызывают-ся перед обращением к doStartTag. Такая последовательность вызовов гаранти-рует, что в теле метода cloStar tTag будут доступны значения всех атрибутов.

Доступ к информации о документеПользовательским дескрипторам часто бывает нужна информация о содержащем

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

Page 32: Java Server Pages

Доступ к информации о документе 33

Класс PageContext предоставляет набор вспомогательных методов, которые ус-ловно подразделяются на следующие категории,

• Доступ к атрибутам в области видимости документа.

• Доступ к атрибутам в указанной области видимости.

• Перенаправление и включение.

• Методы контейнера сервлетов.

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

public class SomeTag extends TagSupport {public int doStartTag[) throws JspException [

User user • (User) pageConteJft.getAttx-ibuteC'user") ;

}1

Пользовательский дескриптор может также обращаться к объекту из заданной об-ласти видимости.

User user = (UserJpageContext.getAttribute("user",PageContext.SESSION_SCOPE);

При вызове метода PageContext.getAttribute задается имя и область видимо-сти, а по завершении работы метода возвращается ссылка на объект Ob j ect. Областьвидимости задается константами PAGE_SCOPE, REQUEST_SCOPE, SESSION_SCOPE иAPPLICATION_SCOPE, определенными в классе PageContext.

Переменная pageContext также используется для получения информации о за-просе, например:

public class SomeTag extends TagSupport (public int doStartTag () throws JspException {

ServletRequest request «• pageConteact.getRequest() ;Locale locale • request.getLocale();

В табл. 1.4 перечислены методы класса PageContext, которые применяются присоздании пользовательских дескрипторов.

Таблица 1.4. Методы класса PageContext

Метод Описание

Object f indAttr ibute Выполняет поиск атрибута в области видимости доку-(String) мента, запроса, сеанса и приложения

Object getAtt r ibute Возвращает объект из области видимости документа;(String) если объект отсутствует, возвращается значение nul l

Page 33: Java Server Pages

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

Окончание табл. 1.4

Метод Описание

void setAttribute(String, Object)

void removeAttribute(String)

JspWriter getOut()

ServletRequestgetRequest()

ServletResponsegetResponse()

ServletContextgetServletContext()

HttpSessiongetSession()

void forward(String path)

void include(String path)

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

Удаляет объект из области видимости документа

Возвращает объект JspWriter, который используетсядескриптором для вывода данных

Возвращает объект ServletRequest, инкапсулирую-щий данные о запросе

Возвращает объект ServletResponse, инкапсулирую-щий данные об ответе

Возвращает объект приложения

Возвращает объект сеанса

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

Включает HTML- или JSP-файл; для указания файлаиспользуется относительный путь

Методы se tAt t r ibute getAttr ibute и removeAttribute переопределены так,что мри вызове им передается дополнительное целочисленное значение, которое оп-ределяет область видимости. Благодаря этому появляется возможность хранить атри-буты в произвольной области. Если при вызове метода область не указана, предпола-гается область видимости документа.

Класс PageContext также предоставляет доступ к предопределенным перемен-ным документа, содержащего дескриптор, например к переменным out, sess ion ит.д. Метод PageContext .getOut используется очень часто, особенно если дескрип-тор предназначен для фильтрации или редактирования содержащихся в нем данных.

Из пользовательского дескриптора можно также выполнять перенаправление иливключение Web-компонентов, например сервлетов или JSP. При вызове методам for-ward и include класса PageContext передается строка, представляющая относи-тельный путь к ресурсу.

Согласно спецификации JSP 1,1, нельзя включать ресурсы из обработчика тела де-скриптора. Это ограничение связано с буферизацией, используемой при работе серв-лета, в который преобразуется JSP. Вспецификации^Р 1.2 этот недостаток устранен.

Page 34: Java Server Pages

Обработка ошибок 35

Обработка ошибокКлассы поддержки дескрипторов должны реагировать на исключительные ситуации,

например генерировать исключение, если задано недопустимое значение атрибута.В процессе работы класс поддержки может генерировать исключение J s p -

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

<%® t a g l i b u r i = ' u t i l . t l d ' p r e f i x = ' u t i l ' % ><%Э page e r r o r P a g e = ' e r r o r . j s p ' %>

Класс поддержки дескриптора для JSP, содержащего приведенный выше код, мо-жет выглядеть приблизительно так:

import j a v a x . s e r v l e t . j s p . J s p E x c e p t i o n ;import j a v a x . s e r v l e t . j s p . t a g e x t . T a g S u p p o r t ;

public c l a s s SomeTagHandler extends TagSupport (p r i v a t e boolean someCondition;

publ ic i n t doStartTag() throws JspException {

if (someCondition = fa l se)throw new JspEiccept ionr ' in format ive message") ;

})•

Документ e r r o r . j sp, указанный посредством директивы page, вызывается в томслучае, если при выполнении метода SomeTagHandler .doStartTag генерируетсяисключение.

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

В пакете j avax . s e r v l e t . j s p . t a g e x t содержится набор классов для поддержкиинформации о библиотеках дескрипторов, а также классы и интерфейсы для реали-зации пользовательских дескрипторов. На диаграмме, представленной на рис. 1.6,показаны классы и интерфейсы, часто применяемые разработчиками.

Класс поддержки дескриптора должен рсализовывать интерфейс Tag. Разработчикипочти никогда не определяют вручную методы, объявленные в интерфейсе Tag, а соз-дают класс поддержки как подкласс класса TagSupport либо класса BodyTagSupport.

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

Page 35: Java Server Pages

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

«Interim»

•parent

PageCcnteM

•1 PAGE_SCOPE•t REQUEST SCOPE*1 SESSION SCOPE*i APPLICATION SCOPE

ге\ваз»0*se!AHniiiitB(!•selAtlribuleO•fletAtlribllleO•gelAmibuleg•findAilrifautoQ•re moveAH ributeQ•remmeAtlr ibuteu•gel All л t lit esSc op eO•geiAJ I ;i Ь u Г в Nwn« s In S с op в О• O O

94oiwardO•included*h ч пЧ-.иРя j»E «с a pi I onQ

(fta rft

Tag

4% SKIP BODV*s EVACBODYJNCUJDE*i SKIP PAGE#1 EVAL PAGE

yg•doSt»nTag()•doEnoTagO•seiBodyCortlsnlO

BafyCcmti* e

Рис. Т.6. Диаграмма классов, принадлежащих пакету tagext

Доступ к информации о документе, содержащем дескриптор, осуществляется по-средством контекста документа, который связывается с каждым дескриптором. Каквидно из рис. 1.6, класс PageContext содержит методы, предоставляющие различ-ные сведения о документе.

Интерфейс TagИнтерфейс Tag описывает основные возможности дескрипторов; ниже перечис-

лены методы, объявленные в этом интерфейсе.

void setPaqeContext(PageContext)void setParent(Tag}int doStartTag() throws JspExceptionint doEndTag() throws JspExceptionvoid release ()

Tag qetParent()

Page 36: Java Server Pages

Классы для реализации пользовательских дескрипторов 37

Методы из первой группы приведены в той последовательности, в которой онивызываются при обработке дескриптора. При появлении открывающего дескрипторасначала производится обращение к методу setPageContext, а затем к методуsetParent. После этого вызывается метод doStartTag. При встрече закрывающегодескриптора вызывается метод doEndTag. В случае пустого дескриптора doEndTagвызывается сразу же после doStartTag. После завершения метода doEndTag произ-водится обращение к методу r e l e a s e , который освобождает ресурсы, использован-ные классом поддержки.

Как doStartTag, так и doEndTag возвращают целочисленные значения, опреде-ляющие поведение контейнера сервлетов. Возвращаемые значения и действия, кото-рые они вызывают, описаны в табл. 1.5.

Таблица 1.5. Значения, Возвращаемые методами doStartTag И doEndTag

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

doStartTag () SKI P_BODY; тело дескриптора не обрабатывается

EVAL_BODY_INCLUDE; тело дескриптора передается без изменений

doEndTag () SKIP_PAGE: часть документа, следующая за закрывающим деск-риптором, не обрабатывается

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

Для каждого дескриптора определен родительский дескриптор (nul l для деск-риптора верхнего уровня) и старший из вложенных дескрипторов. Например, в сле-дующем JSP-фрагменте дескриптор inbetween является родительским для дескрип-тора innermost, а дескриптор outermost выступает в качестве родительского поотношению к дескриптору inbetween.

^example:outermost><example:inbetween>

<example:innermost>

</example:innermost><example:inbetween>

</example:outermost^

Все дескрипторы, в состав которых входят другие дескрипторы, считаются пред-ками включенных дескрипторов. В предыдущем примере дескрипторы inbetween иoutermost являются предками дескриптора innermost.

Интерфейс Tag предоставляет доступ к родительскому дескриптору, но set-методы, определяющие родительский дескриптор и контекст документа, предназна-чены для контейнера сервлетов, и при создании пользовательских дескрипторовприменять их не следует.

На практике разработчики редко реализуют интерфейс Tag непосредственно.Вместо этого они создают класс поддержки дескриптора как подкласс классаTagSupport либо класса BodyTagSupport.

Page 37: Java Server Pages

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

Класс TagSupport: предки, значенияи идентификаторы

Если тело дескриптора не обрабатывается и если дескриптор не влияет на поток вы-полнения, его класс поддержки обычно создается как подкласс класса TagSupport.Класс TagSupport реализует интерфейс Tag и предоставляет следующие возможности:

• определение предков дескриптора;

• доступ к идентификатору дескриптора;

• сохранение и извлечение именованных значений.

По умолчанию подклассы TagSupport игнорируют тело дескриптора и обрабаты-вают JSP-код, следующий за закрывающим дескриптором. Для того чтобы контейнерсервлетов демонстрировал именно такое поведение, надо, чтобы метод doStartTagвозвращал значение SKIP_BODY, а метод doEndTag — значение EVAL_PAGE.

Методы класса TagSupport перечислены ниже.

// Класс TagSupport реализует интерфейс Tag// и предоставляет следующие методы:protected String id;protected PageContext paqeContext;

s t a t i c Tag findAncestorWithClass(Tag, Class)

Object qetValue[String key)void setValue(String key, Object value}void removeValue(String key)Enumeration qetValues ()

String qetldQvoid se t ldQ

Подклассам класса TagSupport доступны две переменные: одна из них содержитидентификатор дескриптора, а вторая — контекст документа. Эти переменные объяв-лены как protected.

Метод findAncestorWithClass определяет расположение предка дескриптора.Этот метод, объявленный как s t a t i c , получает дескриптор для начала поиска и класспредка. Пример использования метода findAncestorWithClass приведен в главе 2.

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

СоветБольшинство пользовательских дескрипторов реализуетсяс помощью классов TagSupport и BodyTagSupportКлассы TagSupport и BodyTagSupport предоставляют основные средства для соз-дания пользовательских дескрипторов. Вместо непосредственной реализации ин-терфейса Tag разработчики создают классы поддержки как подклассы TagSupportи BodyTagSupport.

Page 38: Java Server Pages

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

Поскольку Java не допускает множественного наследования, модифицировать су-ществующий класс так, чтобы он наследовал возможности TagSupport илиBodyTagSupport, невозможно. Единственным выходом в таком случае являетсянепосредственная реализация интерфейсов Tag и BodyTag, однако подобные си-туации возникают достаточно редко.

Тело дескриптораМежду открывающим и закрывающим дескрипторами могут присутствовать раалич-

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

Если класс поддержки не реализует интерфейс BodyTag, тело дескриптора долж-но либо игнорироваться, либо передаваться в неизменном виде. JSP-документ, пока-занный в правом окне на рис. 1.7. содержит простой пользовательский дескриптор,который отображает содержащиеся в нем данные, если процедура регистрации окон-чилась успешно и если пользовательская роль определена как ' u s e r ' . В противномслучае тело дескриптора игнорируется.

i^LognPae. МетиЛ«тмЕ**х«(

рк Edit !<•» Fgn>lt« Tool!

Please LoginD

Name: | Sieve

Password:

2] D« !*; Uc4lHrnt

I в •• ed>> хм* i ^ w i * " s»i>

-Ю1Ч

Welcome Steve

Vou ate a oset

Рис. ».7, Дескриптор, предназначенный для аутентификации пользователей

В левом окне на рис. 1.7 представлена Web-страница, предназначенная для реги-страции. Если процедура регистрации оканчивается успешно, происходит перена-правление к другому документу (контейнер сервлетов вызывает welcome, j sp ; под-робнее о регистрации и средствах защиты будет сказано в главе 9). Документ, код ко-торого показан в листинге 1.4,а, использует дескриптор a u t h e n t i c a t e . Если пользо-вательская роль имеет значение 'user ' , дескриптор передает содержащиеся в немданные без изменений.

Page 39: Java Server Pages

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

Листинг 1.4,8. /welcome, jsp

<html><head><title>Welcome</title></head><body>

<%@ taglib uri='/WEB-INF/tlds/authenticate.tld'prefix='security'%>

<h3>Welcome <%= request.getUserPrincipal () %></h3>

<security:authenticate role='user'>You are a user

</security:authenticate>

</body></html>

Класс поддержки дескриптора a u t h e n t i c a t e показан в листинге 1.4,6.

ЛИСТИНГ 1.4,6. /WEB -INF/ classes/ tags/ Authenticate Tag . Java

package tags;

import javax.servlet.http.KttpServletRequest;import javax.servlet.jsp.JspException;import javax.servlet.jsp.tagext.TagSupport;

public class AuthenticateTag extends TagSupport {private String role • null;

public void setRole(String role) 1this.role = role;

}public int doStartTag() throws JspException {

HttpServletRequest request •= (HttpServletRequest)pageContext.getRequest ();

if(request.isUserlnRole(role)) {return EVAL_BODY_IMCLUDE;

)return SKIP_BODY;

)public int doEndTagO throws JspException {

return EVAL_PAGE;t

Класс AuthenticateTag является подклассом класса TagSupport и поддерживаетединственное свойство, соответствующее атрибуту role дескриптора authent icate .

Если пользовательская роль имеет значение 'user ' , метод doStartTag возвраща-ет EVAL_BODY_INCLUDE и тело дескриптора включается в отображаемый документ.В противном случае метод возвращает SKIP_BODY, и тело дескриптора игнорируется.

Page 40: Java Server Pages

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

СоветВключение тела дескриптораЕсли пользовательский дескриптор не реализует интерфейс BodyTag, он можетлишь передать содержащиеся в нем данные без изменений. Для этого методdoS t a r t Tag должен возвращать значение EVAL_BODY_INCLUDE. Если тело дескрип-тора предназначено для обработки, необходимо, чтобы класс поддержки реализо-вывал интерфейс BodyTag. Подробнее этот вопрос будет рассмотрен в главе 2.

РезюмеПо мере развития JSP становится очевидно, что наличие пользовательских деск-

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

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

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

Page 41: Java Server Pages

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

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

В этой главе...

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

• Итерации.

• Переменные сценария.

- Хранение компонентов bean в области видимости документа.

- Информация о переменных сценария.- Связывание класса поддержки с переменными сценария.- Идентификаторы пользовательских дескрипторов.

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

- Особенности обработки тела дескриптора.

- Генерация JavaScript-кода.

• Вложенные дескрипторы.

- Обнаружение предков дескрипторов.- Разделение данных.

Page 42: Java Server Pages

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

<html:links name='api'><option value='Servlets ' >servlets</option>

</html:links>

HTML-код, сгенерированный в результате выполнения дескриптора, имеет сле-

дующий вид:

<select name='api' onChange^' this . form,submit() '><option value=' Servlets '>servlets</option>

</select>

Кроме обработки тела дескриптора, многие пользовательские дескрипторы обес-печивают также доступ JSP-документов к компонентам bean и переменным сценария.Для создания переменных сценария приходится затрачивать дополнительные уси-лия, однако они окупаются тем, что JSP-документы становятся значительно проще.Ниже представлен дескриптор i t e r a t e , который включает текущий пункт в набор,доступный как bean.

<smp:iterate collection^'<%= vector %>'><jsp:useBean id='item' scope='page' c lass='Java, lang.Str ing '/>

Item: <%= item %><br></smp:iterate>

Сравните его с переменной сценария:

<smp:iterate collection^<%= vector %>'>Item: <%= item l x b r >

</smp:iterate>

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

Page 43: Java Server Pages

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

<util:loop from='l' to='5'>

<util:loop>

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

Методы doInitBody и doAfterBody имеют доступ к телу дескриптора, но методdoInitBody вызывается до первой обработки содержимого.

Page 44: Java Server Pages

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

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

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

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

doStartTag () EVAL_BODY_TAG: обработка тела дескриптора и сохранениерезультата в объекте BodyContent

SKI P_BODY: обработка тела дескриптора не производится

doAf terTag () 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 добавлены два приведенных выше метода. Метод getBodyContentвозвращает тело дескриптора, а метод g e t P r e v i o u s O u t — выходной поток, связан-ный с родительским дескриптором. В случае дескриптора верхнего уровня методgetPreviousOut возвращает значение предопределенной переменной out . Под-робно эти два метода будут рассмотрены далее в этой главе.

По умолчанию подклассы класса BodyTagSupport единожды обрабатывают телодескриптора. Значения, возвращаемые по умолчанию методами данного класса, при-ведены в табл. 2.2.

Page 45: Java Server Pages

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

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

t Sk &» lie» favorite look

tiers!ing over [one, two, three. Tour] ...

Item oneHem: IwoHem: throeItem, four

~*y L o ^ rtimdJ

JSP-код этого документа приведен в листинге 2.1 ,а.

Листинг 2.1,a. /test.jsp

<html><headxtitle>An Iterator</title></head><%@ taqlib uri-

f/WEB-INF/tlds/iterator.tld' prefix-'it' l>

<body>

<% Java.util.Vector vector = new Java.util.Vector ();vector.addElement("one"); vector.addElement("two");vector.addElement("three"); vector.addElement("four");

Iterating over <%= vector *> ...<p>

<it:iterate collection^'<%= vector %>'><jsp:useBean id='item' scope='page' class='Java.lang.String'/>Item: <l= item %><br>

</it:iterate>

</body></btml>

Page 46: Java Server Pages

Итерации 47

В представленном выше листинге вектор объектов Str ing задается как значениеатрибута col lect ion пользовательского дескриптора i t e r a t e . Дескрипторiterate перебирает в цикле строковые значения и сохраняет текущую строку в об-ласти видимости документа. В теле дескриптора для получения строки используетсядействие < j sp: useBean>, затем строка отображается с помощью JSP-выражения.

В листинге 2,1,6 представлен класс поддержки дескриптора i t e r a t e .

Листинг 2.1,6. /WEB-INF/classes/ tags/IteratorTag . Java

package tags;

import java.util.Collection;import java.util.Iterator;

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

public class IteratorTag extends BodyTagSupport {private Collection collection;private Iterator iterator;

public void setCollection(Collection collection) {this.collection = collection;

)

public int doStartTag {) throws JspException {

return collection.size[) > D ? EVAL_BODY_TAG : SKIP_BODY;

}

public void dalnitBodyO throws JspException (i te ra tor = c o l l e c t i o n . i t e r a t o r (};p a g e C o n t e x t . s e t A t t r i b u t e < " i t e m " , i t e r a t o r . n e x t ( ) ) ;

)public int doAfterBody() throws JspException {if (iterator.hasNext()> {

pageContejct.setAttribute("item", iterator.next());return EVAL_BODY_TAG;

}else (

try (getBodyContent0.writeOut(getPreviousOut<));

}

catch[java.io.lOException e) {throw new JspException[e.getMessage());

}return SKIP_BODY;

В листинге 2.1,6 методы класса IteratorTag определены в том порядке, в кото-ром они вызываются контейнером сервлетов. Сначала контейнер вызывает методsetCollection и передает ему значение атрибута c o l l e c t i o n дескриптора.

Затем контейнер вызывает метод doStartTag, который возвращает значениеEVAL_BODY_TAG, Это значение возвращается, только если в составе вектора присут-

Page 47: Java Server Pages

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

ствуют элементы, в противном случае возвращается значение SKIP_BODY и контей-нер сервлетов не вызывает методы doInitBody и doAf terBody.

Если вектор содержит элементы, контейнер сервлетов вызывает метод doInitBody,в котором определяется итератор набора (вектора), извлекается первый элемент век-тора и сохраняется в области видимости документа под именем "item".

После инициализации тела дескриптора и обработки содержимого вызываетсяметод doAf terBody. Если не все элементы вектора исчерпаны, метод doAf terBodyизвлекает очередной элемент и сохраняет его в области видимости документа. Затемметод doAfterBody возвращает значение EVftL_BODY_TAG, вызывая тем самым по-вторную обработку содержимого дескриптора. Если элементов в составе векторабольше не осталось, возвращается значение SKIP_BQDY и цикл завершается.

Дескриптор i t e r a t e не реализует метод release, поскольку в составе i t e r a t eнет данных, которые должны инициализироваться перед повторным обращением кдескриптору.

На первый взгляд может показаться странным, что дескриптор i t e r a t e записы-вает данные в поток, который принято называть выходным потоком предыдущего деск-риптора (previous out). Кроме того, может возникнуть вопрос, почему содержимое за-писывается в поток в теле метода doAfterBody, а не в методе doEndTag. Ответы наэти вопросы вы найдете далее в данной главе.

На рис. 2.2 показана диаграмма взаимодействия класса I teratorTag, код которо-го представлен в листинге 2.1 ,б. Класс поддержки сохраняет каждый элемент векторав области видимости документа.

pageContext.setAttribut©("it«m", i t e r a t o r . n e x t ( } ) ;

Сохраненные элементы затем извлекаются посредством действия < j sp: useBean>.

<jsp:useBean id='it»m' scope»'page' c lass» 'Java. lang.Str ing '>

Заметьте, что ключевое значение, задаваемое при вызове метода se tAt t r ibute("item"), должно соответствовать значению атрибута id действия <jsp:useBean>.Области видимости также должны совпадать.

Избежать этой зависимости можно, обеспечив доступ к элементам посредствомпеременных сценария. В этом случае исчезает необходимость в действии<jsp:useBean>. Реализация класса IteratorTag с использованием переменныхсценария обсуждается в следующем разделе.

СоветОбработчики содержимого не обеспечивают автоматическоевключение тела дескриптораЕсли класс поддержки дескриптора не реализует интерфейс BodyTag, метод IdoStartTag этого класса может возвращать значение EVAL_BQDY_INCLUDE; в ре- |зультате тело дескриптора передается для обработки без изменений.Однако если класс поддержки реализует интерфейс BodyTag, это означает, что Нконтейнер, а именно класс поддержки отвечает за обработку содержимого. В этоьслучае doStartTag не может возвращать EVAL_BODY_INCLUDE. Тело дескрипторадолжно непосредственно обрабатываться, как это показано в листинге 2,1,6.

Page 48: Java Server Pages

JSP Contain if

ttEPigtConttklQ

litColltelionQ

EVAL BOD Г TAG

iitBodyConun)

«> I ли body

EVAL^BOOYJAS

Й4ЛТЛ ПО JTIVIVSXlKbtSj

SKIP BODY

Переменные сценария 49

UtrttarTij Сд1^сЛюп BorfyComtni

• itAmibultQ

Рис. 2.2. Диаграмма взаимодействия для класса I teratorTag

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

екты, доступные другим элементам JSP-документа. Например, дескриптор i t e r a t e ,который рассматривался ранее, обеспечивает доступ к элементам вектора.

В некоторых случаях бывает удобнее, если объекты, создаваемые дескрипторами,доступны не как компоненты bean, а как переменные сценария. Подобный подходреализован в JSP-до куме нте, представленном в листинге 2.2,а.

ЛистИнг2.2,а. /t«t.j.p

<htmlxheadxtitle>Scripting Variable Example</titlex/head>

<«@ taglib uri='iterator.tld' preflx-'it' %>

<% java.util.Vector vector = new Java.util.Vectort);vector.addElement("one"); vector.addElement("two");vector.addElement("three"); vector.addElement{"four");

Page 49: Java Server Pages

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

Iterat ing over <%=vector %> ...<р>

< i t : i t e r a t e collection='<%= vector %>'>item <%= item 3><br>

</i t : i terate>

</px/body></html>

Wcb-страница, отображаемая при обращении к данному документу, не отличаетсяот представленной на рис. 2.1.

Сравнив коды в листингах 2.2.а и 2.1,а, можно заметить, что использование пере-менных сценария исключает необходимость в действии < jsp:useBean>. При этомкод упрощается, и область видимости компонента не указывается.

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

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

2. Создание подкласса класса TagExtralnfo, в котором определяются свойства,связанные с переменными сценария.

3. Модификация TLD, в частности объявление в нем подкласса TagExtralnfo,созданного на предыдущем aiare.

Перечисленные действия подробно рассматриваются ниже на примере дескрип-тора i t e r a t e , приведенного в листинге 2.2,а.

Хранение компонентов bean в областивидимости документа

Класс поддержки для новою варианта дескриптора i t e r a t e ничем не отличается откласса IteratorTag, рассмотренного ранее (/WEB-INF/classes/tags/Iterator-Tag. Java). Дело в том, что первый этап создания дескриптора, использующего пере-менные сценария, совпадает с соответствующим этапом, необходимым для созданиякомпонента bean; при этом компонент сохраняется в области видимости документа.Поэтому в данном примере класс IteratorTag применяется без изменений.

Информация о переменных сценарияJSP-контейнер должен иметь данные об имени переменной сценария, ее типе, об-

ласти видимости и времени жизни, а также знать, существует ли переменная или онадолжна быть создана. Такая информация инкапсулируется в объекте, называемом объ-ектом дополнительной информации о дескрипторе (tag extra info). Этот объект являетсяэкземпляром подкласса класса TagExtralnfo. Класс TagExtralnfo принадлежитпакету javax.servlet . j sp,tagext.

Page 50: Java Server Pages

Переменные сценария 51

В листинге 2.2,6 представлен класс I teratorTaglnfo для пользовательского де-скриптора, содержащегося в документе, код которого приведен В листинге 2.2,а.

ЛИСТИНГ 2.2,6. /WEB-INF/claaяеа/tags/IteratorTagInfo.Java

package tags;import javax.servlet.jsp.tagext.TagData;import javax.servlet.jsp.tagext.TagExtrainfo;import Java*.servlet.jsp.tagext.Variablelnfo;

public class IteratorTaglnfo extends TagExtralnfo (public Variablelnfo[] getvariablelnfo(TagData data) {

return new Variablelnfo[] {new Variablelnfo(data.getld(), // имя переменной сценария

"Java.lang.Object", // тип переменнойtrue, // должна ли переменная быть созданаVariablelnfo.NESTED) // область видимости

);I

Метод getvariablelnfo возвращает массив объектов Variablelnfo, каждый изкоторых представляет переменную сценария. В листинге 2.2,6 метод g e t v a r i a b l e -lnfo возвращает массив, содержащий единственный объект Variablelnfo, пред-ставляющий переменную сценария item, используемую в листинге 2.2,а.

Область видимости переменной сценария задается следующими тремя константами:

• VariableInfo.AT_BEGIN• Variablelnfo.NESTED• VariableInfo.AT_END

Константы AT_BEGIN и AT_END задают область соответственно от открывающегои закрывающего дескрипторов до конца документа. Константа NESTED задает областьмежду открывающим и закрывающим дескрипторами.

Связывание класса поддержки с переменнымисценария

Последним этапом создания переменных сценария является связывание классаподдержки с объектом дополнительной информации о дескрипторе. Это связываниевыполняется в описании библиотеки дескрипторов. TLD-файл для дескриптораi te ra te показан в листинге 2.2,в.

Листинг2.2,в. /WEB-INF/tlds/iterator.tld

<taglib><tag>

<name>iterate</name><tagclass>tags.lteratorTag</tagclass>

Page 51: Java Server Pages

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

<teiclaee>tags.IteratorTagInfo</t*iclas*><bodycontent>JSF</bodycontent><attribute>

<naine>collection</name><required>true</required><rtexprvalue>true</rtexprvalue>

</attribute><info>Iterates over a collection</info>

</tag></taglib>

Описание библиотеки дескрипторов, приведенное в листинге 2.2,в, определяет спомощью элемента t e i c l a s s класс дополнительной информации о дескрипторе.Элемент t e i c l a s s содержится в составе элемента tag l ib .

СоветПеременные сценария предпочтительнее компонентов beanПользовательские дескрипторы обеспечивают доступ к компонентам bean, сохра-няя компоненты в контексте документа. Приложив дополнительные усилия, мож-но создавать пользовательские дескрипторы так, что они будут создавать пере-менные сценария, доступные из^Р-документа.Поскольку из JSP можно непосредственно обращаться к переменным сценария,использовать эти переменные удобнее, чем включать в состав документа действия<jsp:useSean>.

Идентификаторы пользовательских дескрипторовПеременные сценария уменьшают зависимость пользовательских дескрипторов

от других элементов JSP-документа. Однако, как видно из примеров, JSP-документдолжен знать имя переменной. Рассмотрим следующий фрагмент^Р-кода:

< i t : i t e r a t e collection^'<*= vector %>'>item <%= item %><br>

< / i t : i t e r a t e >

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

public Variablelnfo[]'getVariablelnfo(TagData data) {return new Variablelnfo[] {

new Variablelnfo("item", "Java.lang.Object",true, Variablelnfo.NESTED)

1;

Page 52: Java Server Pages

Переменные сценария 53

Поскольку обращение к переменной сценария производится по имени, подобнаязависимость существует всегда. Однако вы имеете возможность устранить ее, позво-лив автору JSP задавать имена переменных сценария посредством атрибута id. На-пример, можно потребовать, чтобы рассматриваемый дескриптор i t e r a t e включалобязательный атрибут id, применяемый для именования переменной сценария.В этом случае дескриптор будет выглядеть следующим образом:

<it: i terate id='anltem' col lection=' <%= vector %>'>item <%= anltem %><br>

</it:iterate>

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

<taglib><tag>

<name>iterate</name><tagclass>tags.IteratorTag</tagclass><teiclass>tags.IteratorTaglnfo</teiclass><bodycontent>JSP</bodycontent><attribute>

<name>id</name><required>tme</required><rtexprvalue>t rue</rtexprvalue>

</attribute><attribute>

<name>collection</name><required>true</required><rtexprvalue>true</rtexprvalue>

</attribute><info>Iterates over a collection</info>

</tag></taglib>

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

Page 53: Java Server Pages

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

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

Тело дескриптораВ процессе работы часто возникает не-

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

JSP-документ, показанный на рис. 2.3,применяет чолыкжатсльешй дескрипторc a p i t a l i z e для преобразования текста, со-держащегося между открывающим и закры-вающим дескрипторами, в верхний регистр.Код документа приведен в листинге 2.3,а.

в 1 Cw*«be I ч Ew4fe - МЁгаяП W e * E щкхя

I Obi £ddi yw Rauoriiii iced №t>

-tout

CAPITALIZE THIS STRING

J*- Lor-J rt-w.f>

Рис.2.З. Пример обработхи содержимого поль-зовательского дескриптора

Листинг 2.3,а. /test.

<htmlxheadxtitle>Capitalize Tag Example</titlex/head>

<%@ taglib uri='example' prefix^'example' %>

<example:capitalize>

capitalize this string</example:capitalize>

</body></html>

Класс поддержки дескриптора 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();

Page 54: Java Server Pages

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

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

bodyContent .c learBody() ;b o d y C o n t e n t . p r i n t ( u p p e r ) ;b o d y C o n t e n t . w r i t e O u t ( g e t P r e v i o u s O u t ( ) ) ;

)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 ( ) ) ;1r 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 " ) становится доступной классу поддержки.

<example:capi ta l ize><%= " c a p i t a l i z e t h i s s t r i n g " %>

</example:capi ta l ize>

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

<tag><name>capitalize</name><tagclass>tags.CapitalizeTag</tagclass><bodycontent>JSP</bodycontent>

</tag>

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

<tag><name>capitalize</name><tagclass>tags.Capital!zeTag</tagclass>

<bodycontent>tagdependent</bodycontent></tag>

Page 55: Java Server Pages

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

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

свое содержимое (такие как рассмотренные выше дескрипторы c a p i t a l i z e иi t e r a t e ) , вам надо составить ясное представление о том, что такое тело дескриптораи как оно обрабатывается контейнером сервлетов. Обсуждению этих вопросов по-священ данный раздел.

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

Writer

BodyTagSupport

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

•jelPieviousOulfl

ftodyConleni

/

I .

^•BodyContenifl

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

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

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

Контейнер сервлетов поддерживает стек объектов BodyContent так, что вложен-ные дескрипторы не заменяют содержимое родительского дескриптора. Каждыйобъект BodyContent содержит ссылку на буферизованный выходной поток, соответ-ствующий объекту, расположенному под ним в стеке. Этот поток принято называтьвыходным потоком предыдущею дескриптора (previous out), или включающим выходнымпотоком (enclosing writer). Для обращения к нему используются методы BodyContent.getEnclosingWriter или BodyTagSupport. getPreviousOut.

Page 56: Java Server Pages

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

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

Листинг 2.4,a. /teat.jap

<htmlxheadxtitle>Body Content</title></head>

<%@ tagl ib uri='body' prefix='body' %>

<body:printBody>BODY Kbr>

<body:printBody>BODY 2<br>

</body:printBody>

</body:printBody>

</body></html>

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

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

| pk Edit git* Fefflritei .jpolj Help

-lalxtl

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

Как видно из рисунка, дескриптор printBody выводит содержащийся в нем текст.Класс поддержки дескриптора printBody представлен в листинге 2.4,6.

ЛИСТИНГ 2.4,6. /WEB-INP/clasees/tags/PxintBodyTag. Java

package tags;

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

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

Page 57: Java Server Pages

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

try (getBodyContent().writeOut(getPreviousOut()};

)catch (Java,io.IOException e) (

throw new JspException(e.getMessage());)return SKIP_BODY;

Как видите, код класса занимает всего несколько строк, но разобраться в нем не-просто, поскольку непонятно, почему данные выводятся в выходной поток предыду-щего дескриптора. Рассмотрим JSP-документ, представленный в листинге 2.4,а, с точ-ки зрения контейнера сервлетов.

1<body:printBody> < % — pageContext.pushBody()* — % >

2BODY Kbr>

<body:printBody> <%-- pageContext.pushBody(}* --%>3BODY 2<br>

</body:printBody> <%— pageContext.popBody()** —%>2

</body:pr±ntBody> <%— pageContext.popBody()** —%>1

* Вызывается контейнером сервлетов после обращения к doStar tTag()** Вызывается контейнером сервлетов после обращения к методу

doAfterTagO , но перед вызовом doEndTagO

До появления включающего, или внешнего, дескриптора p r i n t B o d y , предопре-деленная переменная ссылается на объект J spWri ter , посредством которого пере-дается ответ на запрос. Присвоим этому состоянию номер 1. На рис. 2.6 два дескрип-тора pr in tBody условно обозначаются как внешний и внутренний.

После того как контейнер сервлетов вызывает метод doStar tTag внешнего деск-риптора printBody, он записывает в предопределенную переменную out ссылку на эк-земпляр класса BodyContent. В этом объекте содержится ссылка на JspWriter , кото-рый в данном случае играет роль выходного потока предыдущего дескриптора. В этомсостоянии (ему присвоен номер 2) стек выходных потоков насчитывает два элемента(рис. 2,6). Стек формируется посредством контекста документа, в частности с помощьюметода PageContext. pushBody. Метод pushBociy вызывается контейнером сервлетовсразу после вызова метода doStar tTag внешнего дескриптора pr intBody.

Встретив внутренний дескриптор pr intBody, контейнер сервлетов снова вызы-вает метод PageContext.pushBody. Теперь стек содержит объект BodyContentвнутреннего дескриптора, объект BodyContent внешнего дескриптора, а также объ-ект JspWri ter . Этому состоянию присваиваем номер 3. Заметьте, что выходным по-током предыдущего дескриптора для внутреннего p r i n t B o d y является объектBodyContent внешнего pr in tBody. Аналогично, выходным потоком предыдущегодескриптора для внешнего p r i n t B o d y является объект J spWri ter , посредством ко-торого выводится ответ на запрос.

Page 58: Java Server Pages

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

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

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

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

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

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

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

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

После выполнения метода doAf terBody внутреннего дескриптора pr in tBody, ноперед выполнением метода doEndTag этого дескриптора контейнер сервлетов вызы-вает метод PageContext . popBody. Метод popBody извлекает из стека текущий объ-ект BodyContent, в результате чего восстанавливается состояние 2 (см. рис. 2.6).

Поскольку контейнер сервлетов вызывает PageContext .popBody между вызова-ми методов doAf terBody и doEndTag дескриптора, метод doAf terBody внутреннегодескриптора p r i n t B o d y выполняется в состоянии 3, а метод doEndTag этого деск-риптора — в состоянии 2. Не зная этой особенности, трудно организовать обработкутела дескриптора.

Наконец, после выполнения метода doAf terBody внешнего дескриптора контей-нер сервлетов выбывает метод P&geContext. popBody.

Теперь становится ясно, почему в методе PrintBodyTag. doAf terBody данные вы-водятся в выходной поток предыдущего дескриптора. Так происходит потому, что привызове doAfterBody объект BodyContent еще находится в стеке. В|гутреи1ШЙ деск-риптор pr intBody записывает данные в объект BodyContent внешнего дескриптора.Внешний pr intBody записывает свое содержимое и данные, переданные внутреннимprintBody, в объект JspWriter, Если бы метод PrintBodyTag. doAfterBody записы-вал данные с помощью предопределенной переменной out, они бы попали в текущийобъект и несколько позже были бы вместе с ним удалены из стека.

На первый взгляд может показаться, что в классе PrintBodyTag удобнее было быпереопределить метод doEndTag и выводить данные, пользуясь предопределенной пе-ременной out. Действительно, к момент)' вызова doEndTag объект BodyContent, соот-ветствующий текущему дескриптору, уже удален из стека, к переменная out ссылается

Page 59: Java Server Pages

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

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

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

Генерация JavaScript-кодаВ данном разделе рассматриваются пользовательские дескрипторы, которые

оформляют содержащиеся в них данные в виде HTML-элементов и добавляют к нимфрагменты JavaScript-кода. Возможность пользовательских дескрипторов генериро-вать JavaScript-фрагменты позволяет объединить преимущества программ, выпол-няющихся на стороне клиента и сервера.

На рис. 2.7 показан JSP-документ, в котором тело пользовательского дескрипторапредставляется в виде HTML-элемента SELECT. Дескриптор генерирует элементSELECT и после выбора пункта списка передает данные на сервер. Подобным образомудобно оформлять набор ссылок.

В окне, представленном слева на рис. 2.7, показан процесс выбора пункта списка, ав правом окне — результат выбора.

flkt

. -Ю1 х!ylew Fjrt>rlr« Ioolt

View Documentation for

a Doc* " f""' -vJ

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

OocumBrrtalionforJNDl

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

Page 60: Java Server Pages

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

Код JSP-документа представлен в листинге 2.5,а.

Листинг2.5,a. /teat.jsp

<htmlxtitle>Java Api Document a tion</t it le><body>

<%@ taglib uri='html' prefix='html' %>

<form action='showApi.jsp'><font size='4'> View Documentation for</font>

<html:links name=<option<option value=<option value=<option value-<option value=

api'>Servlets'>servlets</option>JSP'>jsp<7option>Swing'>swing</option>JDBC >JDBC</option>JNDI' >JNDK/option>

<option value='JavaBeans'>JavaBeans</option></html:links>

</fonn>

</body></html>

Дескриптор l inks используется также, как и HTML-элемент se lect ; в его составвходят элементы option. Код документа, отображаемого при выборе пункта списка,показан в листинге 2.5,6.

Листинг 2,5,6. /showApi.jsp

<html><title>The <%» request.getParameter["api") %> Api</title><body>

Documentation for <%= request.getParameter("api") %></body></html>

Документ showApi . j sp лишь показывает, что имеется возможность определить,какой из пунктов списка был выбран. При решении реальной задачи, по-видимому,следует отображать различные данные, в зависимости от того, какой API выбранпользователем. Класс поддержки дескриптора l inks приведен в листинге 2.5,в.

Листинг 2.5,в. /WEB-INF/classes/tags/LinksTag. Java

package tags;import javax.servlet. jsp.JspException;import javax.servlet.jsp.tagext.BodyContent;import javax.servlet.jsp.tagext.BodyTagSupport;

public class LinksTag extends ВodyTagSupport (private String name;private StringBuffer buffer;

Page 61: Java Server Pages

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

public void setName (String name) (this.name = name;

>public int doAfterBody(J throws JspException {

t ry |String body = bodyContent.getString();bodyContent.clearBody();

buffer = new StringBuffer("<select name='" + name + "' " +"onChange=' this.form.submit()/>" +body + "</select>");

bodyContent.print(buffer.toString() ) ;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-элементу se lect , дескриптор l i n k s может содержать атрибутname (атрибуты s ize и mult iple в дескрипторе l inks не поддерживаются). Дляподдержки этого атрибута в классе LinksTag содержится свойство, соответствующеесоглашениям JavaBeans.

Вызывая BodyContent. getString, метод doAf terBody получает содержимоедескриптора, представленное в виде строки. Затем объект BodyContent очищается иформируется буфер, содержащий старое тело дескриптора, помещенное между от-крывающим и закрывающим HTML-дескрипторами se lect . После этого содержимоебуфера помещается в BodyContent, а затем записывается во включающий выходнойпоток. В результате генерируется следующий фрагмент кода:

< s e l e c t n a m e = ' a p i ' o n C h a n g e = ' t h i s . f o r m . s u b m i t ( ) ' ><option value-'Servlets '>servlets</option><option value='JSP'>jsp</option><option value='Swing'>swing</option>

<option value='JDBC >JDBC</option><option value=' JNDI' >jNDK/option>

<option value-'JavaBeans'>JavaBeans</option></select>

Сгенерированный дескриптор select содержит атрибут cmChange; с его помощьюопределяется фрагмент JavaScript-кода ( this . form, submit ()), который при выбореодного из пунктов списка передает содержимое формы программе на стороне сервера.

Помимо обработки содержимого пользовательского дескриптора, данный примердемонстрирует совместное использован HeJSP и JavaScript. JSP в сочетании с JavaScriptпозволяет объединять технологии, предназначенные для работы на стороне клиентаи на стороне сервера и создавать достаточно сложные и в то же время гибкие прило-жения. Благодаря механизму пользовательских дескрипторов средства JSP и JavaScriptпредставляются в формате, привычном для авторов Web-страниц.

Page 62: Java Server Pages

Вложенные дескрипторы 63

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

пользуя контекст документа для сохранения объектов в требуемой области видимо-сти. Вложенные дескрипторы могут поступать подобным образом, но они также могутнепосредственно взаимодействовать между собой с помощью метода f i n d A n c e s t o r -WithClass (Tag, Class) классаTagSupport.

Обнаружение предков дескрипторовНесмотря на то что метод f indAnces torWithClass объявлен как s t a t i c и, сле-

довательно, для обращения к нему не нужен экземпляр класса, чаще всего этот метолвызывается в теле одного из методов класса поддержки дескриптора. В этом случаедескриптор, передаваемый методу f indAnces torWithClass , является текущим де-скриптором, на который указывает переменная t h i s .

// Фрагмент одного из подклассов класса TagSupport.

public i n t doStartTag() throws JspException {t r y (

II Поиск предка данного классаClass k l a s s = com.companyname.TagName".class;Tag a n c e s t o r = f indAnces torWithClass( th i s , k l a s s ) ;

>catch(ClassNotFoundException ex) {

throw new J spExcept ion(ex .ge tMessage( ] ) ;

Почти всегда предок представляет собой подкласс класса TagSupport. Посколькуметод f indAncestorWithClass возвращает ссылку на объект Tag, чтобы воспользо-ваться методами TagSupport, необходимо преобразовать возвращаемое значение ктипуTagSupport.

Вторым параметром методу f indAncestorWithClass передается объект Class .Как правило, при поиске известно имя класса предка, а необходимо найти ссылку на со-ответствующий класс. Именно это позволяет сделать метод findAncestorWithClass-.

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

// Метод, упрощающий нахождение предка для подклассов// класса TagSupport

г

private TagSupport getAncestor(String className)throws JspException (

Class klass = null; // имя class использовать нельзяtry {

klass = Class.forName(className};}catch(ClassNotFoundException ex) f

throw new JspException(ex.getMessage());

Page 63: Java Server Pages

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

return (TagSupport)findAncestorWithClass(thia, klass);

Разделение данных

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

<smp:ancestor>

<smp:offspring/></smp:ancestor>В методе doStartTag предок связывает с именем "name" значение "value".package tags;

public class AncestorTag extends TagSupport {public int doStartTag() throws JspException I

setValue("name", // только строка"value"); // любой объект

return EVAL BODY INCLUDE;

Метод AncestorTag.doStartTag возвращает значение EVAL_BODY_INCLUDE; врезультате контейнер передает тело дескриптора без изменений. В теле родительско-го дескриптора содержится дочерний дескриптор, класс поддержки которого выгля-дит следующим образом:

public class OffspringTag extends TagSupport (public int doStartTag() throws JspException {

AncestorTag ancestor = null;

try {ancestor = (AncestorTag)findAncestorWithClass(this,

tags.AncestorTag.class);pageContext.getOut0.println(

ancestor.getValue["name")) ;}catch(Exception ex) {

throw new JspException(ex.getMessage(}};}return EVAL_BODY_INCLUDE;

Метод OffSpringTag.doStartTag вызывает метод findAncestorWithClass дляобнаружения ближайшего предка класса tags.AncestorTag. Метод TagSupport.getValue ("name") возвращает значение ("value"), которое затем выводится с помо-щью предопределенной переменной out.

Page 64: Java Server Pages

Вложенные дескрипторы 65

РезюмеПриблизительно треть спецификацииJSP 1.1 посвящена пользовательским деск-

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

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

Page 65: Java Server Pages

'

HTML-ФОРМЫ

В этой главе...

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

- Поля редактирования, текстовые областии переключатели опций.

- Флажки опций и списки,

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

- Проверка на стороне клиента с помощью JavaScript-сценариев.

- Проверка на стороне сервера с помощью JSP-документов.- Проверка на стороне сервера с помощью сервлетов.

- Использование сервлетов и JSP-документов для проверкина стороне сервера.

• Базовый набор классов для работы с формами.

- Использование образа разработки fa?ade для HTML-форм.- Элементы, допускающие выбор.- Проверка введенных данных.

• Применение пользовательских дескрипторов.

Page 66: Java Server Pages

HTML-формы выполняют в Web-приложении ту же роль, что и элементы поль-зовательского интерфейса (например, компоненты Swing или AWT) в обыч-ных программах.

В спецификации JSP не предусмотрена поддержка форм, однако вы можете ис-пользовать действие j s p : u s e B e a n для хранения значений форм в объектах bean.Хранение состояния одного объекта {в данном случае элемента формы) в другом объ-екте (компоненте bean) — пример использования образа разработки Memento.

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

Формы и компоненты beanИспользование компонентов bean для хранения состояния— один из основных

способов поддержки форм. Доступ JSP к beans осуществляется достаточно просто,поэтому извлечение значений формы из компонентов не составляет труда.

Передача данных формыКогда пользователь активизирует форму, броузер передает строку параметров ре-

сурсу, URL которого указан посредством атрибута a c t i o n формы. {Если атрибутact ion отсутствует, активизация формы приводит к повторной загрузке Web-структураницы.) В строке параметров содержатся имена и значения в формате7.шя1=значенш1кшля2=зпаче){ие2&. . . &UMHN=3na4emteN. Например, для ф о р м ы , в соста-ве которой находится единственное поле редактирования с именем name, содержа-щее текст John, строка параметров будет иметь вид "name=John", а если в ту же фор-му добавить еще одно поле с именем phone и ввести в нем последовательность спмво-

Page 67: Java Server Pages

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

лов 555-1212, то строка параметров будет выглядеть следующим образом:"name=John&phone=555-1212".

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

<%= request.getParameter("name") %>

Кроме того, данные формы можно поместить в соответствующий компонент bean.

<jsp:useBean id='form' class='beans.Form1 scope='request'><jsp:setProperty name='formf propeirty='* '/>

</jsp:useBean>

Б предыдущем фрагменте кода в качестве значения свойства property указансимвол '*'. При этом используется Java-отражение и устанавливаются свойства ком-понента, соответствующие параметрам запроса. Например, при получении параметрас именем name вызывается метод компонента setName,

В последующих двух разделах описываются особенности применения действияj sp: useBean для хранения состояния формы в компоненте bean. Далее будет обсуж-даться хранение состояния элементов формы в отдельных компонентах; этот подходболее сложен в реализации, но допускает повторное использование кода.

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

Поля редактирования, текстовые областии переключатели опций

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

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

Page 68: Java Server Pages

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

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

Qk pftt lew favoritej ioolj Utlp

Мате- [Ralph

t~viss f~ master catd

^ discovery ^ amencari

1Submit Query

name:ca mm ants: Enlur commentscredit

*-; Lottrf » w

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

£ile Bill VFB» FawrltK loot blip

Name: [Ralph

^vtsa ^ mastej card

^ dtscovefy ^ amerJcan

Submit Queiy I

name: Ralphcommanls: Enter commentscradle, disc

Tt Locot n

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

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

Листинг3.1,a. /form.jsp

<html><title>Textfields, Text Areas, and Radio Buttona</title><body>

<jsp:useBean id='form' class='beans.Form' scope='request'><jsp:satProperty name='form' property='*'/>

</jsp:useBean>

<form>Name:<input type='text' name='name'

value='<%=form.getName()%>' /><p>

<input type='radio' name='credit' value='visa'<%= form. cteditSelectionAttr ("visa") %»visa

<input type='radio' name='credit' value='mc'

<%= form.creditSelectionAttr ("me") %»master card<br>

<input type='radio' name=' credit' value=r disc'

<%= form.creditSelectionAttc("disc") %>>discovery

<input type^'radio' name='credit' valuer'amex'

<%= form.creditSelectionAttr("amex") %>>american express

</p><pxtextarea name='comments' cols='25'

rows='5'><%= form.getComments() %></textarea>

Page 69: Java Server Pages

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

</p><p><input type='submit'/></p></form>

<%@ include file='showForm.jsp' %>

</body></html>

При каждом обращении к данном)' документу в области видимости запроса созда-ется и сохраняется компонент типа beans . Form. Действие j sp: setProperty уста-навливает свойства компонента в соответствии с параметрами запроса. Затем компо-нент используется для определения значений поля редактирования и текстовой об-ласти, а также для установки состояния переключателя опции.

Документ, приведенный в листинге 3.1,а, включает JSP-файл showForm. j sp.В этом файле, содержимое которого показано в листинге 3.1,6, также производитсяобращение к компоненту bean.

ЛИСТИНГ 3.1,6. /showForm.jsp

<b>name: </b> <%= form.getName()<b> comments : </b> <%«• form, get Comment 5 (]<b>credit : </b> <%= form.getCredit()

Файл showForm. j sp включен в состав документа посредством директивыinclude, т.е. обработка содержимого файла производится на этапе компиляции. По-этому выражения, находящиеся в файле showForm. jsp, могут обращаться к компо-ненту form. Если бы для включения файла использовалось действие, содержимоефайла обрабатывалось бы в момент получения запроса и компонент bean с именемform был бы недоступен из файла showForm. jsp.

Код компонента bean, создаваемого JSP-до куме нтом, представлен в листинге 3.1,в.

ЛИСТИНГ З.1.В. WEB-INF/classes/beans/Form.Java

package beans;

public class Form {String name, comments » "Enter comments", credit;

public void setName(String s) { name • s; )public String getNameO { return name ! = null ? name :

public void setComments(String s) ( this.comments = s;public String getComments() ) return comments; }

public void setCredit(String s) ( credit = s; Ipublic String getCredit0 {

return credit != null ? credit : " " ;

public String creditSelectionAttr(String creditName) {

Page 70: Java Server Pages

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

iffcredit !- null) {return credit.equals(creditName) ? "checked"

}

return " " ;

Согласно спецификации JavaBeans, компонент Form содержит методы, обеспечи-вающие доступ к свойствам, в которых хранятся значения поля редактирования(name), текстовой области (comments) и переключателей опции (cred i t ) . Данныйкомпонент также инкапсулирует код для доступа к данным формы. При этом значениеnull обрабатывается специальным образом, чтобы при отсутствии соответствующе-го параметра отображалась не последовательность символов "null", а пустая строка.Если бы обработка значения n u l l не выполнялась в компоненте bean, соответствую-щий код пришлось бы включать в состав JSP-до куме н та. Вынесение Java-кола за преде-лы JSP-файла делает документ более читаемым и упрощает его сопровождение.

Флажки опций и спискиСписки создаются посредством HTML-элементов s e l e c t и option. Элемент

select, содержащий элементы option, имеет следующий вид:

<select name='years' s i z e = ' 5 ' multiple><option value='2000'>2000</option><option value='2001'>200K/option><option valuer'2002">2002</option>. . . последующие пункты , . ,

</select>

Если в приведенном списке пользователь выберет пункты 2000, 2001 и 2002,строка параметров, передаваемая на сервер при активизации формы, будет выглядетьследующим образом: "... years=2000Syears=2Q01&years=2002 ...". Поскольку пара-метр years встречается несколько раз, для чтения его значений необходимо исполь-зовать следующее JSP-выражение:

<% String[] value= request.getParameterValues("years") %>

В данном случае, в отличие от поля редактирования и текстовой области, значе-ние параметра представляется в виде массива строк.

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

На рис. 3.2 показан JSP-документ с формой, содержащей флажки опций и список.Как и для документа, изображенного на рис. 3.1. в форме отсутствует атрибут ac t ion,и при активизации форма повторно отображается на экране, В левом окне на рисункепоказан внешний вид Web-страницы до того, как пользователь щелкнул на кнопкеSubmit Query, а в правом окне — тот же документ после активизации данной кнопки.

Page 71: Java Server Pages

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

- ICl x\f ib Edit Щаш Fjr«fH«! loWl

Find:^ Stanley Cup ChimpsP SuparBowl ChampsГ World Series ChampsW NCAA Champs

For the following years:

ДЕЯ-1

Submit unary |

Si

Find:F* Stanley Cup ChampsP Super Bowl ChimpsГ World Series ClumpsP NCAACtitmpt

looh t)>(>

For the following years:

ESubmit Query 1

Find; oianicj-cup super-bawl пелаd a t « : 1 9 9 9 1ЭЭ7 1ЭЭ5

-IDixl

Рис. З.2. Использование компонентов bean для хранения значений флажковопций и списков

Код JSP-документа, показанного на рис. 3.2, приведен в листинге 3.2,а.

Листинг3.2,a. /form.jsp

<html><title>Checkboxes and Options</title><body>

<jap:u3eBean id='form' class='beans.Form' scopes'request'>

<jsp:setProperty name=' form' property='*'/></jsp:useBean>

<form><font size='5'>Find:</fontxbr>

<input type=f checkbox' name^'categories' valuer' stanley-cup'

<%= form.categorySelectionAttr("stanley-cup") %»Stanley Cup Champs<br>

<input type='checkbox' name='categories' value=' super-bowl'

<%= form,categorySelectionAttr{"super-bowl") %>>Super Bowl Champs<br>

<input type='checkbox' name='categories' value='world-series'

<%= form.categorySelectionAttr("world-series") %»World Series Champs<br>

<input type='checkbox' name^'categories' value='ncaa'<%= form.categorySelectionAttr("ncaa") %>>NCAA Champs<br>

<pxfont size=' 5' >For the following years : </fontxbr>

Page 72: Java Server Pages

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

<select name='years ' s i z e = f 5 ' m u l t i p l e ><% f o r f i n t year=1999; year > 1989; —year) { %>

<option value='<<b= year %>'<%= f o r m . y e a r S e l e c t i o n A t t r ( I n t e g e r . t o S t r i n g ( y e a r ) ) %»<%=year%x/option>

<% } %></select>

</pXpXinput type=f submit'/x/p></form>

<i@ include file='showForm.jsp' %>

</bodyx/html>

Как и документ, показанный в листинге 3.1, данный JSP-код создает компонентbean типа beans . Form и инициализирует его в соответствии с параметрами запроса.После этого данный компонент используется для определения состояния флажковопций и пунктов списка.

Как и в рассмотренном ранее примере, в состав данного документа включаетсяJSP-файл, в котором содержится код, предназначенный для отображения значенийформы. Этот файл представлен в листинге 3.2,6.

Листинг 3.2,6. /showForm. j

if[form.getCategories() != null) ( %><b>find:</t»

StringU strings = form.getCategories () ,-for(int i=0; i < strings.length; ++i) ( %>

<%= strings[i] %>

)if (form.getYearsO !- null) ( %>

<brxb>dates : </b><% StringH strings - form.getYears (1 ;

for(int i=0; i < strings.length; ++i) ( %><%= strings[i] %>

S }

Код компонента bean, который используется JSP-документом, представленным влистингах 3.2,а и 3.2,6, показан в листинге 3.2,в.

Листинг 3.2,в. /WEB-INF/classes/beans/Form. Java

package beans;

public class Form (Stringf] years, categories;

Page 73: Java Server Pages

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

public String[] getCategories() ( return categories; 1public void setCategories(String[] categories) {

this.categories - categories;}public String categorySelectionAttr[String category) {

if (categories != null) {for(int i=0; i < categories.length; ++i) \

if(categories[i].equals(category))return "checked";

return "" ;}

public String[) getYears() f return years; )public void setYears(String[] years) [ this.years = years; }

public String yearSeleetionAttr(String year) {if(years != null) {

for(int i=0; i < years.length; ++i) {if (years[i].equals(year) )

return "selected";i

)return "" ;

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

Поскольку для установки флажка опции используется атрибут checked, а для вы-бора пункта списка— атрибут selected, методы categorySelectionAttr и year-Select ionAttr компонента возвращают для выбранных флажков опций и пунктовсписка соответственно значения "checkeci" и "selected".

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

как на стороне клиента, так и на стороне сервера. На стороне клиента для этогообычно используются JavaScript-сценарии, а на стороне сервера—JSP-документы.

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

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

Page 74: Java Server Pages

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

Проверка на стороне клиента с помощьюJa vaScript-сценариев

Поскольку проверка на стороне клиента не имеет непосредственно отношения кразработке JSP-документов, она рассматривается здесь лишь в общих чертах. Нарис. 3.3 показан JSP-документ, содержащий простую форму с нолями редактирования,которые предназначены для ввода имени пользователя и его почтового адреса. Бданный документ включен JavaScript-сценарий, который позволяет убедиться, что псеполя заполнены, в составе почтового адреса содержится символ '@* и адрес заканчи-вается последовательностью ". com" или ". edu".

В окне, прннеденном на рис. S.3 слека, показана частично заполненная форма, а вправом окис — результат, полученный при активизации этой формы.

£ K k r t SiA> V*Wion nth Ji

11 f ib £diP Jtlw F vti

' A>*ft« JXJ hup /tocdfrott.

Firsl Name: jgeorei

Lsst Ma me

E-mail Address: [

Submit Query

£]Е>=пя

waSdipt -Mtfaiuft Irtann... ___

riT*i Ipob i ^ t

•-.LoMlrwanfl

aP*Go

J

£'.tMЕЙеVddaticn«*nJmsScret -Mtus«n Irtane .. . |p| K[

pit Edit llcw Fflmmra IDOII t j t t

ОвШпйЛШйЯтт Bp7l« _^

£тЫ Асйви (nurt cortiin ^ end er*J in com c* i

о™'

Рис. 3.3. Использование JavaScript для проверки корректности данных на сторонеклиента

Кодировка JSP-документа, показанного на рис. 3.3, представ,1ена в листинге 3.3.

Листинг 3.3./form.jsp

<htral><head><title>Client Side Validation with JavaScript</title>

</head>

<body><jsp;useBean id='form' scope='request' class='beans.Form'>

<jsp:setProperty name='form' property-'*'/></jsp:useBean>

<form name='simpleForm' onSubmit='return validate[)'><tablextr>

<td>First Kame:</td><tdxinput type='text' size=15 name='f irstName'

valuer' <%= form.getFirstName () %>' /></tdx/trxtr>

<td>Last Name:</td><tdxinput type='text' size=15 name=' lastbiame'

value='<%= form.getLastKame () %>' /></tdx/trxtr>

Page 75: Java Server Pages

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

<td>E-mail Address:</td>

<tdxinput type='text' size=25 name='emailAddress'

value='<%= form.getEmailAddress{) %>' /></td></tr></table>

<pxinput type=' submit' /></p>

</form>

<script language='JavaScript' >

function validate() (var firstName = simpleForm.firstHame.value,

lastName = simpleForm.lastName.value,emailAddress = simpleForm.emailAddress.value,

errorMsg •= "",errorDetected •• false;

if(firstName — "" || lastName — "" || emailAddress — ""){errorMsg += "Please fill in all fields";errorDetected = true;

}if(!isEmailAddressValid(emailAddress)) {

if (errorMsg.length > 0)errorMsg += "\n";

errorMsg += "Email Address must contain @ and " +"end in .com or .edu";

errorDetected = true;

)

if(errorDetected)alert(errorMsg);

return !errorDetected;

)

f u n c t i o n i s E m a i l A d d r e s s V a l i d ( s ) (v a r a t S i g n = new R e g E x p ( " . * ( 8 ) . * " ) ,

d o t E d u = new R e g E x p ( " . e d u $ " ) ,dotCom = new R e g E x p ( " - c o m $ " ) ;

r e t u r n a t S i g n . t e s t ( s ) S S ( d o t c o m . t e s t ( s ) | I d o t E d u . t e s t ( s ) ) ;}

</script></bodyx/html>

В качестве значения атрибута onSubmit формы указано выражение ' r e t u r nva l idate () ', т.е. при активизации формы вызывается JavaScript-функция va l idate .Если функция val idate возвращает значение true, данные формы передаются насервер, в противном случае отображается диалоговое окно с сообщением об ошибке.

Как и в предыдущих примерах, значения формы сохраняются в составе компонентаbean. Поскольку этот компонент не используется при проверке корректности данных итак как подобные компоненты были рассмотрены ранее, код bean здесь не приводится.При необходимости вы можете скопировать код компонента вместе с кодами другихпримеров, рассмотренных в данной книге, обратившись по адресу http://www.phptr. сотп/advj sp.

Page 76: Java Server Pages

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

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

Проверка на стороне сервера с помощьюJSP-д окументов

Проверка корректности данных на стороне сервера выполняется с помощью JSP-документов или сервлетов. Здесь рассматривается использование JSP-документов, апроверка посредством сервлетов будет обсуждаться в следующем разделе.

На рис, 3,4 показан JSP-документ, содержащий форму, аналогичную форме нарис, 3,3. В левом окне на рис. 3.4 показано состояние документа перед щелчком на кноп-ке Submit Query, а в правом окне — состояние документа после активизации формы.

i [lie £dll yitw fffrtjrlm Itotr t№V

Firtl Name: Igeorge

L>!t Hum Г

E-mail Address: Г

Submit Query |

ЕЭ

ii

d

0 ISHV» SWsVAiatiwi v«i JSP - Micunoft lite

| j £ii> Edit i n * Favorittt loois |

; ; A * * » H | « ] | tp49iN.i».o>ao I t l«IN«H . l

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

First Name: \ gen где

La si Name |

Б-mail Address: [

SubmitQueiy |

™чЕч>ки1 . l a l x |

in .cum sr.edu•

Г Local inli«nd

Рис, З.4. Использование JSP-документов дли проверки корректности данных на сторонесервера

По сравнению с кодом, представленным в листинге 3.3, данный код имеет два от-личия. Во-первых, в нем отсутствует JavaScript-сценарий, а во-вторых, в качестве зна-чения атрибута act ion формы задано значение v a l i d a t e , jsp.

<form name=fsimpleForm' act ion^ 'va l idate . j sp f />

</form>

При активизации формы запрос передается документу 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");

Page 77: Java Server Pages

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 += "<br>";

errorMsg += "Email address must contain 8 and " +"end in .com or .edu";

errorDetected = true;}if(errorDetected) { %>

<%= errorMsg %><jsp:include page='form.jsp' flush='true'/>

<% } else { %><jsp:forward page='registrationComplete.jsp'/>

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

Проверка на стороне сервера с помощью сервлетов

Для проверки корректности данных на стороне сервера вместо JSP-докумснтов

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

внести единственное изменение— задать значение атрибута action, равное

ValidatiortServlet.

<form name='simpleForm' action='ValidationServlet'/>

</form>

Сервлет, на который ссылается данный фрагмент кода, приведен в листинге 3.5.

Листинг 3.5./WEB-INF/clasaes/ValidationServlet.java

import Java.io.lOException;

import j avax.servlet.RequestDispatcher;import javax.servlet.ServletException;

Page 78: Java Server Pages

Проверка корректности данных 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("") IIemail.equals("")) {

errorMsg += "Please fill in all fields.";errorDetected = true;

1if(email.indexOf("@") =- -1 |[

(!email.endsWith(".cam") £fi !email.endsWith(".edu"))) {ifferrorMsg.length() > 0)

errorMsg += "<br>";

errorMsg += "Email address must contain @ and " +"end in .com or ,edu";

errorDetected = true;>if(errorDetected) (

res.getWriter().print(errorMsg);nextStop = "/form.jsp";

)RequestDispatcher rd;rd - getServletContext().getRequestDispatcher(nextStop);

if[nextStop.equals("/form.jsp"))rd.include[req, res);

elserd.forward(req, res) ;

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

Page 79: Java Server Pages

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) {req.satAttr ibuta("val idate-error" , errorMsg);nextStop « "/form.jsp";

)

)

Page 80: Java Server Pages

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

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

<htmlxheadxtitle>Server Side Validation</title><%@ taglib ur i - ' va l idate ' prefix^'validate' %>

</head>

<body><4 String errorMsg = [String)request.getAttribute(

"val idate-error");if[errorMsg != null) { %><*= errorMsg %>

<% J «>

<p><form name-'simpleForm' a c t i o n = ' V a l i d a t i o n S e r v l e t ' / >

</form></p></bodyx/html>

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

<htmlxheadxtitle>Server Side Validation</title><%@ taglib uri-'validate' prefix='validate' %>

</head>

<body>•«validate: showValidateError/>

<form name=rsimpleForm' action^'ValidationServlet'/>

</form></p></bodyx/html>

Класс поддержки данного пользовательского дескриптора может выглядеть так,как показано в листинге 3.6.

Листинг3.6. /WEB-INF/olasses/tags/ShowValidateErrorTag.Java

package tags;

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

public class ShowValidateErrorTag extends TagSupport fpublic int doEndTagO throws JspException (

String msg =• (String)pageContext.getRequest().getAttribute("validate-error")

if(msg != null) (try (

Page 81: Java Server Pages

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

pageContext.getOut().print(msg);

}catch(Java.io.lOException ex) {

throw new JspException(ел.getMessage());

return EVAL_PAGE;\

)

Базовый набор классов для работыс формами

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

<jsp:useBean id='form' class='beans.Form* scope='request *><jsprsetProperty name='form1 property^'*'/>

</jsp:useBean>

Если в качестве значения атрибута property дескриптора j sp : se tProperty за-дан символ '*', JSP-контейнер использует Java-отражение для установки свойств ком-понента bean в соответствии с параметрами запроса. Например, для параметра за-проса с именем category JSP-контсйнср обращается к методу setCategory. Еслиэтот метод существует, JSP-контейнср вызывает его и передает значение параметраcategory. Такие же действия предпринимаются для остальных параметров запроса.

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

Несмотря на то что для разных форм создаются различные JSP-классы, коды этихклассов во многом совпадают. Совпадающие фрагменты кода могут использоваться вразличных компонентах bean. Рассмотрим следующий фрагмент кода (он входит в со-став компонента, представленного в листинге 3.2,с):

. . .private String!] categories;

щ

public String categorySelectionAttr(String category) 1if [categories != null) (

for{int i=0; i < categories.length; ++i) {if(categories[i].equals(category) J

return "checked";

return "";

Page 82: Java Server Pages

Базовый набор классов для работы с формами 83

Метод categorySelectionAttr, приведенный выше, определяет, установлен лифлажок опции с именем category, и возвращает либо строку "checked", либо пустуюстроку. Возвращаемое значение может быть использовано в качестве атрибута HTML-дескриптора input. Другие компоненты, соответствующие другим формам, содержатаналогичные методы для флажков опций. Например, для набора флажков с именемgrocery компонент bean реализует следующий метод, практически идентичный рас-смотренному выше:

private String!] groceries;

public String grocerySelectionAttr(String grocery) [if(categories != null) {

for(int i=0; i < groceries.length; ++i) {if(groceries[i].equals(grocery))

return "checked";

)return " " ;

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

Использование образа разработки fagadeдля HTML-форм

Объект facade предоставляет единый упрощенный интерфейс для общих средствподсистем. Для случая HTML-форм образ разработки facade может быть реализовантак, как это показано на рис. 3.5.

JSP-докумеш

<inpLttype='te)tti

form.getNam'eO %>' >

<%if(form.validate()) {

)

Form (обьекг facade)

validated

Пакет beans.html(общие средства)

Textfield Element

get Valued

RadioElement

validate))

Код, специфический для приложения Повторно используемый код

Рис. 3.5. Использование образа разработки facade для поддержки форм

Page 83: Java Server Pages

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

Объектом Fac-ade является компонент bean для формы, а общими средствами —классы из пакета beans .html, содержащие код для элементов формы. На рис. 3.5 об-щие средства составляют повторно используемый код; возможность повторного ис-пользования кода является одним из основных преимуществ применения образаГа9ас1е для поддержки форм.

Согласно толковому словарю, fa$ade — фальшивое, поверхностное или искусст-венное представление некоторого явления. Это подходящее определение для компо-нента, представленного на рис. 3.5, который лишь делегирует функции более общимсредствам. Например, рассмотренный ранее метод categorySelectionAttr можетиметь следующий вид:

public class Form \CheckboxElement categories = new CheckboxElement 0;

public String categorySelectionAttr() {return categories.selectionAttr(category) ;

В данном случае Form.categorySelectionAttr возвращает строку символов,которая может быть использована в качестве атрибута HTML-дескриптора input.

Класс CheckboxElement, используемый компонентом Form, выглядит приблизи-тельно так;

public class CheckboxElement (private Stringtl items;

public String selectionAttr(String item) {if [items !- null) (

for( int i-0{ i < items.length; ++i) (i f( i tems[i] .equals( i tem))

return "checked";

return "" ;}

}

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

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

Реализация базового набора классовБазовый набор классов позволяет существенно упростить процесс сохранения со-

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

Page 84: Java Server Pages

Базовый набор классов для работы с формами 85

На рис. 3.6 показан JSP-документ, в состав которого входит форма, содержащая

большое количество элементов. Атрибут a c t i o n для данной формы не указан, поато-

му после щелчка на кнопке Submit Query форма повторно отображается на экране.

| -А Ги

: Fit! grill Vim Fjwrutl Tool; ДО

С*Ы!

Nam&: |Rick

Credit Card

^ r-ll.4F.FP' - -*Г -1

Г am* i

Exairalion Date

D1/DO *

iiSLiH/OD05/00.»]

Ргогзгятгптд Language Екрипепс.ч

P Hint Г JSVJ P Peri Г Osp

Ccrnmftnts

ulist's this? j j

ISelet) Л FftJitr

orange ^|

SubmJlOuny

jf l ( Local rtieJ

Рис. З.6. Использование базового набора классовдля поддержки форм

Объем JSP-кода для Web-страницы, показанной на рис. 3.6, достаточно велик;

фрагменты этого кода представлены в листинге 3.7,а.

Листинг 3.7,а. /form, j s p (фрагменты)

<jsp:useBean id='form' class='beans.Form' scope='request'><jsp:setProperty name='form' property='*'/>

</jsp:useBean>

<form>

<%— Поле редактирование —%>Name: <input type='text ' name='name'

value='<%- form.getName() %>'/>

<%— Флажки опций —%>Programming Language Experience:<br><input type='checkbox' name='languages' value='html'

<%= form.languageSelectionAttr("html") *> /> Html

<%— Переключатели опций —%>Credit Card:<br>

<input type-'radio' name='credit' value='visa'<%- form.creditSelectionAttr("visa") %>>

visa</input type='radio'><br>

Page 85: Java Server Pages

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

< % — Текстовая область — % >Comments:<br><textarea name='comments' cols='20' rows='5'>

<%= form.getComments() £></textarea><p>

<%—— Окно списка --%>Expiration Date:<br><select name=' expiration' size=5>

<option <%= form.expirationSelectionAttr("01/00") %>value=' 01/00'>01/00</option>

</select>< % — Раскрывающийся список —%>

Select a Fruit:<br><select name='fruit'>

<option <%= form.fruitSelectionAttr("apple") £>value='apple'>apple</option>

</select>

<%— Кнопка Submit —i><pxinput type=' submit' /></p>

</form></bodyx/html>

;

При загрузке данный JSP-докумеит создает экземпляр компонента beans . Form и

устанавливает его свойства в соответствии с параметрами запроса. Затем с помощью

компонента bean задаются значения формы.

Код компонента bean, использованного в листинге 3.7,а, представлен в листинге 3.7,6.

ЛИСТИНГ3.7,6. /WEB-INF/classes/beans/Formoava

package beans;

import beans.html.CheckboxElement;import beans.html.OptionsElement;import beans.html.RadioElement;import beans.html.TextElement;import beans.html.TextAreaElement;

public class Form {

TextElement name = new TextElement();RadioElement credit = new RadioElement();TextAreaElement comments = new TextAreaElement();CheckboxElement languages = new CheckboxElement();OptionsElement expiration = new OptionsElement();OptionsElement fruit = new OptionsElement(};

public String getNameО ( return name.getValue(); }public void setKame(String s) ( name.setvalue(s); }

public String getComments() [ return comments.getValue();}

Page 86: Java Server Pages

Базовый набор классов для работы с формами 87

publ ic void setComrrtents [Str ing s) ( comments . setValue (s) ; }

publ ic S t r i n g g e t C r e d i t O { r e t u r n c r e d i t . g e t V a l u e ( ) ; )public void s e t C r e d i t ( S t r i n g s) ( c r e d i t - a e t V a l u e ( s ) ; )

public S t r i n g [ ] getLanguages() { r e t u r n l a n g u a g e s . g e t V a l u e ( ) ; )public void se tLanguages(Str ing[] s) ( l a n g u a g e s . s e t V a l u e ( s ) ; }

public S t r i n g [ ] g e t F r u i t ( ) [ r e t u r n f r u i t . g e t V a l u e ( ) ; }publ ic void s e t F r u i t ( S t r i n g ! ] s ) { f r u i t . s e t V a l u e ( s ) ; t

publ ic S t r i n g ! ] g e t E x p i r a t i o n ( ) ( r e t u r n e x p i r a t i o n . g e t V a l u e ( ) ; )publ ic void s e t E x p i r a t i o n ( S t r i n g [ ] s) ( e x p i r a t i o n . s e t V a l u e ( s ) ; 1

publ ic S t r i n g c r e d i t S e l e c t i o n A t t r ( S t r i n g s) {r e t u r n c r e d i t . s e l e c t i o n A t t r ( s ) ;

)publ ic S t r i n g l a n g u a g e S e l e c t i o n A t t r [ S t r i n g s) {

r e t u r n l a n g u a g e s . s e l e c t i o n A t t r ( s ) ;]public String expirationSelectionAttr(String s) (

return expiration.selectionAttr(s);)public String fruitSelectionAttr(String s) (

return fruit.selectionAttr(s);

Данный компонент bean использует несколько классов из пакета beans .html, на-пример TextElement, RadioElement и др. Сравнив данный компонент с компонен-том /WEB-INF/classes/beans/Form. Java, показанным в листинге 3.2,в и выпол-няющим более простые функции, можно увидеть, насколько упрощает процесс разра-ботки использование классов, принадлежащих beans . html.

Поскольку компонент bean, приведенный в листинге 3.7,6, не делает ничего, кро-ме делегирования функций объектам, он представляет собой самый настоящий объ-ект facade и может быть сгенерирован при разборе HTML-кода.

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

С точки зрения содержимого пакета beans.html элементы формы можно разде-лить на две категории: элементы, генерирующие единственный параметр (например,поля редактирования, текстовые области и переключатели опций), и элементы, ге-нерирующие несколько параметров в составе запроса (например, флажки опций исписки). Эти две категории элементов представляются двумя абстрактными классамиStringElement и StringArrayElement.

Поскольку все классы, представляющие элементы формы, должны включать мето-ды для проверки корректности содержимого, классы StringElement и Str ing-ArrayElement реализуют интерфейс ValidateElement, показанный в листинге 3.7,в.

Page 87: Java Server Pages

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

ValicJaledElemenL(from html)

•galValidationErrarQ

StringEfement

•getValuej)

Siring(hem 1*nfl}

Рис. 3.7. Диаграмма классов для пакета beans, html

ЛИСТИНГ 3.7,В. /WEB-INF/classes/beans/html/ValidatedElement. Java

package beans-html;

public interface ValidatedElement {boolean validate О;String getValidationError() ;

В интерфейсе ValidatedElement объявлен метод val idate, который указывает,корректно ли содержимое элемента. Если элемент заполнен неправильно, т.е. еслиметод v a l i d a t e возвращает значение false, метод getValidationError долженвозвращать строку, указывающую причины, по которым содержимое элемента счита-ется некорректным.

Код класса StringElement показан в листинге 3.7,г.

Page 88: Java Server Pages

Базовый набор классов для работы с формами 89

Листинг 3.7,г. /WEB-rNF/olaeaes/beans/html/StringElemant. java

package beans.html;

public class StringElement implements ValidatedElement Ifinal protected String emptyString = "";private String value;

public void setvalue(String value) {this.value - value;

)public String getValueU {

return value !- null ? value : emptyString;)public boolean validated {

return true;)public String getValidationError() (

return emptyString;

I

Класс StringElement поддерживает строку символов, доступ к которой осущест-вляется с помощью метода getValue. Если значение не задано, данный метод воз-вращает пустую строку. Это необходимо, так как в противном случае метод возвращалбы значение nul l , которое отображалось бы в соответствующем HTML-элементе,

По умолчанию данные, описываемые подклассами StringElement, считаютсякорректными. Чтобы изменить поведение класса, надо переопределить методыval idate и getValidationError. Пример переопределения этих методов приве-ден в следующем разделе.

Классы TextElement и TextAreaElement являются подклассами StringElement,причем в них не реализуются новые средства. Определения этих классов приведеныниже.

public class TextElement extends StringElement {1public class TextAreaElement extends StringElement 1}

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

Код класса StringArrayElement, выступающего в роли суперкласса для классовCheckboxElement и OptionsElement, показан в листинге 3.7,д.

Листинг 3.7,Д. /WEB-IKF/classes/beans/html/StringArrayElement.Java

package beans.html;

public abstract class StringArrayElementimplements SelectableElement, ValidatedElement {

final String emptyString • " " ;private String[] value;

Page 89: Java Server Pages

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

public void setValue[String[] value) {this.value = value;

}

public 3tring[] getValueO freturn value != null ? value : new String[ ]{}

Ipublic boolean validated (

return true;}

public String getValidationError[) {return " " ;

}

public boolean contains(String s) {String!] strings - getValueO;

for(int i=0y i < strings,length; ++i) {if(strings[i].equals(s))

return true;}

return false;

Класс StringArrayEleraent похож на StringElement, отличие состоит лишь в том,что StringArrayEleraent поддерживает массив строк. Как и для класса StringElement,содержимое StringArrayElement no умолчанию считается корректным.

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

Элементы, допускающие выборФлажки и переключатели опций, а также пункты списка представляют собой груп-

пы однотипных элементов. При этом один или несколько элементов группы могутСыть выбраны. Элементы такого типа описывает интерфейс SelectableEleraent,код которого приведен в листинге 3.7,е.

ЛИСТИНГ 3.7,в. /WEB-INF/classes/beans/html/SelectableElement. }ava

package beans.html;

public interface SelectableElement {String selectionAttr(String s};

Интерфейс SelectableElement реализуется классами RadioElement, Checkbox-Element и Opt ions Element. Для выбранного элемента, в зависимости от его типа, ме-тод select ionAttr соответствующего класса возвращает значение "checked" или

Page 90: Java Server Pages

Базовый набор классов для работы с формами 91

"selected". Если элемент не выбран, возвращается пустая строка. Коды трех указанныхклассов приведены ниже.

// Переменная emptyString объявлена в классе// StringElement как protected.

public class RadioElement extends StringElementimplements SelectableElament {

public String selectionAttr(String value) {return getValue().equals{value) ? "checked" : emptyString;

}

I

public class CheckboxElement extends StringerrayElement {public String selectionAttr(String s) (

return contains(s) ? "checked" : amptyString;

public class OptionsElement extends StringArrayElement {public String selectionAttr(String s) (

return contains(s) ? "selected" : emptyString;

}

JПо умолчанию данные, поддерживаемые всеми классами пакета beans . html, счи-

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

Проверка введенных данныхВ JSP-документе, показанном на рис. 3.8, проверяется правильность ввода имени и

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

При наличии базового набора классов добавление средств проверки не составляетбольшого труда. Сначала надо указать в качестве значения атрибута act ion формыJSP-документ, выполняющий проверку.

// Так должен выглядеть дескриптор form для документа,// представленного в листинге 3.7,а.

<form act ion='val idate . j sp 1 >

Далее следует создать документ v a l i d a t e , j sp, код которого содержится в лис-тинге 3.8,а.

Page 91: Java Server Pages

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

l '

NVM: I™"

Credd Cird:

r vii

1" maste» Call

f discovery

ЕирМплО»!fttyW *

ода

O-VtD •

ЕБ/C-D *

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

Г Him r j m T Pari Г jip

Commims

iSitM i Fnft

The form was not filled out correctly because

Ntma caw.fll contj.n

С radii Cditf must Ьв li

Cndil Card:

r matter card

r dtaccvary

EKpirsiiu^Data.

owo_

ow

Г UntJ Г J « rpdTJip

CDmmenls

j

5 F * C a Prun

oppl» il

SutifrJUXmy |

Рис. З.8. Проверка данных формы

ЛистингЗ.в.а. /validate.jsp

<jsp:useBean id-' form' class='beans.Form' scope='request'><jsp:setProperty name='form' property^'•'/>

</jsp:useBean>

<% String errorMsg = "";boolean errorDetected «• false;

if(Jform.validate!)) (errorMsg += form.getValidationError();errorDetected = true;

}

if(errorDetected) { %><font colors'red' size='5'>The form was not filled out correctly because:<p></fontxfont size='3'><%= errorMsg %></fontx/p><jsp:include page='form.jsp' flush='true'/>

<% ) else { %><jsp:include page='registrationComplete.jsp' fluah='true'/>

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

Page 92: Java Server Pages

Базовый набор классов для работы с формами 93

Поскольку документ va l idate . 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 credi t = new CreditElement()private NameElement name = new NameElement();private String error;

public boolean validate () 1error = "";

if(Iname.validate(}) {error += name.getValidationError [) ;

}

if(!credit.validate()) {if(error.length!) > 0)

error += "<br>";

error += credit.getValidationError(};

)

return error == "";}public String getValidationError() {

return error;

Компонент bean, приведенный в листинге 3.8,6, похож на компонент, код которо-го содержится в листинге 3.7,6. Отличие состоит в том, что вместо объектовTextElement и CheckboxElement используются экземпляры классов NameElementи CreditElement. Кроме того, в компоненте реализован метод va l ida te , выпол-няющий проверку имени и типа платежной карты. Если данные формы не выдержи-вают проверку, создается сообщение об ошибке.

Классы NameElement и CreditElement представляют собой подклассы классовTextElement и CheckboxElement. В них переопределены методы, объявленные в ин-терфейсе ValidatedElement. Код класса CreditElement показан влистинге 3.8,в.

Page 93: Java Server Pages

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

1else (

forfint i=0; i < value.length (); ++i) {.char с •» value. charAt (i) ;

if (c == v ' Illc > "0' st с < '9')M

valid = false;if[c — ' M

error = "Name cannot contain spaces";

Page 94: Java Server Pages

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

elseerror = "Name cannot contain digits";

return valid;)public String getValidationError(J [

return error;

Если поле ввода имени заполнено и не содержит пробелов или цифр, методNameElement. v a l i d a t e возвращает значение t r u e .

Как видите, реализация проверки на базе имеющихся классов — простая задача.К компоненту bean добавляются новые методы, а элементы классов расширяются и вних переопределяются методы v a l i d a t i o n и g e t V a l i d a t i o n E r r o r .

СоветПовторное использование кода для поддержки формРассмотренный базовый набор классов существенно упрощает задачу поддержкиформ в JSP-документах. В данном случае не имеет значения, используете вы пред-ложенный набор или реализуете собственные классы, важно понимать общиепринципы, лежащие в основе используемых средств.Главным преимуществом применения базового набора классов является возмож-ность повторного использования кода. Эта цель может быть достигнута и другимиспособами, например, необходимые функции могут быть инкапсулированы в со-ставе пользовательских дескрипторов.

Применение пользовательских дескрипторовНаверное, вы согласитесь с тем, что наличие расширяемых HTML-дескрипторов уп-

ростило бы задачу создания Web-страипц. Например, было бы удобно, если бы дескрип-тор s e l e c t можно было расширить для создания списка, состоящего из гипертексто-вых ссылок. В HTML не предусмотрены средства создания новых дескрипторов па базеимеющихся, по это можно сделать, реализуя пользовательские дескрипторы JSP. На-пример, описанный выше вариант дескриптора s e l e c t рассматривался в главе 2,

В данном разделе речь пойдет о пользовательском дескрипторе, расширяющемHTML-дескриптор form. В частности, мы реализуем в нем поддержку дополнительногоатрибута focus, позволяющего при загрузке формы передать фокус ввода указанномуэлементу. Пример использования данного дескриптора приведен в листинге 3.9,а.

Page 95: Java Server Pages

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

Листинг 3.9,а. Пример применения пользовательского дескриптора form

<%@ tagl ib ur i=' form.t ld r prefix='html' %>

<%-- При загрузке формы поле редактирования nameполучает фокус ввода --%>

<html:form name='myForm' focus='name' method='post'><%-- Остальные элементы формы были представлены

в листинге 3.7,а —%>

</htrrd: f orm>

Форма, создаваемая дескриптором, приведенным в листинге 3.9,а, почти иден-тична форме, показанной на рис. 3.8. Отличие состоит лишь в том, что сразу послезагрузки формы фокус ввода получает поле редактирования с именем name. В данномлистинге элементы отсутствуют, частично они приведены в листинге 3.7,а.

Класс поддержки дескриптора form представлен в листинге 3.9,6.

Листинг 3.9,6. /WEB-INF/classes/tags/FormTag.Java

package tags;

import javax.servlet.jsp.*;import javax.servlet.jsp.tagext.*;

public class FormTag extends BodyTagSupport (public String method, name, focus;

public void setName(String name} { this.name •* name; }public void setMethodfString method} ( this.method = method; )public void setFocus(String focus) { this.focus «* focus; }

public int doEndTag() throws JspException [try {

String body = bodyContent.getString();bodyContent.clearBody();

StringBuffer buffer = new StringBuffer("<form name='" + name +

pl1 method^'" + method + "

1>"

+ body + "\n</form>\n" +"<script language='JavaScript'>\n" +"document." + name + "." + focus + ".focus[)" +"\n</script>");

bodyContent.print(buffer.toString ());bodyContent.writeOut(pageContext.getOut());

(catch(Java.io.IOException ex) (

throw new JspException(ex.getMessage [)};Ireturn EVAL__PAGE;

Page 96: Java Server Pages

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

В данном разделе детали создания пользовательских дескрипторов не обсуждают-ся. Подробно этот вопрос был рассмотрен в первых двух главах данной книги. Здесьже лишь приводится пример использования дескрипторов для расширения HTML-элементов.

Класс поддержки, показанный в листинге 3.9,6, генерирует HTML-дескрипторform и передает ему в неизменном виде атрибуты method И name. Заметьте, что про-чие атрибуты, в частности, такой важный атрибут, как a c t i o n , пользовательский де-скриптор не поддерживает. Добавить их несложно; дескриптор должен обрабатыватьих так же, как и атрибуты method и name.

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

РезюмеВ данной главе описывались средства поддержки HTML-форм. Эти средства соот-

ветствуют двум образам разработки: Memento, согласно которому состояние формыхранится в компонентах bean, и facade, определяющему принцип инкапсуляциифункций и упрощения доступа к повторно используемому код)'. Эти образы разработ-ки были использованы при создании базового набора классов, который вы можетерасширять, реализуя новые функциональные возможности.

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

• Apache Struts:

http://Jakarta.apache.org/struts/index.html

• БиблиoтeкaJSP-дecкpИLlтopoвIN16:

http://sourceforge.net/projects/jsptags/

• Библиотека Form:

http://cupid.saninternet.com/~joeo/Form.html

Page 97: Java Server Pages

ШАБЛОНЫ

В этой главе...

• Инкапсуляция алгоритмов компоновки.• Необязательное содержимое.

0г • Содержимое, зависящее от роли.• Раздельное определение областей.

• • Вложенные области.• Расширение областей.• Объединение различных подходов к созданию облает• Реализация дескрипторов поддержки областей.

Page 98: Java Server Pages

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

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

При реализации компонентов, контейнеров и диспетчеров компоновки обычноиспользуют образы разработки Composite и Strategy"'. Образ разработки Compositeприменяется для создания компонентов и контейнеров; при этом оговаривается, чтоконтейнер является компонентом. Таким образом, вы можете поместить в контейнерлюбой компонент, даже если компонент сам является контейнером. Благодаря этомувы можете реализовывать любые уровни вложенности контейнеров.

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

В JSP отсутствуют аналоги компонентов, контейнеров и диспетчеров компоновки.Однако B J S P есть возможность определять пользовательские дескрипторы и включатьWeb-компонснты, а это позволяет рсализовывать и компоненты, и контейнеры, и дис-петчеры компоновки. (Web-KOMitoHCHTaMH считаются сервлеты, HTML-файлы и JSP-документы. Термин Web-компонент никак не связан с термином компонент, используе-мым в этой главе.) В данной главе будут рассмотрены вопросы создания расширяемыхWeb-приложений, простых в сопровождении и допускающих повторное использование.

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

1См. Geary, Graphic Java Volume 1:AWT, Prentice Halt, 1998. - П р и м . авт.

2См. Gamma, Helms,Johnson, Vtissides. Design Patterns, Addhon-Wesky, 1994. -Прим. авт.

Page 99: Java Server Pages

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

• Раздел (section) — объект, который представляет HTML или JSP в контексте до-кумента.

• Область (region) — объект, содержащий разделы.

• Шаблон (template) — JSP-документ, который определяет размещение областей иразделов.

Области и шаблоны соответствуют контейнерам и диспетчерам компоновки. Раз-делы похожи на компоненты, поскольку они представляют некоторое содержимое(HTML-файл или JSP-документ) и отличаются от компонентов тем, что они не под-держивают события.

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

Подход, используемый в данной главе, соответствует образу J2EE Composite View ипозволяет составлять единый просмотр из нескольких частных просмотров, приме-няя компоненты bean и пользовательские дескрипторы. Дополнительную информа-цию о шаблонах можно найти в работе Элара (Alur), Крапи (Crupi) и Малкса (Malks)CoreJ2EE Patterns, опубликованной Prentice Hall и Sun Microsystems Press.

Инкапсуляция алгоритмов компоновкиПоскольку в процессе разработки расположение элементов неоднократно изменя-

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

Большинство Web-страниц состоит из нескольких разделов. Например, страница,показанная на рис. 4.1, содержит заголовок, колонтитул, оглавление в левой части иосновное содержимое.

Для размещения элементов Web-страницы может быть использован дескрипторt a b l e . Код конкретного JSP-документа приведен в листинге 4.1.

Листинг 4.1. Включение данных в состав документа

<htmlxhead><title>A JSP Page Without Templates</title>

</head>

<body background^'graphics/blueAndWhiteBackground.gif'>

<table><tr valign='top'>

<td><jsp:include page='sidebar.jsp' flush='true'/>

<7td><td>

Page 100: Java Server Pages

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

<table><tr>

<td><jsp:include page='header.jsp' flush='true'/>

</td></tr><tr><td>

<jsp:include page='introduction.jsp' flush='true'/></td>

</tr><tr>

<td><jsp: include page=' footer.. jsp' flush^' true' />

</td></tr>

</table></td>

</tr></table>

</body></html>

Заго/товок

Оглавление

Основноесодержимое

Колонтитул

Hcn^l IrHM E «*»

3 <**

Г S Chapter 4. TarpatesJAVA

IntroducHonWindow Tuolkr& 1ypical> provide a tayDUI mechanism lhai poenions widgelg en a contain^(orfKsir:plf. AWT and Swing nave Ijyoijl manajtrs, wlnrns VisuilWori<5 Smilllslk has

Because layoul undeigoes шачу changes mar Ihe course of dmlopmenl ft impctiinttoencspsulate tiiai fund ions In у се liyout can bs mgdiOsd with minimal iimpaci in Ihe ratt at[he ipplicalion In fact, layoul managers are an вилтр\в of one of the tenets of abject-onenisd design encapsulate Hi) concepl Ihst «wiati which is also a tundamemal themefor many design pstltrns.

JSP doss Tint provids direct support fat ancipsutiting layout, to web pages with iderrtLC.il(ormals usually replicate layoul cotfe; Sne«amplg A,web Pa;ie Latnai aiiwra a web pageconuinmg socticr.s for a header, fooler, sidebar, a^d main concent

wthoulmoinvrtelIlestr>»wjierteiiv>b1 large websTfesidentical formats, eirch a mechanism is valuable bacauefThat mechfnitm is JSP templates

it localize Б changes to

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

Page 101: Java Server Pages

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=fregions' prefix^'region' %>

<region:render template=' /template.j sp'><region:put section^'title' content=<region:put section='header' content=<region:put section='sidebar'content^<region:put section^'content'content=

Templates' direct^'true'/>/header.jsp' />/sidebar.jsp' />/introduction.jsp' />

<region:put section^'footer' content='/footer.jsp' /></region:render>

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

Каждый раздел связан с шаблоном, который задается посредством атрибутаtemplate дескриптора region: render. Открывающий дескриптор region : renderсоздает область (region) и помещает ее в область видимости приложения. Подробновопросы создания области будут рассматриваться далее в этой главе.

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

При обработке закрывающего дескриптора region: render включается шаблон, за-данный ранее с помощью атрибута template. Код шаблона представлен в листинге 4.2,6.

Page 102: Java Server Pages

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

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

<$@ taglib uri-'regions' prefix^'region' %>

<htmlxhead><title><region:render section^'t i t le '/></tit le>

</head>

<body background5^' graphics /blueAndWhiteBackground.gif'>

<table><tr valign='top'>

<region:render section=f sidebar'/></td><td>

<table><tr>

<td><region:render section='header'/>

</td></tr><tr>

<td><region:render section^'content'/>

</td></tr><tr>

<td><region:render section^'footer'/>

</td></tr>

</table></td>

</tr></table>

</body></html>

Подобно всем шаблонам, шаблон, приведенный в листинге 4.2,6, использует дляпредстааления разделов дескриптор region: render. Этот дескриптор имеет доступ кобласти, включающей шаблон и сохраненной в области видимости приложения. Деск-риптор region: render получает имя содержимого, связанного с разделом, и включаетсоответствующий ресурс. (Следует заметить, что дескриптор region: render пред-ставляет как области, так и разделы.)

В дескрипторе region : render может быть задан атрибут d i r e c t . Если значениеэтого атрибута равно true, содержимое не включается в region: tender, а непо-средственно передается в поток, соответствующий предопределенной переменнойout. Например, в листинге 4.2,а содержимое дескриптора t i t l e ("Templates") ис-пользуется в качестве заголовка окна.

Page 103: Java Server Pages

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

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

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

Листинг 4.2,в. /header, jsp

<table>

•ctdximg src=' graphics/java.gif /></td>•ctdximg згс=' graphics/templates.gif /></td>

</tr></table><hr>

Поскольку код в файле /header. j sp является включаемым содержимым, нет необ-ходимости повторять его в каждом документе. Заметьте также, что в /header. j sp от-сутствуют привычные HTML-дескрипторы html, head, body и т.д. Эти дескрипторысодержатся в шаблоне. Код в файле /header . j sp прост и удобен для сопровождения.

В данном разделе вы ознакомились с пользовательскими дескрипторами r e -gion: render и region:put из библиотеки, предназначенной для создания облас-тей. Данная библиотека предоставляет и другие средства, например, поддержку не-обязательного содержимого и содержимого, определяемого ролями. Эти вопросы бу-дут рассмотрены в последующих разделах.

ЮТ

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

Необязательное содержимоеДанные, представляемые посредством шаблонов, необязательны, благодаря этому

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

Page 104: Java Server Pages

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

Specifying All Content:

SIDEBARHEADER

CONTENT

FOOTER

Omitting Content:

SIDEBARHEADER

FOOTER

Рис. 4.2. Необйзательное содержимое раздела

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

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

<I3 taglib uri='regions' prefix='region' %>

<table><tr>

<td valign='top'><font size='5'>Specifying All Content:</font><table cellspacing=

f20'>

<tr><td>

< % — Содерзнииов этой области задано — % ><region:render template='hscf.jsp'>

<region:put section='header'content='/header.jsp'/>

<region:put section^'sidebar'content='/sidebar.jsp'/>

<region:put section='content'content='/content.j sp' />

<region:put section='footer'content='/footer,jsp' />

</region:render></td>

</table></td>

<td valign='top'><font size='5'>0mitting Content:</font><table cellspacing='20

f>

<tr><td>

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

Page 105: Java Server Pages

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

<region:render template™'hscf,jsp'><region:put section-'header'

content™'/header,jsp'/>

<region:put section^'sidebar'content™'/sidebar.j sp' />

<region:put section='footer'content='/footer.j sp'/>

</region:render></td>

</tr></table></td>

</tr></table>

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

<font size™'5'>SIDEBAR</font>

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

<font size='5f>HEADER</font>

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

Шаблон для областей в листинге 4.3,а содержится в файле /hscf. j sp и представ-лен в листинге 4.3,6.

Листинг 4.3,6. /hscf. js P : шаблон, используемый областями в листинге 4.3,а

<htmlxhead>

<%@ taglib uri='regions' prefix™'region' %>

</head>

<table border-'1' width='500'><tr> < % — Область, расположенная слева —%>

<td valign™'top' width='25%'><region:render section='sidebar'/>

</td><td valign™'top' align='center' width='*'>

<table height™'300'><tr> < % — Заголовок — % >

<td align='center' height^'20%'><region:render section='header'/>

</td></tr> < % — Основное содержимое — % >

<td align='center' height='*'><region:render section='content'/>

Page 106: Java Server Pages

Содержимое, зависящее от роли 107

</td></tr> <i— Колонтитул —%>

<td align='center' height='15%'><region:render section='footer'/>

</td></tr>

</table></td>

</tr></table>

</bodyx/html>

Как и шаблон, показанный в листинге 4.2,6, шаблон, код которого представлен влистинге 4.3,6, использует для размещения разделов дескриптор table .

Для того чтобы разделить две части документа, в шаблоне, представленном в лис-тинге 4.3,6, указано обрамление таблицы. Как правило, в шаблонах отображаютсятолько разделы.

Содержимое, зависящее от ролиЧасто в Web-приложениях информация отображается в зависимости от роли

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

e Jtanrinjfnndt и м E d »

£14* Ыи 'iHw Fjf«riirt Ic

8*d

с; ь™^ HUUI H-V e<*n - J Q J J U

The Antique House

curator

P*tc. 4.3. Содержимое, зависящее от роли

Page 107: Java Server Pages

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

Шаблон, на базе которого созданы документы на рис. 4.3, имеет следующий вид:

<table>

<tci><region:render section='editPanel' role='curator'/></td>

</table>• * ш

Дескриптор region:render представляет содержимое раздела только в том слу-чае, если роль пользователя соответствует значению атрибута role. Атрибут roleможно не указывать, в этом случае содержимое раздела (если оно присутствует) ото-бражается.

В предыдущем примере шаблон задает отображение раздела edi tPanel в случае,если значение роли пользователя равно curator. Это ограничение распространяет-ся на все области, использующие данный шаблон. В некоторых случаях бывает необ-ходимо задать содержимое, зависящее от роли, для конкретных областей. Сделать этоможно, задавая атрибут role в дескрипторе region: put. Этот атрибут применяетсяследующим образом:

<region:render template='hscf. jsp ' >

<region:put section='header' content='/header. jsp 'role-'curator '/>

</region:render>

В данном примере раздел header будет добавлен к области, создаваемой посред-ством дескриптора region: render, только в том случае, если значение роли пользо-вателя равно curator.

Раздельное определение областейДо сих пор определение и отображение всех областей задавалось в одном файле.

Рассмотрим, например, JSP-документ, показанный на рис. 4.4.

С1 №р: //bcjhw аЕШАчсп/лЗКЛ - Мимо» ! Я Н Е «few

lh Bill i l™

SIDEBARHEADER

CONTENT

FOOTER

S] Done

Рис. 4.4. Определение областей

Page 108: Java Server Pages

Раздельное определение областей 109

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

Листинг 4.4. Определение и представление области

<%@ tagl ib ur i='regions ' prefix='region' %>

<table>

<td><%-- Содержимое области задано s составе элемента —%><region:render teraplate='hscf,j sp'>

<region:put section='header' content" '/header. j sp '/><region:put section='sidebar ' content^ '/s idebar. j sp '/><region:put section= f content' content^ '/content . j sp '/><region:put section='footer ' content- '/ footer . j sp '/>

</region:render></td>

</tr></table>

Определение и представление области может быть разделено. Например, в лис-тинге 4.5 приведен JSP-документ, показанный на рис. 4.4, в котором представляетсяуже существующая область.

Листинг 4.5,а. JSP-документ, который использует существующую область

<%@ tagl ib ur i='regions ' prefix^'region' %>

<%@ include f i le='/regionDefini t ions. j sp ' %>

<region:render region^'SIDEBAR_REGION'/>

В документе, код которого представлен в листинге 4.5,а, используется областьSIDEBAR_REGION, определенная в файле /regionDef i n i t i o n s . j sp (листинг 4.5,6).

Листинг 4.5,6. /regionDefinitions. j sp : определение области

<%? taglib uri='regions' prefix='region' %>

<region: define id=' SIDEBAR_REGlOlsr scope=' application'template='hscf.jsp' >

<region:put section='header' content='/header.jsp'/><region:put section^'sidebar' content='/sidebar.jsp'/><region:put section^'content' content='/content.jsp'/><region:put section='footer' content='/footer.jsp'/>

</region:define>

Page 109: Java Server Pages

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

Для определения области с именем SIDEBAR_REGION, предназначенной для хра-нения в области видимости приложения, в листинге 4.5,6 используется дескрипторregion:define. Имя области и область видимости задаются посредством атрибутовid и scope. В дескрипторе region : define обязательно должен присутствовать ат-рибут template, который определяет шаблон, используемый областью.

Подобно region: render, дескриптор region: define может содержать деск-рипторы region:put, которые включают имена и содержимое разделов в область,созданную с помощью открывающего дескриптора region: define.

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

Вложенные областиПоскольку разделы и области реализуются в соответствии с образом разработки

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

БЬ &» Ю" Ъятк

iSIDEBAR HEADER

LEFT

TOP

BOTTOM

FOOTER

RIGHT

Рис. 4.5. Вложенные области

JSP-документ, показанный на рис. 4.5, идентичен JSP-документу, код которого пре-дставлен в листинге 4.5,а. Этот JSP представляет существующую область SIDEBAR_REGION, которая определена в листинге 4.5,в.

Page 110: Java Server Pages

Вложенные области 111

Листинг 4.5,В. /regionDefinitions. jsp

<%@ taglib uri='regions' prefix^'region' %>

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

<region:put section^'header' content='/header.jsp'/><region:put section^'sidebar' content='/sidebar.jsp'/><region:put section='content' content='BORDER_REGION'/><region:put sect ion=' footer ' c o n t e n t = ' / f o o t e r . j s p ' / >

</region:define>

<region:define id='BORDER_REGION' scope=' a p p l i c a t i o n 't e m p l a t e ^ ' t l b r . j s p ' >

<region:put sec t ion=' top ' content= F / top. j sp ' /><region:put s e c t i o n = ' l e f t ' c o n t e n t = ' / l e f t . j s p ' / ><region:put s e c t i o n = ' r i g h t ' c o n t e n t = ' / r i g h t . j s p ' / ><region:put section='bottom' content='/bottom, j sp '/>

</region:define>

В области SIDEBAR_REGION в качестве содержимого раздела content задана об-ласть B0RDER_REGION. Таким образом, BORDER_REGION является вложенной в об-ласть SIDEBAR_REGION.

Обратите внимание, что область BORDER_REGION не обязательно определять прежде,чем она будет включена в состав SIDEBAR_REGION. Дело в том, что SIDEBAR_REGION lieобращается к BORDER_REGION до начала отображения SI DEBAR_REGION.

Чтобы завершить данный пример, рассмотрим шаблон, приведенный в листинге4.5,г; он используется BORDER_REGION.

Листинг 4.5,г. Шаблон, используемый BORDER^REGION

<html><head><%@ taglib uri='regions' prefix^'region' %>

</head>

<table border='2' width='500'><tr> <%— Строка Top —%>

<td colspan='2' align='center' height^'50'><region:render section^'top'/>

</td></tr>

<tr> < % — Строки Left и Right — % ><td aligns'center' width='50%' height-'150'>

<region:render section='left'/></td><td align='center' width='50%' height='150'>

<region:render section^'right'/></td>

</tr>

<tr> < % — Строка Bottom --%><td colspan='2' align='center' height='50'>

<region:render section^'bottom'/>

Page 111: Java Server Pages

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

</td></tr>

</table>

</bodyx/html>

Шаблон, приведенный в листинге 4.5,г, представляет разделы top, l e f t , r i ght иbottom, определенные в области BORDER_REGION. Имя BORDER_REGION выбрано поаналогии с диспетчером компоновки AWT BorderLayout, компоненты которого рас-полагаются аналогичным образом.

Расширение областейБольшие возможности при работе с областями предоставляет определение облас-

ти как расширения другой области. Например, JSP-документ, показанный на рис. 4.6,воспроизводит область, которая является расширением области SIDEBAR_REGION,неоднократно встречавшейся в этой главе.

Код JSP-документа, показанного на рис. 4.6, приведен в листинге 4.6,а.Подобно JSP-документу, представленному в листинге 4.5,а, документ в листинге

4.6,а включает JSP-файл, содержащий определение области. Затем в документе вос-производится область EXTENDED_SIDEBAR_REGION.

i:1iiip/AK<hHtMn/'4iarBMn-iinnidiltp-NlniigltMvniF<to>f UOljElPI» »ll lit* Fjwlttt Iwd t*t> •

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

SiDEBAROVERRIDDEN HEADER

CONTENT

OVERRIDDEN FOOTER

о

::•

J

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

Листинг 4.6, а. Расширение существующей области

taglib uri='regions' prefix='region' %><%Q include file-'/regionDefinitions-override.jsp' %>

<region:render region='EXTENDED_SIDEBAR_REGIOK'/>

Page 112: Java Server Pages

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

JSP-код, в котором определены области SIDEBAR_REGION и EXTENDED_SIDEBARREGION, приведен в листинге 4.6,6.

Листинг 4.6,6. /regionDefinitions-override.

<$@ taglib uri='regions' prefix^'region' l>

<region:define id='SIDEBAR_REGION' scope-' application'tempiate='hscf.jsp'>

<region:put section^'header' content"'/header.j sp'/><region:put section^-' sidebar' content"

5' /sidebar, jsp' />

<region:put section^'content' content='/content.jsp'/><region:put section

1*'footer' content='/footer,jsp'/>

</region:define>

<region:define id='EXTENDED_SIDEBAR_REGION' scope=' application'region-'SIDEBAR_REGION'>

<region:put section-'header ' content=' /overridden-header.jsp'/><region:put sect ion- ' footer ' content=' /overridden-footer. jsp'/>

</region:define>

Как правило, при определении областей указывается шаблон, как в случае с обла-стью 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

<*@ taglib uri='regions' prefix='regions' %><%@ include file='/regionDefinitions-override.jsp' %>

«region:render region='EXTENDED_SlDEBAR_REG!ON'/>

Page 113: Java Server Pages

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

£j hUp7A>caliaifc8G8Q/regiora/ - MBIDUH Internet Expfcnr ._. 1 D| _xj

Ella £dir y iw F^^rltes Took |jv^

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

TOP

LEFT RIGHT

BOTTOM

#] Done

OVERRIDDEN HEADER

OVERRIDDEN TOP

L E F T Direct Сnnrent

OVERRIDDEN BOTTOM

HEADER

CONTENT

FOOTER

FOOTER

TsLoodf*«l

|

1

Рис. 4.7. Использование расширений и вложенных областей

Содержимое включенного JSP-файла приведено в листинге 4.7,6.

Листинг4.7,6. /regionDefinitions-override.isp

taglib uri='regions1 prefix-'region

1 %>

<region:define id=1SIDEBAR^REGION' scope-'request'

template-'hscf.jsp'><region:put section-'header' content='/header.jsp'/><region:put section-'sidebar' content='EXTENDED_BORDER_REGION'/><region:put section-

1content' content='/content.jsp

1/>

<region:put section-'footer' content=1/footer.jsp'/>

</region:define>

<region:define id='BORDER_REGION' scope=Trequest'

template^'tlbr.j sp'><region:put section^

1top

1 content='/top.jsp'/>

<region:put section=1bottom

1 content='/bottom.jsp'/>

<region:put section^1left' content

=1/left.jsp'/>

<region:put section^'right1 content

=l/right.jsp

1/>

</region:define>

<region:define id='EXTENDED_SIDEBAR_REGION' scope-1request'

region='SIDEBAR_REGION'><region:put section='header' content-'/overridden-header.jsp'/><region:put section-'sidebar'content='BORDER_REGION

T/>

<region:put section-1content'content='SIDEBAR_REGIOM'/>

</region:define>

<region:define id-'EXTEHDED_BORDER_REGION' scope-'request'region-'BORDER_REGION'>

<region:put section^1top

1 content='/overridden-top.jsp'/>

Page 114: Java Server Pages

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

<region:put sect ion^'bottom' content='/overridden-bottom.jsp '/><region:put s e c t i o n = ' r i g h t * >

<font s ize='4 '>Direct Content</font></region:put>

</region:define>

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

Область EXTENDED_SIDEBAR_REGION отображается при просмотре документа,показанного на рис. 4.7. Данная область расширяет область SIDEBAR_REGION и пере-определяет разделы header, s idebar и content. В разделах s idebar и contentEXTENDED_SIDEBAR_REGION содержатся соответственно области BORDER_REGION ИSIDEBAR_REGION.

В области SIDEBAR_REGION в качестве содержимого раздела s idebar использует-ся область EXTENDED_BORDER__REGION. Эта область расширяет BORDER_REGION и пе-реопределяет разделы top, bottom и r ight . Заметьте, что содержимое разделаright EXTENDED_BORDER_REGION определяется в теле дескриптора reg ion:put .Такая возможность подробно обсуждается далее в этой главе.

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

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

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

рипторов, предназначенных для поддержки областей, перечислены в табл. 4.1. Всеэти компоненты принадлежат пакету beans . regions.

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

Компонент Описание

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

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

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

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

Page 115: Java Server Pages

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

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

Content

*Contern[)

•toSlrinaO

• абстрактный газе с. в которомлен единственный абстрактный метод

String

\

#comeni /

L

j

Section

^I&n[j9rQ

^loStringO

\Region

•guSectiomO

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

sections Has hi able(Turn krtlfj

Рис. 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.Serializableprotected final String content, direct;

// Воспроизведение содержимого в JSP-документеabstract void render(PageContext pc) throws JspException;

public Content(String content) {this(content, "false");

1public Content(String content, String direct) {

this.content » content;this.direct = direct;

Page 116: Java Server Pages

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

public String getContent () {return content;

}

public String getDirectO (return direct;

)public boolean isDirectO {

return Boolean.valueOf(direct).booleanValue[);)public String toStringO (

return "Content: " + content,-J

Абстрактный класс Content поддерживает два свойства: content и d i r e c t .Свойство content представляет содержимое, воспроизводимое посредством методаrender, а свойство d i r e c t указывает, как содержимое должно воспроизводиться. Ес-ли значение d i r e c t равно true, содержимое отображается непосредственно, а если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;

Ipublic String getNameO {

return name;)

public void render(PageContext pageContext)throws JspException (

if {content ! = null) (

Page 117: Java Server Pages

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

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

Page 118: Java Server Pages

Реализация дескрипторов поддержки областей 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);Jpublic 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);

Jcatch(Exception ex) ( // lOException или ServletException

throw new JspException(ex.getMessage());)

Ipublic String toStringO {

String s = "Region: " + content.toString() + "<br/>";int indent = 4;Enumeration e - sections.elements();

while(e.hasMoreElements()) {Section section = (Section)e.nextElement();for(int i=0; i < indent; ++i) {

s += "&nbsp;";

>s += section.toStringO + "<br/>";

Ireturn s;

>I

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

Page 119: Java Server Pages

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.

Page 120: Java Server Pages

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

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

Компонент Описание

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

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

RenderTag

PutTag

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

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

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

Region(Ваш гц1яи

>0•get Sections!)•tenderf)

(fregion

RegionDefinrtionTag

•setScopeQ•doStartTagg

* d E f T OgetScop

•released

9findftegionByKeyOi*creale RegionF tomTeivtfilataO^ c r e ale Regi о nFro mRe gion 0

•pvtO

•set Se

BodyTagSuppnrt

•SodyTagSupportQ•doAflenSodyO•doEndTagO•

gO•geLBodyConlentO•geLPreviousOutO•leleasaQ•setBodyContentO

ъ

PuiTag

•selSeclionQ

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

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

Page 121: Java Server Pages

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

Для создания областей предназначены дескрипторы region; render и region:

define, классами поддержки которых соответственно являются RenderTag и

RegionDef initionTag. Функции по созданию областей инкапсулированы в классе

RegionTag, который представляет собой суперкласс RenderTag и RegionDefini-

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

Page 122: Java Server Pages

Реализация дескрипторов поддержки областей 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);

return EVAL_BODY_INCLUDE;Jpublic int doEndTag() throws JspException (

pageContext. setAttribute (id, region, getScope {))•;return EVAL PAGE,-

Page 123: Java Server Pages

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

protected int getScope() {int constant = PageContext.FAGE_SCOPE;

scope - (scope — null) ? "page" : scope;

if("page".equalsIgnoreCase(scope))constant = PageContext.PAGE_SCOPE;

else if("request".equalsIgnoreCase(scope))constant = PageContext.REQUESTJ3COPE;

else if{"session",equalsIgnoreCase(scope))constant = PageContext.SESSION_SCQPE;

else if("application".equalsIgnoreCase(scope)constant • PageContext.APPLICATION_SCOPE;

return constant;}public void released {

super.release() ;scope • "page";

Класс RegionDefinitionTag является подклассом RegionTag и создает областьлибо на базе шаблона, либо на базе другой области. Заметьте, что если одновременнозаданы атрибуты region и template, то при выполнении метода RegionDefini-tionTag. 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 {

Page 124: Java Server Pages

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

return sectionName != null;

public in t doStartTag() throws JspException {HttpServletRequest request = (HttpServletRequest)

pageContext.getRequest();

i f ( r o l e !«• null &£ [request. istJserlnRole (role) )return SKIP_BODY;

if(renderingRegion() ) (if(!findRegionByKey()} [

createRegionFromTempiate[);)Regionstack.push(pageContext, region);

}return EVAL_BODY_INCLUDE;

}public in t doEndTag() throws JspException (

Region region = RegionStack.peek(pageContext);

if(region == null)throw new JspException("Can't find region");

if(renderingSection(}) fSection section = region, get(sectionName);

i f (sect ion == null)return EVAL_PAGE; // пропущенные разделы игнорируются

section.render(pageContext);)e lse if(renderingRegion()) (

try <region.render(pageContext);RegionStack.pop(pageContext);

Icatch (Exception ex) { // IOException или ServletException

throw new JspException(ex.getMessage());}

Ireturn EVAL_PAGE;

Ipublic void released {

super.release[);sectionName •= role = null;

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

Код класса PutTag представлен в листинге 4.9,г.

Page 125: Java Server Pages

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 =

Page 126: Java Server Pages

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

"If content is specified in the tag body, the " +"direct attr ibute must be true." ;

boolean hasBody = hasBodyО;boolean contentSpecifled = (content != nul l) ;

if([hasBody f iS contentspecified} | |UhasBody S& IcontentSpecified))throw new JspException(bodyAndContentMismatchError);

if(hasBody is direct != null ssdirect.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 = null; // переменная не может иметь имя classtry <

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 можетиметь следующий вид:

<region:render template='/WEB-INF/jsp/templates/hscf.jsp'><region:put section= f t i t l e ' >

The Fruitstand</region:put>

</region:render>

Если тело дескриптора region:put присутствует, оно предстаиляет собой содер-жимое, которое должно непосредственно отображаться. Если тело дескриптора зада-но, класс PutTag проверяет атрибут d i rect , чтобы убедиться, что его значение рав-но true.

Page 127: Java Server Pages

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

РезюмеКомпоненты, контейнеры и диспетчеры компоновки позволяют следовать при

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

Page 128: Java Server Pages

РАЗРАБОТКАWEB-ПРИЛОЖЕНИЙ

В этой главе...

Model 1.Model 2: архитектура MVC.Пример использования архитектуры Model 2.- Дескриптор доставки.

- Успешная регистрация.

- Создание новой учетной записи.

Page 129: Java Server Pages

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

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

Авторы спецификации JSP обеспечили достаточную гибкость описываемых в нейсредств. Вы можете создать Web-приложение на базе JSP различными способами. Ос-новные способы реализации Web-проектов описаны ниже.

• Создание документа как набора HTML-дескрипторов и JSP-скриптлетов,

• Делегирование функций компонентам bean.

• Использование сервлетов, JSP-документов и компонентов bean для реализацииархитектуры "модель-просмотр-контроллер" (MVC — Model-View-Controller).

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

При делегировании функций компонентам bean Java-код перемещается из JSP-доку-ментов в компоненты, поэтому приложения, созданные по такому принципу, становят-ся более жизнеспособными. Этот подход, известный как архитектура Model 1, был ре-комендован в версии 0.91 спецификации JSР. (Скопировать версии 0.91 и 0.92 специфи-кации JSP можно, обратившись по адресу h t t p : //www. k i rkdor f fe r , com/j spspecs.)

Последний подход — объединение сервлетов, JSP и beans в рамках архитектурыMVC— позволяет создавать расширяемые программы, удобные для сопровождения.При этом инкапсулируются функции и уменьшаются побочные эффекты при внесе-нии изменений. Этот подход также был рекомендован в версии 0.91 спецификацииJSP и носит название архитектуры Model 2.

В начале данной главы мы кратко рассмотрим архитектуру Model 1, а затем перейдемк подробному обсуждению архитектуры Model 2. В главе б Model 2 будет расширенасредствами, упрощающими реализацию приложений в рамках данной архитектуры.

Page 130: Java Server Pages

132 Глава 5. Разработка Web-приложений

Model 1Архитектура Model 1, условно показанная на рис. 5.1, включает JSP-документы,

компоненты bean и бизнес-объекты.

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

Ответ

JSP- документДоступ/модификаций

bean

Бизнес-о&ьаюы

Р и с . 5 . 1 . Архитектура Model 1: JSP-документ, компоненты bean и бизнес-объекты

Согласно данной архитектуре, запросы передаются JSP-документам, которые взаи-модействуют с бизнес-объектами посредством компонентов bean. Такое косвенное об-ращение уменьшает зависимость JSP-документов от изменений, вносимых в бизнес-объекты. Это важно, поскольку в больших проектах модификация бизнес-объектовпроизводится достаточно часто. Если интерфейс, реализуемый компонентами bean, ос-тается без изменений, изменение бизнес-объектов не влияет HaJSP-документы.

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

Поскольку обеспечить разделение обязанностей между авторами Web-страниц иразработчиками программного обеспечения в рамках архитектуры Model 1 трудно,данный подход применим только для небольших проектов с участием несколькихразработчиков, каждый из которых владеет Java, JSP, HTML и XML.

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

СоветПреимущества и недостатки Model 1Архитектура Model 1 уменьшает зависимость JSP-документов от бизнес-объектов, по-зволяя параллельно вести работы над созданием бизнесюбъектов и Web-страниц.Однако в рамках данной архитектуры разработчики программного обеспеченияпомимо создания бизнес-объектов оказываются вовлеченными в процесс созданияJSP-документов. При работе над большими проектами это недопустимо.

Page 131: Java Server Pages

Model 2: архитектура MVC 133

Model 2: архитектура MVCПодобно Model 1, Model 2 позволяет разделить бизнес-объекты и JSP-документы,

что очень важно для большинства проектов, где бизнес-объекты претерпевают по-стоянные изменения. Кроме того, архитектура Mode! 2 (рис. 5.2) отделяет генерациюсодержимого от его представления.

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

Ответ

1

Серела7[генерирует содержимое)

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

Создание/модификация

[Bean (содержимое)]

Доступ

JSP-документ|(представляет содержимое)!

Бизнес-объекты

... .-

I [ Реализуется разработчиками программ

Рис. 5.2. Model 2: архитектура MVC

Реализуется авторами Web-страниц

Согласно архитектуре Model 2, запросы передаются сервлету, который обращает-ся к бизнес-объектам и создает содержимое. Это содержимое сохраняется в компо-ненте bean, к которому имеет доступ JSP-документ. Документ представляет содержи-мое, применяя для этого, как правило, средства HTML.

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

Model 2 представляет собой архитектуру MVC, где бизнес-объекты соответствуютмодели (предоставляют данные), сервлет является контроллером {обрабатывает за-просы), aJSP-документы играют роль просмотра. Архитектура MVC появилась "на за-ре" вычислительной техники и была реализована в Smalltalk. Благодаря разделениюбизнес-логики и средств представления, что позволяет легко заменять компоненты иобеспечивать создание гибкого программного обеспечения, эта технология выдер-жала испытание временем.

На самом деле Model 2 представляет собой модифицированную архитектуру MVC,так как модель не генерирует события, предназначенные для обработки просмотром.Такой подход выбран потому, что в Web-приложениях обычно не используется не-сколько просмотров одновременно.

Page 132: Java Server Pages

134 Глава 5. Разработка Web-приложений

СоветПреимущества МУСМного лет назад MVC стала базой, на которой создавались приложения Smalltalk.Одним из основных принципов объектно-ориентированной разработки являетсявыделение понятий и инкапсуляция их в классах. Например, приложение, предна^значенное для расчета заработной платы, оперирует с такими понятиями, как со-трудники, оклады и т.д. Инкапсуляция этих понятий в классах позволяет распре-делять функции между объектами и снижать зависимость объектов друг от друга.Благодаря такому подходу увеличивается гибкость приложений и появляется воз-можность повторного использования их компонентов.В архитектуре MVC инкапсулируются три основных понятия, присутствующих вбольшинстве графических приложений: модели, просмотры и контроллеры. Посравнению с другими приложениями, где эти понятия не выделяются, MVC-программы отличаются большей гибкостью.

Пример использования архитектуры Model 2В данном разделе описывается одна из реализаций знакомого всем Web-приложе-

ния, предназначенного для регистрации пользователей. Это приложение соответст-вует архитектуре Model 2. Оно, как показано на рис. 5.3, состоит из шести JSP-документов, одного пользовательского дескриптора, двух компонентов bean и двухсервлетов— один из сервлетов предназначен для регистрации, а второй— для созда-ния новых учетных записей. Кроме того, в состав приложения входят дескриптордоставки (/WEB-INF/web. xral), который определяет отображение сервлета, и файлописания библиотеки пользовательских дескрипторов (/WEB-INF/t Ids / u t i l i -t i e s , t ld) . В файле описания библиотеки дескрипторов определен единственныйпользовательский дескриптор, применяемый в данном приложении.

Компоненты beanПриложение, предназначенное для регистрации пользователей, содержит два

компонента bean. Один из них представляет пользователей, а другой — базу данныхучетных записей. Код класса User показан в листинге 5.1,а.

ЛИСТИНГ 5.1 ,а. /WEB-INF/classes/beans/User . Java

package beans;

// Информация о пользователе хранится в неизменном виде

public class User implements java,io.Serializable (private final String userName, password, hint;

public User(String userName, String password. String hint} (this.userName <• userName;this.password = password;

Page 133: Java Server Pages

Пример использования архитектуры Model 2 135

this.hint = hint;}public String getUserName[) {public String getPassword() {public String getHinto {

return userName; }return password; }return hint; }

public boolean equals(String uname, String pwd)return getUserName().equals(uname) ь&

getPassword{).equals(pwd);

IJSP Java | XML Файл описания библиотеки дескрипторов

Рис. 5.3. Структура каталогов и файлы, используемые при создании приложения

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

Кроме того, в классе User реализован метод equals, который возвращает t rue вслучае, если пароль, указанный при вызове метода, соответствует паролю, заданномупри создании объекта.

Информация о пользователе хранится в неизменном виде; значения свойств могутбыть установлены только при выполнении конструктора User. Это является естест-венной защитой при доступе из различных потоков. Если объект не может изменять-

Page 134: Java Server Pages

136 Глава 5. Разработка Web-приложений

ся при выполнении программы, автоматически обеспечивается его целостность. Таккак при объявлении свойств использован модификатор f inal , свойства не могут из-меняться после инициализации.

Код класса, представляющего учетную запись пользователя, приведен в листин-ге 5.1,6.

Листинг 5.1 ,б. /WEB-lKF/classes/beans/LoginDB.java

package beans;

import java.util.Iterator;import java.util.Vector;

public class LoginDB implements Java.io.Serializable {private Vector users • new Vector!);

public void addUser(String uname, String pwd, String hint} {users.add(new User(uname, pwd, hint));

)public User getUser(String uname, String pwd} {

Iterator it = users.iterator();User bean;

synchronized(users} {while(it.hasNext()) {

bean = (User)it.next();

if(bean.equals{uname, pwd))return bean;

return null;}public String getHint(String uname) (

Iterator it = users.iterator ();User bean;

synchronized(users) fwhile (it.hasNextd } {

bean = (User)it.next();

if(bean.getUserName().equals(uname))return bean.getHint ();

return null;

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

Page 135: Java Server Pages

Пример использования архитектуры Model 2 137

Для обеспечения многопотокового выполнения синхронизируются фрагментыкода. Класс Java, u t i l .Vector синхронизирован, поэтому необходимость в синхро-низации метода LoginDB.addUser не возникает. Однако итераторы не синхронизи-рованы, и если данные изменятся при просмотре набора, будет сгенерировано ис-ключение.

Дескриптор доставкиВ состав Web-приложения входит дескриптор доставки, который определяет кон-

фигурацию приложения и содержит следующую информацию,

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

• Конфигурация сеанса.

• Определение сервлетов/JSP.

• Отображение сервлетов/JSP.

• Отображение МШЕ-типов.

• Страница приветствия.

• Страницы, отображаемые при возникновении ошибок.

• Средства защиты.

Дескрипторы доставки хранятся в каталоге /WEB-INF приложения в файле с име-нем web.xml. Дополнительную информацию о дескрипторах доставки вы найдете вспецификации сервлетов, которую можно скопировать, обратившись по адресуhttp://java.s-un.com/products/servlet. Дескриптор доставки для рассматри-ваемого приложения представлен влистинге 5.1,в.

Листинг5.1,в.

version="1.0" encoding="ISO-8859-l"?>

<!DOCTYPE web-appPUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN""http://java.sun.com/j2ee/dtds/web-app_2.2.cttd">

<web-app><servlet>

<servlet-name>login</servlet-name><servlet-class>LoginServlet</servlet-class>

</servlet>

<servlet><servlet-name>new-account</servlet-name><servlet-class>MewAccou.ntServlet</servlet-class>

</servlet>

<servlet-mapping><servlet-name>new-account</servlet-name><url-pattern>/new-account</url-pattern>

Page 136: Java Server Pages

138 Глава 5. Разработка Web-приложений

</servlet-mapping>

<servlet-mapping><servlet-name>login</servlet-name><url-pattern>/login</url-pattern>

</servlet-mapping>

<taglib><tagl ib-ur i>ut i l i t ies</tagl ib-ur i><taglib-location>/WEB-INF/tlds/utilities.tld</taglib-location>

</taglib></web-app>

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

<£orm action='<%= response.encodeURL("login") %>'><form action='<%= response.encodeURL("new-account") %>'>

В дескрипторе доставки задан UR1 описания библиотеки дескрипторов. Благодаряэтому к описанию можно обращаться следующим образом:

<%@ tagl ib u r i - ' u t i l i t i e s ' p r e f i x - ' u t i l ' %>

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

СоветСценарии развития событийСценарии развития событий были впервые описаны в книге Ливера Джекобсена(Ivarjacobsen) Object-Oriented Software Engineering и по сути представляют собой фор-мализацию требований к программе. Сценарий развития событий — это набор си-туаций, которые могут возникать при выполнении текущей задачи пользователя,например при регистрации на Web-узле.Несмотря на то что сценарии развития широко используются при описании тре-бований к программному обеспечению, лучше всего они подходят для разработкиWeb-приложений, поскольку HTTP-запрос соответствует одному сценарию разви-тия событий.

Успешная регистрацияСценарий успешной регистрации пользователя предполагает следующие действия.

• Пользователь указывает в поляxJSP-документа имя и пароль.

Page 137: Java Server Pages

Пример использования архитектуры Model 2 139

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

• Если в базе данных существует запись для этого пользователя, запрос перена-правляется странице приветствия.

Информация в окне броузера, соответствующая успешной регистрации, показанана рис. 5.4.

•• IIГП-J' 11

P» E« »

Please

Name:

Password:

| B - if"! I*iw F i n » I*ot. HUp В«!<ю№»:а|»Ше»!|чидд1/ид:п№ ^J <?0o

Login

( M

: Eta Е Л ^ F ^

i J^^T__ l^ P Посели

Welcome |oe

,!*t. leai Udp

|№90ив»д1Ло?п1Л|1О'» ^

{gloialrtini

_ |D| K||

РИС. 5.4. Успешная регистрация

На рис. 5.5 представлена последовательность событий, соответствующая успеш-ной регистрации.

^rЕсли »ЭйпчСь о пользоватм*ч SIJSH

Конго не нт Ust г ( J H .

нта welcome j5p

fiyi глаг

« n

ir- usir)

forwtni

" D

РИС. 5.5. Последовательность событий, соответствующаяуспешной регистрации

Страница, содержащая регистрационную форму, отображается в окне, показан-ном на рис. 5.4, слева. Когда данные формы передаются на сервер, вызывается серв-лет регистрации. Этот сервлет проверяет, соответствует ли пользовательское имя ипароль данным, содержащимся в базе. При положительном результате проверкисервлет помещает объект User в область видимости сеанса и перенаправляет запросстранице приветствия, которая извлекает имя пользователя и создает приветствен-ное сообщение.

Page 138: Java Server Pages

140 Глава 5. Разработка Web-приложений

JSP-код страницы приветствия показан в листинге 5.1,г.

Листинг 5.1,г. /login, j

<html><headxtitle>Login Page</titlex/head><body>

<l@ include file='login-form.jsp' %></body></html>

Поскольку регистрационная форма используется двумя другими JSP-документами вданном приложении (листинги 5.1,з и 5.1,л), она реализована в отдельном JSP-файле,который включается в файл login. j sp.

Код, реализующий регистрационную форму, показан в листинге 5.1,д.

Листинг 5.1,д. /login-form.j

<%@ taglib u r i = ' u t i l i t i e s ' pref ix='ut i l ' %>

<font size=' 5' color='blue'>Please Login</fontxhr>

<form action^'<%= response.encodeURL("login") %>' method='post'><table>

<tr><td>Name:</td><tdxinput type='text ' name='userName'

value='<util:requestParameter property='userName'/>'></td>

</trx t r><td>Password:</td><tdxinput type='password' name='password' size='S'></td>

</tr></table><br><input type='submit' value='login'>

</form>

Поскольку файл login-form, j sp предназначен для включения в другие файлы, внем не содержатся такие дескрипторы, как html, head и body; эти дескрипторы на-ходятся в составе включающего JSP-документа.

Для обработки запроса применяется сервлет регистрации; в дескрипторе доставкиэтому сервлету присвоено имя login. На случай, если работа с cookie запрещена илиподдержка сеанса не используется, имя кодируется посредством метода HttpServ-letResponse.encodeURL.

Единственный пользовательский дескриптор применяется в данном приложениидля сохранения содержимого поля ввода имени при повторном отображении доку-мента. О реализации подобных дескрипторов см. в главе 1.

При передаче данных формы на сервер запрос направляется сервлету регистра-ции, код которого показан в листинге 5.1,е.

Page 139: Java Server Pages

Пример использования архитектуры Model 2 141

Листинг 5.1 ,е. /WEB-lNF/claeees/LoginServlat. java

import javax.servlet.ServletConfig;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServietRequest;import javax.servlet.http.HttpServletResponse;

import beans.LoginDB;import beans.User;

public clas3 LoginServlet extends HttpServlet {private LoginDB loginDB;

public void init(ServletConfig config)throws ServletException (

super.init(confIg);config.getServletContext().setAttribute("loginDB",

loginDB • new LoginDBO);}public void service(HttpServletRequest req,

HttpServletResponse res)throws java.io.iQException, ServletException I

User user = loginDB.getUser(req.getParameter("userName"),req.getParameter("password"});

iffuser !« null) { // данные о пользователе есть в Сазеreq.getSessiont).setAttribute("user", user);

getServietContext().getRequestDispatcher(res.encodeURL("/welcome.jэр")).forward(req,res);

}else { // необходимо создать новую учетную запись либо

// повторить попытку регистрацииgetServletContext().getRequestDispatcher(

res.encodeURL("/logiiiFeiled.jsp")).forward(req,res);

Сервлет регистрации выполняет две задачи: инициализирует базу данных и обра-

батывает запросы. Первая задача решается в методе init, который создает базу и со-

храняет ее в области видимости приложения.

Обработка запросов осуществляется посредством метода service. При этом

сервлет обращается к базе данных и проверяет, содержится ли в ней запись, соответ-

ствующая имени пользователя и паролю, введенным в форме. Если запись о пользова-

теле существует, объект User сохраняется в области видимости сеанса и управление

передается странице приветствия. Если пользователь не учтен в базе, вызывается

страница, соответствующая некорректной регистрации. Код этой страницы показан

влистинге 5.1,з.

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

одновременное обращение из нескольких потоков, в сервлете не осуществляется

Page 140: Java Server Pages

142 Глава 5. Разработка Web-приложений

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

Код страницы приветствия приведен в листинге 5.1,ж.

ЛИСТИНГ 5.1 ,Ж. /welcome. jsp

<html><head><title>Welcome</title></head><body>

<jsp:useBean id='user ' scope='session' class='beans.User• />Welcome <%= user.getUserName() %>

</body></html>

Для получения имени пользователя документ обращается к компоненту User, со-держащемуся в области видимости сеанса. Результаты показаны на рис. 5.2.

СоветАрхитектура Model 2 позволяет инкапсулировать Java-кодв сервлетахВ рамках архитектуры Model 2 Java-код инкапсулируется в сервлетах. В данномприложении практически весь Java-код сосредоточен в сервлетс, код которого по-;;казан в листинге 5.1 ,е. BJSP-файлах, представленных в листингах 5.1,г и 5.1 д Java-код отсутствует. Такой подход позволяет разработчикам программ заниматьсятолько созданием сервлетов, а авторам Web-страниц сосредоточить усилия на соз-дании JSP-Документов.Вы можете возразить, что страница приветствия в листинге 5.1,ж содержит вызовметода, с помощью которого определяется пользовательское имя, и будете правы.Если автор Web-страницы считает недопустимым присутствие даже такого фраг-мента кода, разработчик программного обеспечения может заменить его пользо-вательским дескриптором.

Создание новой учетной записи

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

1. Пользователь задает имя и пароль в форме, предоставляем о uJSP-до куме нтом.

2. Сервлет регистрации проверяет, содержится ли в базе запись для данногопользователя.

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

Page 141: Java Server Pages

Пример использования архитектуры Model 2 143

4. Если пользователь выбирает создание новой учетной записи, управление пере-дается с оответс твую ще му JS Р-д о куме нту.

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

Действия по созданию новой учетной записи показаны на рис. 5.6. Первой ото-бражается Web-страница регистрации (Login Page), находящаяся в левом верхнем уг-лу, последующие страницы {Login Failed, New Account и Account Created) расположе-ны на рисунке по часовой стрелке.

Э L o g t n :'•!•!• 1.1 :• :.!r I г. • i ; :•

Please Login

Name: (joe

Password: [""*

'•aba* &

BHDI

LuqiJi Imlird bAir.iitr.nh 1Ш«тп1-1 t nfilDrri

С

Login Failed

Please enter a valid use-пате and password,of create з new account

Please Login

Name: ||ое

Password: Г~

I «Си

An account has been crated for joe

Please Login

Name:

Password: I

Open a New Account

User Name:

Password:

Confirm Password;!

Рис. 5.6. Создание новой учетной записи (окна расположены по часовойстрелке, начиная с верхнего левого угла)

Page 142: Java Server Pages

144 Глава 5. Разработка Web-приложений

На рис. 5.7 показана диаграмма событий, соответствующая неудачной попытке регистрации и созданию новой учетной записи.

I Ш I I 1ШШ • j

1 аол \' MTWt*fl-1 ^ ^

Lfor»

L

J— niiJ]

inl i . . i>

tddU

1 l m l

f c ! „,u«rt

ihim - g r

l.hlnt)

jj

1HNvn*")

Г ^D

fbrwirtd

г

iРис. 5.7. Диаграмма событий для создания новой учетной записи

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

Если пользователь не учтен в базе, запрос перенаправляется документу, посредст-вом которого отображается Web-страница Login Failed. На этой странице находитсяссылка, которая указывает на JSP-документ, содержащий форму. В качестве атрибутаact ion данной формы указан сервлет, предназначенный для создания новой учетнойзаписи. Этот сервлет получает информацию, введенную в полях формы, создает за-пись для нового пользователя и добавляет запись в базу данных.

После создания новой учетной записи запрос перенаправляется JSP-документу Ac-count Created.

Код JSP-документа, соответствующего неудачной регистрации, приведен в листин-ге 5.1.з.

Листинг 5.1,з. /loginPailed. jep

<html><headxtitle>Login Failed</title></head>

<font color='red' size='5'>Login Failed</font><font color='red' 3ize='4'xp>

Please enter a valid username and password, or createa new account

</fontx/p>

Page 143: Java Server Pages

Пример использования архитектуры Model 2 145

include f i le=Vlogin-form. j sp 1 %>

<hr>Click <a href='<%= response.encodeURL("newAccount.jsp") %>' >here</a> to open a new account.

</body></html>

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

Код JSP-документа New Account, посредством которого создается новая учетнаязапись, приведен в листинге 5.1,и.

Листинг5.1,и. /n«wAcoount. jep

<htmlxheadxtitle>New Account</tit lex/head><body><font s i z e * ' 5 ' color='blue'>Open a New Rccount</font><hr><form action='<%= response.encodeURL("new-acoaunt") %>'

method*3' post' >

<table><tr><td> User Name: </td><tdxinput type='text' name=

1userName

tx/td>

</tr><tr><td> Password: </td><tdxinput type=' password

1 name=' password

1 size=' 8 •></td>

</trxtr><td> Confirm Password: </td><tdxinput type='password' name='confirm-password'

size='8'x/td></trxtr>

<td> Password Hint: </td>< t d x i n p u t type=' t e x t ' name=' password-hint' ></td>

</tr></table><br><input type='submit' value- 'create account'>

</form></body></html>

Документ New Account содержит форму с полями для ввода имени пользователя,пароля и подсказки. Атрибут act ion данной формы имеет значение new-account —имя, присвоенное в дескрипторе доставки сервлету, который предназначен для соз-дания новой учетной записи.

Код сервлета, создающего учетную запись, приведен в листинге 5.1 ,к.

Page 144: Java Server Pages

146 Глава 5. Разработка Web-приложений

Листинг 5.1 ,К. /WEB-IHF/classes/NewAccountServlet. Java

import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;

import beans.LoginDB;

public class NewAccountServlet extends HttpServlet (public void doPost(HttpServletRequest req,

HttpServletResponse res)throws java.io.lOException, EervletException {

LoginDB loginDB = (LoginDS)getServletContext().getAttribute("loginDB") ;

loginDB.addUser(req.getParameter("userName"),req.getParameter("password"),req.getParameter("password-hint"J);

getServletContext().getRequestDispatcher(res.encodeURL("/accountCreated.jsp")).forward(req, res);

Данный сервлет имеет доступ к базе данных и информации, введенной пользова-телем в форме. Этот сервлет создает в базе данных новую запись, используя для этогометод LoginDB, addUser, а затем перенаправляет запрос документу Account Created.

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

Код)5Р-документа Account Created приведен в листинге 5.1,л.

Листинг 5.1 ,Л. /accountcreated.jsp

<htmlxheadxtitle>Account Created</title></head><body>

<font size='4' color-'blue'>An account has been created for<%= request.getParameter("userName") %>

</font>

<p><%@ include file='login-form.jsp' %></p>

</body></html>

Документ Account Created отображает сообщение о том, что новая учетная записьсоздана, а также форму для регистрации пользователя.

Page 145: Java Server Pages

Пример использования архитектуры Model 2 147

РезюмеРеализовать Web-приложение на базе JSP можно различными способами. В данной

главе описаны два подхода, соответствующие архитектуре Model 1 и архитектуреModel 2. Архитектура Model 2 имеет ряд преимуществ перед Modei 1 и позволяет соз-давать гибкие, расширяемые приложения, удобные для сопровождения.

В следующей главе будут рассмотрены средства, упрощающие создание приложе-ний, соответствующих архитектуре Model 2.

Page 146: Java Server Pages

БАЗОВЫЙ НАБОРКЛАССОВ ДЛЯ

СОЗДАНИЯПРИЛОЖЕНИЙ

MODEL 2

В этой главе...

• Базовый набор классов Model 2.- Интерфейс Action.

- Фабрика действий.- Маршрутизатор действий.- Сервлет действий.

• Модернизация программ.• Учет новых сценариев развития.

• Применение пользовательских дескрипторов.• JSP-сценарии.

Page 147: Java Server Pages

Model 2 представляет собой реализацию архитектуры "модель-просмотр-кон-троллер", позволяет обеспечить независимую работу авторов Web-страниц иразработчиков программного обеспечения, поэтому очень хорошо подходит

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

В данной главе описан простой набор классов, соответствующий Model 2. При раз-работке данного набора использовались те же базовые понятия, что и в Apache Struts(информацию о продукте Struts можно получить на узле h t t p : //www. apache ,org).

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

Базовый набор классов Model 2В базовом наборе классов, рассматриваемом в данной главе, используется единст-

венный сервлет, который исполняет роль контроллера. Этот сервлет называетсясервлетом действий (action servlet). Все HTTP-запросы, оканчивающиеся на .do, об-рабатываются сервлетом действий. Как показано на рис. 6.1, сервлет действий пере-дает эти запросы компонентам bean, которые называются действиями (action).

Компоненты-действия обновляют бизнес-объекты и возвращают сервлету дейст-вий маршрутизатор действий {action router). Сервлет действий использует маршрути-затор для перенаправления запросов JSP-документу. JSP-документ также обращается кбизнес-объектам (часто такие обращения осуществляются с помощью пользователь-ских дескрипторов) и возвращает броузеру ответ на запрос.

Page 148: Java Server Pages

150 Глава 6. Базовый набор классов для создания приложений Model 2

Броузер

1. Запрос

' •Сервлет действий(контроллер)

5. Перенаправление(toward или send Redirect)

7. Ответ

1

2. Передача запроса

L

4. Возвр

г

JSP-документ(просмотр)

ащ

IX

{ Компон

г"хЗ. Обновление

ентЛ ^"V действие )

ается маршрутизаторействий

в. Доступ

С~~ ~~1й

Бизнес-объекты(модель)

Реализуется разработчиками программ Реализуется авторами Web-страниц

Рис. 6.1. Базовый набор классов Model 2

В базовом наборе классов (см. рис. 6.1) определены четыре типа объектов, пере-численных в табл. 6.1.

Таблица 6.1. Классы и интерфейс, входящие в состав базового набора(интерфейс выделен курсивом)

Имя Описание

Action

ActionFactory

ActionServlet

ActionRouter

Данный интерфейс реализуется действиями, специфическимидля приложения

Создание экземпляров действий

Отображение запросов в действия

Перенаправлениезапросов^Р-документам

На рис. 6.2 показано взаимодействие объектов этих типов.Сервлет действия обычно вызывается из JSP-документа или другого сервлета. Этот

сервлет обращается к фабрике действий и в зависимости от запроса получает соот-ветствующий тип действия.

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

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

Page 149: Java Server Pages

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

Hashtabls Class Atfiari

getAciionfl ;n Action sciion = get(classname)

[action ***• mill]actjon - newinsl

put(cla&sname. action)

ActionRoutar router = perfbrm(iht3. req. res)

req, res]

Маршрутизатор nзапрос елURL которогоэээвмплярэ мэршпосредством иет<

Действие соэ,случае, еслиОдин эд&ыписпольэуатсз

толыл в томоно не существует.пяр действияi есеми эйпросамнхиеги типа.

ренэпра&лпе' [•ly We Ь - KOHODW акту,|к при создании

^ругиэатора.э Action, perform.

Рис. 6.2. Диаграмма взаимодействия для ActionServlet

После того как сернлет действий получает действие, он вызывает метод per formэтого действия. Метод perform реализует функциональные возможности, специфи-ческие для конкретного приложения (обычно он обращается к бизнес-объектам). Ме-тод Act i o n . perform возвращает маршрутизатор действий, который содержит URI ипеременную типа boolean, указывающую, какой метод должен использоваться дляпере неправления по данном)' URI: forward или s e n d R e d i r e c t .

Получив маршрутизатор, сервлет действий вызывает его метод r o u t e , который пе-ренаправляет запрос указанному Web-компоненту. Обычно этим Web-компонентом яв-ляется JSP-документ, HTML-страница или другой сервлет. Как правило, в составе Web-компонента находится форма или ссылка, при активизации которой формируется за-лрос. При получении запроса последовательность его обработки начинается сначала.

Далее в этом разделе мы рассмотрим классы, перечисленные в табл. 6.1, а затем об-судим возможность применения базового набора для создания приложения из главы 5.

Интерфейс ActionИнтерфейс Act ion приведен в листинге 6.1

та.

Листинг 6,1,a. /WEB-INF/classes/actions/Action. java

package actions;

import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;

// Данный интерфейс реализуется действиями,// специфическими для приложения

Page 150: Java Server Pages

152 Глава 6. Базовый набор классов для создания приложений Model 2

public interface Action (public ActionRouter perform[HttpServlet servlet,

HttpServletRequest req,HttpServletResponse res)

throws Java.io.IOException,javax.servlet.ServletException;

>

В интерфейсе Action объявлен единственный метод perform, которому при вы-зове передаются ссылка на сервлет действий, а также объекты, представляющийHTTP-запрос и ответ.

Фабрика действийКод класса ActionFactory представлен в листинге 6.1,6.

Листинг 6.1,6. /WEB-XNF/classes/actions/ActionFactory. Java

package actions;

import Java.util.Hashtable;

public class ActionFactory (

private Hashtable actions = new Hashtable{);

II Этот метод вызывается сервлетом действий.

public Action getAction[String classname,ClassLoader loader)throws ClassNotFoundException,

IllegalAccessException,InstantiationException {

Action action •• (Action)actions.get(classname);

if(action == null) {Class klass •= loader.loadClass(classname);action • (Action)klass.newInstance();actions.put(classname, action);

return action;

Фабрика действий содержит хэш-таблицу действий и метод getAction, который

вызывается сервлетом действий. Этот метод возвращает действие, соответствующее

заданному имени класса. Если действие отсутствует в хэш-таблице, фабрика создает

новое действие и помещает его в таблицу. При последующих запросах, предполагаю-

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

Page 151: Java Server Pages

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

Маршрутизатор действийКод класса Act ionRouter показан в листинге 6.1,в.

ЛИСТИНГ6.1,В. /WSB-INF/clasaes/actions/ActionRouter. Java

package actions;

import javax.servlet.GenericServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet-http.HttpServletResponse;

// Маршрутизаторы действий сохраняются в неизменном виде.

public class ActionRouter {private final String url;private final boolean isForward;

public ActionRouter(String url) {this(url, truej; //По умолчанию forward

)public ActionRouter(String url, boolean isForward) (

this.url = url;

this.isForward = isForward;

}

// Этот метод вызывается сервлетом действий.

public void route(GenericServlet servlet,HttpServletRequest req,HttpServletResponse res)

throws javax.servlet.ServletException,Java.io.lOException {

if(isForward) {servlet.getServletContext()-getRequestDispatcher(res.encodeORL(url)).forward(req, res);

}else {

res.sendRedirect(res.encodeRedirectURL(url));

Маршрутизатор действий перенаправляет запрос, используя для этого либо метод

forward, либо метод sendRedirect. Конкретный способ обработки запроса опреде-

ляется при выполнении конструктора класса.

Сервлет действийДескриптор доставки отображает URL, заканчивающиеся символами ".do", в

сервлет действий.

Page 152: Java Server Pages

154 Глава 6. Базовый набор классов для создания приложений Model 2

// Фрагмент /WEB-INF/web.xml

<web-app><servlet>

<servlet-name>action</servlet-name><servlet-class>ActionServlet</servlet-class>

</servlet>

<servlet-mapping><servlet-name>ac:tion</servlet-name><url-pattern>*,do</url-pattern>

</servlet-mapping>

</web-app>

Суффикс ". do" запомнить просто, поскольку он ассоциируется с действиями. Пе-

ред суффиксом ".do" указывается имя действия. Например, для действия регистра-

ции, которое реализуется посредством класса с именем LoginAction из пакета

actions, URL задается следующим образом:

<form action='<%= response.encodeURM"actions-LoginAction.do") %>'

method='post'>

Рассмотрим, как сервлет действий, представленный в листинге 6,1,г, отображает

URL в класс действия.

Листинг 6.1,г. /WEB-iNF/classes/ActionServlet. java

import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;

import actions.Action;import actions.ActionFactory;import actions.ActionRouter;

public class ActionServlet extends HttpServlet {

private ActionFactory factory = new ActionFactory ();

public void service(HttpServletReque3t req,HttpServletResponse res)

throws Java.io.IOException,javax.servlet.ServletException {

try <Action action = factory.getAction(getClassname(req),

getClass().getClassLoader());ActionRouter router - action.perform[this,req,res);router.route(this, req, res);

}catch(Exception e) {

throw new ServletException(e);

Page 153: Java Server Pages

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

private String getСlassname(HttpServletRequest req) (String path = req.getServletPath ();int slash = path.lastlndexOf("/"),

period = path.lastlndexOf(".");

if[period > 0 &s period > slash)path = path.substring(slash+1, period);

return path;

Реализация метода A c t i o n S e r v l e t . s e r v i c e не составляет труда. Для получениядействия сервлет обращается к фабрике действий и вызывает метод perform действия.Метод perform возвращает маршрутизатор действий, который перенаправляет запрос.

Метод Act ionServ le t .getClassname отвечает за отображение URL в имя классадействия. Этот метод получает ссьшку на путь к сералету и выделяет строку между косойчертой и точкой. Для URL действия регистрации, о котором шла речь выше, путь к серв-лету имеет вид / a c t i o n s . LoginAction. do, а имя класса — a c t i o n s . LoginAction.

Модификация приложения, предназначенногодля регистрации

В предыдущей главе мы рассмотрели пример приложения, предназначенного длярегистрации пользователей. Это приложение было создано в соответствии с архитек-турой Mode] 2. Сейчас мы модифицируем данный пример и используем в нем базовыйнабор классов. На рис. 6.3 показана структура каталогов и файлы модифицированногопримера. Та же информация для исходного примера была приведена на рис. 5.3.

(jSloginFailed

login-form

newAccount

welcome

Iogin2 '. Web-inf-'

H £13 classes - -actions

l i beans- - . .tags-

•u

ActionRoutel

LoginAction

NewAccountActio

•4J GetRequestParameterTag

^ J S P -V] Java j ^ j XML | * ] Файл описания библиотеки дескрипторов

Рис. е.З. Структура каталогов и файлы, использованные при создании приложения

Page 154: Java Server Pages

156 Глава 6. Базовый набор классов для создания приложений Model 2

Если сервлет действий находится в каталоге /WEB-INF/classes, а классы, под-держивающие действия,— в каталоге /WEB-INF/classes/act ions/, то в исходноеприложение надо внести лишь два изменения. Во-первых, в файлах l o g i n , j sp иnewAccount. j sp надо изменить URI сервлетов. В исходных JSP-файлах строки, со-держащие URI, выглядят следующим образом:

<%-- Фрагмент исходного файла l o g i n . j s p --%><form action='<%= response.encodeURL("login") %>' m e t h o d = f p o s t ' >

<%-- Фрагмент исходного файла newAccount.jsp --%><form action='<%= response.encodeURL("new-account") %>'

method^ 'pos t '>

Модифицированные URI имеют вид

<%— Фрагмент нового файла l o g i n . j s p —%><form action^'<%= response.encodeURL("actions.LoginAction.do") %>'

method='pos t '>

<%— Фрагмент нового файла newAccount.jsp —%><formaction='<%= response.encodeURL("actions.NewAccountAction.do") %>'method^'post' >

Во-вторых, сервлеты, предназначенные для регистрации и создания новой учет-

ной записи, должны быть переписаны как действия. Эти действия представлены в

листингах 6.2,а и 6.2,6.

ЛИСТИНГ 6.2,a. /WEB-INF/classes/actions/LoginAction.Java

package actions;

import javax.servlet.*;import javax.servlet.http.+;import beans.LoginDB;import beans.User;

public class LoginAction implements Action (public ActionRouter perform[HttpServlet servlet,

HttpServletRequest req, HttpServletRespon.se res)throws Java.io.IOException, ServletException {

LoginDB loginDB = getLoginDB(servlet.getServletContext()J;User user « loginDB.getUser(req.getParameter("userName"},

req.getParameter("password")};

if (user \= null) f // информация о пользователе// содержится в базе

req.getSession[).setAttribute("user", user);return new ActionRouter("/welcome.jsp");

ielse

return new ActionRouter("/loginFailed.jsp");)private LoginDB getLoginDB(ServletContext context) (

LoginDB loginDB = (LoginDB)context.getAttribute("loginDB");

Page 155: Java Server Pages

Модернизация программ 157

ifdoginDB == null)context.setAttribute("loginDB", loginDB = new LoginDB(J);

return loginDB;

Листинг 6.2,6. /WEB-INF/classes/actions/NewAecountAction . Java

package actions;

import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServietResponse;

import beans.LoginDB;import beans.User;

public class WewAccountAction implements Action [public ActionRouter perform(HttpServlet servlet,

HttpServletRequest req,HttpServletResponse res)

throws Java.io.lOException,javax.servlet.ServletException{

LoginDB loginDB - (LoginDB)servlet.getServletContext {).getAttribute("loginDB");

String uname = req.getParameter("userName");

loginDB.addUser(uname, req.getParameter("password"),req.getParameter("password-hint"});

req.setAttribute("userName", uname);

return new ActionRouter("/accountCreated.jsp");

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

отличие отсервлетов, они возвращают маршрутизаторы действий.

Модернизация программКак и любой программный проект, рассмотренный пример оставляет разработчи-

ку возможность для модернизации. Так, например, в JSP-докумеитах используется не-посредственное обращение к действиям, это иллюстрирует дескриптор form, нахо-дящийся в файле login-form.jsp:

<form action='<%= response.encodeURL("actions.LoginAction.do") %>'method='post' >

Page 156: Java Server Pages

158 Глава 6. Базовый набор классов для создания приложений Model 2

Действия, в свою очередь, как это видно из следующего фрагмента файла Login-Action . j ava, непосредственно обращаются JSP-документам:

i f(user != null) { II информация о пользователе// содержится в базе данных

гeq.getSession().setAttr ibute("user", user);return new ActionRouter("/welcome.jsp");

Jelse

return new AetionRouter("/loginFailed.jsp");

Такая взаимосвязь между JSP-документами и действиями нежелательна, посколькуизменение имени JSP-файла приводит к изменениям в составе действия, а изменениеимени файла, реализующего действие, или даже перемещение его в другой пакет при-водит к изменениям в JSP-документе.

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

<form action='<%= response.encodeURL("login-action") %>'method='post'>

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

i f [user ! = null) { // информация о пользователе// содержится в Сазе данных

req.getSession[).setAttr ibute("user", user);return new ActionRouter("welcome-page");

}else

return new ActionRouter("login-failed-page");

Для отображения логических имен в классы действий и JSP-документы могут ис-пользоваться наборы ресурсов (механизм наборов ресурсов будет рассматриваться вглаве 8). Для этого надо в первую очередь реализовать файл свойств, показанный влистинге 6.3,а, и поместить его в каталог /WEB-INF/classes.

ЛИСТИНГ 6.3,a. /WEB-INF/classes/actions .properties

# Отображение действий, используемое ActionServlet

login-action=actions.LoginActionnew-account-action=actions.NewAccountAction

# Отображение JSP, используемое маршрутизаторами

login-failed-page^/loginFailed.jspwelcome-page=/welcome.j spaccount-created-page=/accountCreated.jsp

Затем надо добавить к сервлету действий метод i n i t , в теле которого создаетсянабор ресурсов, определенный в файле свойств. Набор ресурсов сохраняется в облас-ти видимости приложения и доступен как сервлету действий, так и маршрутизаторудействий. Код сервлета действий показан в листинге 6.3,6.

Page 157: Java Server Pages

Модернизация программ 159

ЛИСТИНГ G.3,6. /WEB-INF/classes/ActionServlet.Java

import java.util.MissingResourceException;import Java.util.ResourceBundle;

import javax.servlet.ServletConfig;import javax,servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;

import actions.Action;import actions.ActionFactory;import actions.ActionRouter;

public class ActionServlet extends HttpServlet (private ActionFactory factory = new ActionFactory();

public void init(ServletConfig config) throws ServletExceptionisuper.init(config} ;

ResourceBundle bundle = null;

try Ibundle = ResourceBundle.getBundle("actions");

»catch(MissingResourceException e) {

throw new ServletException(e) ;)getServletContext().setAttribute("action-mappings", bundle);

)public void service(HttpServletRequest req,

HttpServletResponse res)throws Java.io.lOException, ServletException {

try {String actionClass = getActionClass(req);Action action = factory.getAction(actionClass,

getClass().getClassLoader());ActionRouter router • action.perform(this,req,res);router.route(this, req, res);

}catch(Exception e) {

throw new ServletException(e);)

)private String getClassname(HttpServletRequest req) {

String path = req.getServletPath[);int slash = path.lastlndexOf("/"),

period = path.lastlndexOf(".");

if(period > 0 S& period > slash)path = path.substring(slash+1, period);

return path;1

Page 158: Java Server Pages

160 Глава 6. Базовый набор классов для создания приложений Model 2

private String getActionClass(HttpServletRequest req) {ResourceBundle bundle = (ResourceBundle)getServletContext[).

getAttribute("action-mappings");return (String)bundle.getObject(getActionKey [req});

>private String getActionKey(HttpServletRequest req) (

String path - req.getServletPathO;int slash « path.lastlndexOf("/"),

period » path. lastlndexQf ("."),-

if(period > 0 6& period > slash)path = path.substring(slash+1, period);

return path;

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

Класс ActionRouter, код которого приведен в листинге 6.3,в, также модифици-рован с тем, чтобы выполнять отображение логических имен в JSP-документы.

ЛИСТИНГ 6.3,В. /WEB-INF/classes/actions/Ac tionRouter . Java

package actions;

import java.util.ResourceBundle;import javax.servlet.GenericServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;

// Маршрутизаторы действий не подвергаются изменениям

public class ActionRouter {private final String key;private final boolean isForward;

public ActionRouter(String key) {

this(key, true); // по умолчанию forward

}

public ActionRouter(String key, boolean isForward)this.key = key;this.isForward - isForward;

// Данный метод вызывается сервлетом действий.

public synchronized void route(GenericServlet servlet,HttpServletRequest req,HttpServletResponse res)

throws Java.io.lOException,javax.servlet.ServletException (

ResourceBundle bundle = (ResourceBundle)servlet.

Page 159: Java Server Pages

Модернизация программ 161

getServletContext().getAttribute("action-mappings");

String url - (String)bundle.getObject(key);

if(isForward) (servlet.getServletContext().getRequestDispatcher(res.encodeURL(url)).forward(req, res);

}

else {res.sendRedirect(res.encodeRedirectURL(url));

I

Если сервлет действий и маршрутизаторы действий выполняют отображение ло-гических имен, действия и JSP-документы могут использовать эти имена. Например, влистингах 6.3,г и б.З.д представлены соответственно коды класса NewAccountActionHjSP-документа login-form, j sp.

Листинг 6.3,r, /WEB-iNF/classes/actions/NewAccountAction. Java

public class NewAccountAction implements Action {public ActionRouter perform(HttpServlet servlet,

HttpServletRequest req, HttpServletResponse res)throws Java.io.IQException, ServletException (

LoginDB loginDB = (LoginDB)servlet.getServletContext [) .getAttribute("loginDB");

String uname = req.getParameter("userName");

loginDB.addUseс(uname, req.getParameter("password"),req.getParameter("password-hint"));

req.setAttribute("userName", uname);

return new ActionRouter("aooount-created-page");

Листинг 6,3,д. /login-form.jsp

<*@ taglib uri-'util ities' prefix-'util' %>

<font size='5' color='blue'>Please Login</fontxhr>

<form action='<%= response.encodeURL("login-action.do") %>'method='post'>

</form>

Page 160: Java Server Pages

162 Глава 6. Базовый набор классов для создания приложений Model 2

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

<web-app><servlet>

<servlet-name>action</servlet-name><servlet-class>ActionServlet</servlet-class>

<init-param><param-naiue>actioii-mappings</param-name><param-valvie>actions</param-value>

< / init-param></servlet>

</web-app>

Для использования инициализационных параметров соответствующий фрагмент

кода сервлета действий должен выглядеть так:

public class ActionServlet extends HttpServlet {

public void init (ServletConfig config) throws ServletException(super.init(config) ;

ResourceBundle bundle = null;

try {bundle = ResourceBundle.getBundle [

config.getlnitParameter("action-mappings"));)catch(MissingResourceException e) (

throw new ServletException(e);

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

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

Учет новых сценариев развитияКачество базового набора класса можно оценить по тому, насколько просто могут

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

Page 161: Java Server Pages

Учет новых сценариев развития 163

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

1. Реализация действия, которое обращается к бизнес-объектам (модель) и, воз-можно, сохраняет компоненты bean в соответствующей области видимости дляпоследующего отображения в JSP-документе (просмотр). Эту задачу решаютразработчики программного обеспечения.

2. Реализация JSP-документа, обращающегося к бизнес-объектам {которые мог-ли быть модифицированы на предыдущем этане) и к компонентам bean, сохра-ненным в некоторой области видимости (компоненты также создаются на пре-дыдущем этапе). Эту работу выполняют авторы Web-страниц.

3. Связывание с логическими именами действий и JSP, созданных на этапе 1 и 2.Данную задачу решают авторы Web-страниц и разработчики программногообеспечения.

Обратите внимание на разделение обязанностей по разработке действий и JSP-документов между программистами и Web-дизайперами.

Для некоторых сценариев развития событий могут потребоваться дополнитель-ные меры, например, модификация существующих действий (разработчики про-грамм), JSP-докумеитов (авторы Web-страниц) или реализация пользовательских де-скрипторов (разработчики программ). Однако во многих случаях достаточно выпол-нить действия, перечисленные выше.

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

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

2. Сервлет действий перенаправляет запрос странице, соответствующей неудач-ной регистрации; на этой странице содержится ссылка на подсказку для вводапароля.

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

4. Действие извлекает из базы данных учетных записей подсказку для конкретно-го пользователя и сохраняет имя пользователя и подсказку в области видимо-сти запроса.

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

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

Page 162: Java Server Pages

164 Глава 6. Вазовый набор классов для создания приложений Model 2

£ik £dLI ]£iMr FavorltKl Jool

A,

Login Failed

irl iist?rnam& and password, w^aii? a new account

Please Login

Name jjoe

Password f^

tngln j

Click Ь*в \o see your password him.

з

*'•'PiiiwadHnl • MiandtImarvt£ц*кн

£.!• {dlt ii«* Fipodln I w j

, |Q | X

Hint forjoi is fly tfcg'i

Please Login

Name:

PasswonJ |

[agin I

PMC 6.4. Отображение подсказки для ввода пароля

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

Этап 1: реализация действия, связанногос подсказкой

Код действия ShowHintAction показан в листинге 6.4,а.

Листинг 6.4,3. /HEB-INF/classes/actiona/ShowHintAotion. Java

package actions;

import javax.servlet.ServletContext;import javax.servlet.http,HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;

import beans.LoginDB;

public class ShowHintAction implements Action {public ActionRouter perform(HttpServlet servlet,

HttpServletRequest req, HttpServletResponse res)throws IOException, ServletException {

LoginDB loginDB = getLoginDB(servlet.getServletContextString шпате = (String)req.getSession().

getAttribute("userName");

req.setAttribute("hint", loginDB.getHint(uname)};req.setAttribute("userName", uname);

return new ActionRouter("show-hint-page");

Page 163: Java Server Pages

Учет новых сценариев развития 165

private LoginDB getLoginDB(ServletContext context) (LoginDB loginDB = (LoginDB)context.getAttribute("loginDB");

iffloginDB — null)context.setAttribute("loginDB", loginDB = new LoginDBO);

return loginDB;

Действие, представленное в листинге 6.4,а, реализует п. 4 описанного выше сце-нария развития событий, т.е. сохраняет имя пользователя и пароль в области види-мости запроса. Конструктору маршрутизатора действия, возвращаемого методомperform, передается логическое имя show-hint-page. Это логическое имя соответ-ствует JSP-до куме нту подсказки и связано с ним в файле свойств приложения.

Этап 2: реализация JSP-документа,соответствующего подсказке

Код JSP-документа, соответствующего подсказке, приведен в листинге 6.4,6,

ЛИСТИНГ 6.4,6. /sbowHintAction.jSp

•chtmlxtitle>Password Hint</title><body>

Hint for <bx%= request.getAttribute ("userName") %></b> is<i><%= request.getAttribute("hint") %></i>

<pxfont size='5' color='blue'>Please Login</fontx/p><hr>

<form action-'<%= response.encodeURL("login-action.do") %>'method^'post' >

<table><tr>

<td>Name:</td><tdxinput type-'text' name='userName'

value='<%= request.getAttribute("userName") %>'></td>

</tr><tr><td>Password:</td><tdxinput type='password' name=' password' sizs=' 8' ></td></tr>

</table><br><input type='submit' value=' login'>

</form>

</body></html>

Page 164: Java Server Pages

166 Глава 6. Базовый набор классов для создания приложений Model 2

JSP-документ, показанный в листинге 6.4,6, реализует п. 5 сценария развития со-бытий, извлекая и отображая имя пользователя и подсказку.

С действием, соответствующим регистрации, связано логическое имя login-action. Оно, как и другие логические имена, задается в файле свойств приложения.

Этап 3: модификация файла свойствСодержимое файла свойств приложения показано в листинге 6.4,в.

ЛИСТИНГ 6.4,В. /WEB-INF/classes/actions.properties

# Отображение действий, используемое ActionServlet

login-action^actions.LoginActionnew-account-асtion=actions.NewAccountActionshow-hint—action=actions,ShowHintAetion

# Отображение JSP, используемое маршрутизаторами

login-failed-page=/loginFailed.jspwe1сome-page=/welcome.jspaccount-created-page=/accountCreated.jsp3how-hint-page=/3howHint.jsp

Этап 4: модификация документа,соответствующего неудачной регистрации

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

Листинг 6.4,Г. /loginFaiXed. jap

<htmlxheadxtit le>Login Failed</title></head><body>

<font color='red' size-'5'>Login Failed</font><font color='red' s ize='4'xp>

Please enter a valid username and password, or createa new account

</fontx/p>

<%@ include f i le- '/login-form.jsp' %>

<jsp:useBean id='loginDB' class='beans.LoginDB'scope='application' />

<%if(loginDB.getHint(request.getParameter("userName")) != null){%>Click <a href='<%=response.encodeURL("show-hint-action.do")%>'>

Page 165: Java Server Pages

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

here</a> to see your password h i n t .<% } else { %>

Click <a href=' <%=re5ponse.encodeURL("newAccount.jsp")%>'>here</a> to open a new account.

<\ } %>

</body></html>

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

Применение пользовательских дескрипторовОдно из основных преимуществ архитектуры Model 2 состоит в том, что Java-код

инкапсулируется в сервлетах. Это важно при работе над большими программнымипроектами; при этом Web-диэаннеры работают над кодом HTML- и JSP-документов, аразработчики программ обеспечивают функциональные возможности, используемыепри создании Wcb-страииц, Если JSF-документы свободны от Java-к ода, авторы Web-страниц и программисты могут параллельно выполнять поставленные перед ними за-дачи; их зависимость друг от друга минимальна.

Однако, даже при использовании архитектуры Model 2, небольшие фрагментыJava-кода часто "просачиваются" в JSP-докумснты. Например, страница, соответст-вующая неудачной попытке регистрации, код которой показан в листинге 6.4,г, со-держит следующий фрагмент:

<htmlxheadxtit le>Login Fai led</ti t leX/head>

<j sp:useBean id='loginDB1 class='beans.LoginDB'scope='application'/>

<%if ( l o g i t i D B . g e t H i n t ( r e q u e s t . g e t P a r a m e t e x - ( " u s e r N a m e " ) ) ! = n u l l ) {%>Click <a href='<%=response.encodeURL["show-hint-action.do")%>'>here</a> to gee your password hint.

<% } e l s e { %>Click <a href='<%=response.encodeURL("newftccount.jsp")%>' >here</a> to open a new account.

Java-код в JSP-документе можно заменить пользовательскими дескрипторами, спе-цифическими для конкретного приложения. Так, в приведенном ниже примере со-держатся два пользовательских дескриптора.

<htral><headxtitle>Login Fai led</t i t lex/head>

<%@ t a g l i b ur i= 'appl ica t ion-tags ' prefix='app' i>

" app:hintAvailable>

Page 166: Java Server Pages

168 Глава 6. Базовый набор классов для создания приложений Model 2

Click <а href='<%=response.encodeURL("show-hint-action.do"J%>'>here</a> to see your password hint.

/apprhintAvailable>

<app:hintNotAvailable>Click <a href='<%=response.encodeURL("login.jsp")%>' >here</a> to retry login.

</app:hintHofcAvailable>

Если подсказка доступна, в сгенерированный HTML-документ включается тело деск-

риптора hint Available, в противном случае — тело дескриптора hintNotAvailable.

Большинство пользовательских дескрипторов, например, дескрипторы, приве-

денные выше, реализуются очень просто. В листинге 6.5,а показан класс поддержки

дескриптора hintAvailable, а в листинге 6.5,6 — класс поддержки hintNot-

Available.

Листинг6.5,a. /WEB-lNF/claeses/tags/HintAvailableTag. Java

package tags;

import j avax.servlet.ServletRequest;import j avax.servlet.j sp.JspException;import javax.servlet.jsp.tagext.TagSupport;import beans.LoginDB;

public class HintAvailableTag extends TagSupport {public int doStartTag0 throws JspException {

ServletRequest req - pageContext.getRequest();LoginDB loginDB = [LoginDB)pageContext.

findAttribute("loginDB");

if(loginDB.getHint(req.getParameter("userName")) ! = null)return EVAL_BCDY_INCLUDE;

else

return SKIP_BODY;

}

Листинг 6.5,6. /WEB-INF/classes/tags/HintKotAvailableTag. Java

package tags;

import javax.servlet.jsp,JspException;

public class HintNotAvailableTag extends HintAvailableTag Ipublic int doStartTag0 throws JspException {

int available = super.doStartTag();return available == EVAL_BODY_INCLUDE ?

SKIP_BODY : EVAL_BODY_INCLUDE;

Page 167: Java Server Pages

JSP-сценарии 169

Для того, чтобы определить, доступна ли подсказка для данного пользователя, обакласса поддержки обращаются к базе данных регистрации. Методы d o S t a r t T a g этихклассов возвращают либо SKIP_BODY, либо EVAL_BODY_INCLUDE. Возвращаемое зна-чение определяет, должно ли включаться тело дескриптора в сгенерированныйHTML-документ. Подробно процесс создания пользовательских дескрипторов описанв главах 1 и 2.

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

JSP-сценарииДо сих пор в данной книге все время подчеркивалось, насколько важно разделять

Java-код и JSP-документы. В большинстве случаев этим советом не стоит пренебрегать,особенно тогда, Ko^aJSP-flOKyMeiiTbi используются в качестве просмотра в приложе-ниях, соответствующих архитектуре MVC. Однако возможны ситуации, в которыхоказываются полезны JSP-докумеиты, содержащие Java-код. В данной книге такие до-кументы будем называть JSP-сценариями. Следует заметить, что JSP — достаточно гиб-кий инструмент, позволяющий поддерживать различные подходы к построению Web-приложений.

JSP-сценарии — это JSP-документы, содержащие как HTML-дескрипторы, так и Java-код. Подобно пользовательским дескрипторам, JSP-сценарии инкапсулируют функ-ции, необходимые авторам Web-страниц. Рассмотрим сценарий, приведенный в лис-тинге 6.6,а, который выводит параметры запроса. Этот сценарий можно использо-вать для отладки.

ЛИСТИНГ 6.6, a. /showRequestParameters -jap: JSP-сценарий

J a v a . u t i l . E n u m e r a t i o n e = request .getParameterNames [ ) ;boolean hasParams = f a l s e ;

while(e.hasMoreElements()) (S t r i n g name = ( S t r i n g ) e . n e x t E l e m e n t ( ) ;String[] va lues = r e q u e s t , get Parameter-Values (name) ;

hasParams - true;

£ o r ( i n t i=0; i < v a l u e s . l e n g t h ; ++i) {S t r i ng next = v a l u e s [ i ] ;i f ( i == 0) { %>

<b><%= name %>:</b> <%= next %>}

e lse f %>, <%= next !>

Page 168: Java Server Pages

170 Глава 6. Базовый набор классов для создания приложений Model 2

if(ihasParams) { %><i>No parameters with th i s request</i>

JSP-документ, использующий сценарий, код которого представлен в листинге 6.6,а,показан на рис. 6.5.

t UJFCmami-HimidilMpMEitfaia

f* E*1 I » , fgnNM-. I»k »

Setod Your Age Bracket я i Ю r

SelwYourFeMoiileFnKiB Гк™

ЗиЬпЛОину |

11.30 1

i7A(jpl»

21-30

Г Р а а !

J

f 31-40 '

Г Grapa

LGI

" Jl-Stt

J

•nrtECkn

П I«<I t»t> о—

J

Рис. 6.5. Использование JSP-сценария

В верхнем окне на рисунке отображается HTML-документ, код которого приведенв листинге 6.6,6.

Листинг6.6,6. tes t .html

<htmlxtitle>JSP Scripts</title><body>

<form action=' showParaxns. jsp' ><font size='4' color='blue'>Select Your Age Bracket:</font>

<input type»'radio' name='age' value=<input type='radio' name='age' value=<input type='radio' name='age' value=<input type='radio' name='age' value=<input type='radio' name='age' value=

ll-20'>ll-20</input>21-30'>21-30</input>31-40'>31-40</input>41-50'>41-50</input>

<font 4' color='blue'>Select Your Favorite Fcuits:</font>

<input type-'checkbox' name='fruit' value=' Kiwi'>Kiwi</input><input type='checkbox' name='fruit' value='Apple'>Apple</input><input type='checkbox' name='fruit' value='Pear'>Pear</input><input type='checkbox' name='fruit' value='Grape'>Grape</input>

<p><input type='submit'/></p></form>

</body></html>

В дампом HTML-документе атрибут action формы имеет значение showParams. j sp.

Содержимое файла showParams. j sp показало в листинге 6.6,в.

Page 169: Java Server Pages

JSP-сценарии 171

Листинг 6.6,в. /showParams.jsp

<htmlxtitle>JSP Scripts</title><body>

<font size='4' color='blue'>Request Parameters:</font><%@ include file='showRequestParameters.jsp' %>

</body></html>

Как видно из приведенного примера, JSP-сценарии включаются в состав другихJSP-до кум с н то в -

И JSP-сцснарии, и пользовательские дескрипторы инкапсулируют функции, необ-ходимые авторам Web-страниц. По сравнению с JSP^eiiapunMir, пользовательскиедескрипторы сложнее в реализации; для создания пользовательского дескриптора на-до написать класс поддержки, скомпилировать его и включить в описание библиоте-ки дескрипторов. JSP-сценарии реализуются проще, но для работы с ними необходи-мо использовать директиву i n c l u d e . Несмотря на то что пользовательские дескрип-торы лучше обеспечивают повторное применение кода, для JSP-сценариев такженайдется место в наборе инструментов разработчика^Р-докумснтов.

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

для разработки сложных Web-приложений, поскольку они представляют собой"смесь" HTML-дескрипторов и Java-кода. Я не согласен с этим. При создании большихприложений на 6ascJSP я всегда следую архитектуре Model 2, а средства, описанные вданной глане, полностью устраивают меня.

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

Объектно-ориентированное проектирование:

Gamma, Helm, Johnson, Vlissidcs. Design Patterns, Addison-Wesley, 1992.Wirfs-Brock, Wilkerson, Wiener. Designing Object-oriented Software, Prentice Hall, 1990.Meyer, Bertand. Object-Oriented Software Construction, Prentice Hall, J997.Budd, Timothy. An Introduction to Object-oriented Programming, Addison-Wesley, 1991.

Архитектура"модель~просмотр-контроллср":

Alpert, Brown, Woolf. The Design Patterns Smalltalk Companion, Addison-Wesley, 1998.Gamma, Helm, Johnson, Vlissides. Design Patterns, Addison-Wesley, 1992.

Сценарии развития событий:

Fowler, Scott. UML Distilled Second Edition, Addison-Wesley, 2000.Schneider, Winters. Applying Use Cases, a Practical Guide, Addison-Wesley, 2000.

Page 170: Java Server Pages

ПОДДЕРЖКАСОБЫТИЙ

В этой главе...

• Поддержка событий в базовом наборе классов Model 2.• Повторная активизация форм.• Обработка форм, чувствительных к повторной активиза-

ции, средствами базового набора классов Model 2.• Перехват повторной активизации без использования

базового набора классов.

Page 171: Java Server Pages

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

фокусом ввода.До того, как была реализована спецификация Servlet 2.3, контейнеры сервлетов

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

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

В данной главе мы реализуем обработку событий в базовом наборе классов Model2. Далее вы увидите, как механизм обработки событий может быть использован дляподдержки форм, чувствительных к повторной активизации. Повторная активизациявозникает в результате неправильного использования закладок или кнопки Reload.

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

Page 172: Java Server Pages

174 Глава 7. Поддержка событий

Поддержка событий в базовом набореклассов Model 2

Базовый набор классов Model 2 хорошо подходит для создания Web-приложений,поскольку он позполяет разделить бизнес-л о гику и средства представления данных иобеспечить параллельную работу авторов Web-страниц и разработчиков программно-го обеспечения. Если же набор классов Model 2 генерирует события, приложениеможет быть расширено. Например, если базовый набор классов генерирует событиядо и после выполнения некоторого действия, приложение имеет возможность обра-батывать эти события для выполнения ряда задач, таких как аутентификация, под-держка национальных кодировок или перехват повторной активизации форм.

В данном разделе мы расширим созданный ранее базовый набор классов Mode) 2так, чтобы до и после вызова метода perform действия генерировались события. Доработка набора ьслассов осуществляется в соответствии с моделью делегирования со-бытий Java, т.е. источники событий передают событии обработчикам, или объектампрослушивания.

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

new Attie nEvsnlf AC1ion£rtrt>CT10M.BEFORe_PERFOB*t, fflQUlst, risoonse)

У

peffomi(seiv<at, request, response}

i. t n Шщ l¥fl rir;

.PERFORM)

Uflff и*допос*цр.та^сГ10п1_1Я#п«р."к

>ызые jwit и uel otv (JSfqr»Arign й•tertttjun

inecAcDonfactiait, rt'ques!, response)

Рис. 7.1. Последовательность событий, возникающих при выполнении действий

Page 173: Java Server Pages

Поддержка событий в базовом наборе классов Model 2 175

Перед выполнением действия сералет создает событие действия, указывая в каче-стве типа события ActionEvent.ACTION_BEFORE_PERFORM Это событие передает-ся методу f ireEvent действия, который вызывает метод beforeAction каждого за-регистрирован н о го об раб отч и ка.

После выполнения события сервлет действий создает событие типа Action-Event .ACTION_AFTER_PERFORM. Это событие снова передается методу fireEvent

^ действия, который вызывает метод afterAction каждого зарегистрированного об-работчика.

Интерфейс ActionListener показан в листинге 7.1. Заметьте, что класс Actionи интерфейс ActionListener не имеют никакого отношения к Action иActionListener AWT.

ЛИСТИНГ 7.1 ,а. /WEB- INF/ classes /actions /events /ActionLi a tener. Java

package actions.events;

import javax,servlet.ServletException;

public interface ActionListener extends Java,util.EventListener (public void beforeAction[ActionEvent event)

throws ServletException;public void afterAction[ActionEvent event)

throws ServletException;

Интерфейс ActionListener расширяет интерфейс J a v a . u t i l .EventListener,который должны реализовать все обработчики событий. Методы в интерфейсеEventListener отсутствуют.

В интерфейсе ActionListener объявлены два метода: beforeAction и a f t e r -Action. Как показано на рис. 7.1, эти методы нызываются до и после обращения к ме-тоду perform действия.

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

При вызове каждому из методов ActionListener передастся объект исключения.Код класса ActionEvent представлен в листинге 7.1,6.

Листинг7.1,6. /WEB-INF/classes/actions/events/ActionEventoava

package actions.events;

import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import actions.Action;

public class ActionEvent extends Java.util.EventObject (public static final int BEFORE_ACTION=0, AFTER_ACTION=1;

private int eventType;private HttpServletRequest request;private HttpServletResponse response;

Page 174: Java Server Pages

176 Глава 7. Поддержка событий

public ActionEvent(Action action, int eventType,HttpServietRequest request,HttpServietResponse response) (

super(action); // определение действия как11 источника событий

this.eventType • eventType;this.request = request;this.response = response;

}public int getEventType[) f return eventType; }public HttpServietRequest getRequest() { return request; }public HttpServletResponse getResponse() { return response; }

public void setEventType(int eventType) {this.eventType = eventType;

При построении события конструктору класса передаются объект Action, тип со-

бытия, а также объекты, представляющие запрос и ответ. Конструктор передает объ-

ект Action конструктору суперкласса (java.util. EventObject), определяя дейст-

вие как источник события. Для получения действия можно воспользоваться методом

getSource, унаследованным от суперкласса.

Код интерфейса Action, который обсуждался в главе 6, приведен в листинге 7.1.в,

На этот раз в интерфейсе предусмотрена поддержка событий, связанных с действия-

ми. Теперь объект Action позволяет регистрировать обработчики и передавать им

события в том порядке, в котором они были зарегистрированы. Для передачи собы-

тий используется метод Action . f ireEvent.

Листинг 7.1,в. /WEB-INF/claps*a/action»/Action. Java

package actions;

import javax.servlet.http.HttpServiet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import actions.events.ActionEvent;import actions.events.ActionListener;

// Данный интерфейс реализуют действия, специфические для приложения

public interface Action {ActionRouter perfom(HttpServlet servlet,

HttpServletRequest req,HttpServletResponse res)throws j avax,servlet.ServletException;

void addActionListener[ActionListener listener);void fireEvent(ActionEvent event)

throws javax.servlet,ServletException;

Page 175: Java Server Pages

Поддержка событий в базовом наборе классов Model 2 177

Реализацию по умолчанию методов Action.addActionListener и Action.

FireEvent предоставляет абстрактный класс ActionBase, код которого приведен в

листинге 7.1,г.

Листинг 7.1,Г. /WEB-INF/clasaea/actions/ActionBaae. Java

package actions;

import Java.util.Enumeration;import java.util.Vector;import javax.servlet.ServletException;import actions.events.ActionEvent;import actions.events.ActionListener;

public abstract class ActionBase implements Action (protected boolean isSensitive = false,

hasSensitiveForms • false;private Vector listeners = new Vector!);

public ActionBase() {addActionListener(new SensitiveActionListener());

)public void addActionListener(ActionListener listener) {

listeners.addElement(listener);

public void removeActionListener(ActionListener listener) {listeners.remove(listener);

}public void fireEvent(ActionEvent event)

throws ServletException (Enumeration it - listeners.elements 0;

while(it.hasMoreElementsf) ) {ActionListener listener =

(ActionListener)it.nextElement 0;

switch!event.getEventType 0) {case ActionEvent. BEFORE__ACTION:

listener.beforeAction(event);break;

case ActionEvent.AFTER_ACTION:listener.afterAction(event);break;

Наконец, модифицированный код сервлета действий, в котором учтена обработка

событий, показан в листинге 7.1,д. Предыдущий вариант сервлета содержался в лис-

тинге 6.3,6.

Page 176: Java Server Pages

178 Глава 7. Поддержка событий

Листинг7.1,д. /WEB-lNF/classes/ActionServlet. Java

// Пропущенные фрагменты кода можно найти в листинге 6.3,6.

import actions.events.ActionEvent;

public class ActionServlet extends HttpServlet {

public void service(HttpServletRequest req,HttpServletResponse res}throws ServletException {

Action action = getAction(req);ActionEvent event = new ActionEvent(action,

ActionEvent.BEFORE_ACTION, req, res);

action.fireEvent(event);

ActionRouter router = performAction(action, req, res);

action.setEventType(ActionEvent.AFTER_ACTION);

action.fireEvent(event);

// Подобно действиям, маршрутизаторы// могут генерировать событияrouteAction(router, req, res);

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

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

Повторная активизация формПри работе с формами пользователь может неумышленно выполнить некоторые

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

Page 177: Java Server Pages

Повторная активизация форм 179

На рис. 7.2 показаны Web-страницы, при работе с которыми форма может бытьповторно активизирована. Приведенные Web-страницы демонстрируют последова-тельность действий, выполняемых пользователем с именем Timotliy (его регистраци-онное имя t imothy) при создании новой учетной записи.

' Eil. £a< Kn. Fpwmut loot! jjMp

, « * . » jg l 1ч1„,-.1к,|пв1МЭ№™в!*>а.п-|1О»л* ,t _

Login Failed

Please enter a vabd username andpassword, or create a new account

Please Login

Name: jtimottiy

Password: |

Click here to open a new account

f c t t o n i » М М М

В

J

Open a New

User Name:

Password:

Confirm Password:

Password Hinl:

create account

C'lm'evaai/qusn-c^o.iri-r

Account

tirnolh^

my dog's name

1

фЫнгг...

1-|П|«|

о

An account has been crated foe timothy

Please Login

Name: (timothy

Password: [~

Рис. 7.2. Ситуация, при которой можетпроизойти повторная активизация формы

Сначала пользователь Timotliy пытается зарегистрироваться в системе, но по-скольку для него еще не создана учетная запись, запрос перенапраплястся документуLogin Failed. Здесь Timothy активизирует ссылку на страницу, предназначенную длясоздания новой учетной записи, в результате чего вызывается документ, озаглавлен-ный Open a New Account. На Wcb-странице, отображаемой на экране, пользовательзаполняет форму и щелкает на кнопке create account.

На этом этапе для пользователя Timothy создается учетная запись, и на экранеотображается Web-страница Please Login, содержащая форму. Посредством этойформы Timothy должен зарегистрироваться, используя вновь созданную учетную за-пись. Но что произойдет, если вместо регистрации пользователь щелкнет на кнопке

Page 178: Java Server Pages

j f c Local iinrow

180 Глава 7. Поддержка событий

Reload, вызвав тем самым повторную загрузку Web-страницы? Или если он воспользу-ется кнопкой Back, а на странице регистрации, не изменяя данных в форме, щелкнетна кнопке create account? В обоих случаях будет сгенерирован повторный запрос, т.е,возникнет ситуация, которая в большинстве приложений вызывает нежелательныепоследствия.

В данной главе рассматриваются два способа противодействия повторным запро-сам; в одном из них используется обработка событий, а в другом— пользовательскиедескрипторы JSP. В обоих случаях при попытке повторной активизации формы гене-рируется исключение {рис. 7.3).

J ''"Г //tuiiiilhusi .naiKVnvnnlsfmiw ui;i:i>unl in i iln - Mi,.ur.:i>tt Inlcmni Г xpln

j j Aidr»M j|l]iittp localhast908D/events/new-accounl-ac(ion.do

Error: 500

Location: /events/new-account-action-do

Internal Servlet Error:

javaxservlet ServletException: Sorry, but this is a sensitive page that cent be resubmitted.at actions S ensitiveActionListener.be Core Acti on(actions/3ensitiveActionListener.jav»:Z4)ataotions.ActionBase.fireEventCactions/AotionBasejava:48)atedionsActionBasebeforeActionCections/ActionBasejava3Z) ША

Рис, 7.3. При попытке повторной активизации формы генерируется исключение

Обработка форм, чувствительных к повторнойактивизации, средствами базового набора классовModel 2

Как следует из сказанного выше, некоторые действия чувствительны к повторнымзапросам, возникающим при использовании закладок и кнопки Reload. В нижнем ок-не на рис. 7.2 показан результат выполнения действия n e w - a c c o u n t - a c t i o n (см.URL в поле Address). Действия, чувствительные к повторным запросам, называютсячувствительными действиями.

Повторное обращение к чувствительным действиям приводит к ошибке или к воз-никновению недопустимого состояния. Так, например, при повторном вызове дейст-вия n e w - a c c o u n t - a c t i o n , код которого представлен в листинге 7.2,а, могут бытьсозданы две одинаковые учетные записи.

Page 179: Java Server Pages

Повторная активизация форм 181

Листинг7.2,в. /WEB-INF/classas/actions/NewAoccmntAction. Java

package actions;

import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServietResponse;

import beans.LoginDB;import beans.User;

public class NewAccountAction extends ActionBase (public NewAccountAction() {

isSensitive = true; // чувствительное действие

public ActionRouter perform(HttpServlet servlet,HttpServletRequest req,HttpServletResponse res)throws ServletException {

LoginDB loginDB = (LoginDB)servlet.getServletContext().getAttribute("loginDB");

String uname « req.getParameter("userName");

loginDB.addUser(uname, req.getParameter("password"),req.getParameter("password-hint"]

req.setAttribute("userName", uname);

return new ActionRouter("account-created-page");

Кроме чувствительных действий, мы также будем обсуждать чувствительные фор-мы. Форма называется чувствительной к повторным активизациям, или просто чувстви-тельной, если она вызывает чувствительное действие. Так, например, чувствительнойявляется форма, предназначенная для создания новых учетных записей, посколькуона вызывает чувствительное действие new-account-action.

Некоторые действия перенаправляют запросы JSP-документам, содержащим чув-ствительные формы. В качестве примера можно привести действие query-account-action, код которого содержится в листинге 7.2,6; его выполнение приводит к ото-бражению JSP-документа, показанного на рис. 7.2, содержащего чувствительную фор-му. Будем называть такие действия действиями с чувствительными формами.

Листинг 7.2,6. /WEB-INF/classes/aetiona/QueryAccountAction. java

package actions;

import javax.servlet.ServletException;import javax.servlet.http.*;

// Данное действие перенаправляет запрос JSP-документу,// содержащему чувствительные формы.

public class QueryAccountflction extends ActionBase {

Page 180: Java Server Pages

182 Глава 7. Поддержка событий

public ActionRouter perform(HttpServlet servlet,HttpServletRequest req,HttpServletResponse res)throws ServletException (

// query-account-page - это логическое имя// JSP-документа, отображаемого в левом верхнем// углу на рис. 7.2.

return new ActionRouter("query-account-page");

»

i

Для перехвата повторной активизации чувствительных форм необходимо про-слеживать чувствительные действия и действия с чувствительными формами. Чтобыдобиться этого, надо внести изменения в интерфейс Action и класс ActionBase.Фрагменты модифицированного кода показаны в листингах 7.3,а и 7.3,6,

Листинг 7.3,а. Интерфейс Action с поддержкой чувствительных форм

//Пропущенные фрагменты кода можно найти в листинге 7.1,в.

package actions;

public interface Action extends ActionListener (

public boolean hasSensitiveForms();public boolean isSensitive();

ЛИСТИНГ 7.3,6. Класс ActionBase С поддержкой чуВСТВИтелЬНЫХ форм

//Пропущенные фрагменты кода можно найти в листинге 7.1,г.

package actions;

public abstract class ActionBase implements Action {protected boolean isSensitive = false,

hasSensitiveForms = false;

public ActionBase(J {

addActionListener(new SensitiveActionListener());

public boolean isSensitive() {return isSensitive;

}public boolean hasSensitiveForms() {

return hasSensitiveForms;

Page 181: Java Server Pages

Повторная активизация форм 183

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

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

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

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

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

Исходный код класса Token приведен в листинге 7.4,а.

ЛИСТИНГ 7.4,a. /WEB-INF/classes/beans/Token . Java

package beans;

import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import j avax.servlet.http.HttpSession;import Java-security.MessageDigest;

public class Token (private String token;

public Token[HttpServletRequest req) throws ServletException (HttpSession session = req.getSession(true);long systime = System.currentTimeMillis (),-byte[) time - new Long[systime).toString().getBytes();byte[] id = session.getldf).getBytes();try {

MessageDigest md5 = MessageDigest.getlnstance ("MD5");md5.update(id);md5.update(time);token = toHextmdb.digest());

)catch(Exception ex) {

throw new ServletException(ex);

public String toString() {

Page 182: Java Server Pages

184 Глава 7. Поддержка событий

return token;)private String toHex(byte[] digest) {

StringBuffer buf = new StringBuffer() ;

for(int i=0; i < digest.length; i++)buf.append(Integer.toHexStringf(int)digest [i] & OxOOff)

return buf.toString();

Объект Token создает уникальную закодированную строку, основой для созданиякоторой являются данные о сеансе и текущее время. При обсуждении примера с ис-пользованием маркера удобно рассматривать объект Token как "черный ящик": в от-вет на обращение объект предоставляет вам строку-маркер.

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

// Пропущенные фрагменты кода можно найти в листинге 7.3,6.

public abstract class ActionBase implements Action {

public ActionBase() {addActionListener{new SensitiveActionLiatener());

На рис. 7.4 показана последовательность событий, которые сопутствуют вызовуметода bef oreAct ion обработчика, связанного с чувствительным действием.

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

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

Page 183: Java Server Pages

Повторная активизация форм 185

Action ЕУВГИ ActionLllinn RtomSI

1

nm Action£v»nt(»d«jn

lActwnQ

л1

ЛгаЕчтт! (tvsni)

расту пу( н HJUiаугтопняегсч обы1

AclionE«nt.ACT10N_BEFORe_PERFORM.i

, nipmm;

•xnt gMR<quisi|)

л и сеанса н*

i ел уча*

[relum

^^,rttum

getSseiionO

mponct)

РИС. 7.4. Фильтрация событий перед их выполнением

Page 184: Java Server Pages

186 Глава 7. Поддержка событий

Action SinsitrvaActipn

LatBMt Reguesi

HltnSinainn

new ActionEvantlaction

I1

Еслиии#«т н

0&лас1о&лкт

ActionEwnt ACT10M_AFreR_PERF0RMd

s ,

. response]

(Action) gelSourc e()

n( ge(RequBSL()

,J.:'!.:::Mi-L L

запроса н всеанса

-.,. 5В1АЛ nblH»n^«i'- l o k » n loStringO)

g«lS«»ionO .

UniLOvcPcriraO]

-Token lakan = navr TolkmO

Takin

U

Го сп« 1ыпвчнмилчуБСТвкТбДънЬГП Дейстй!- 1

лили

Рис, 7.5, Фильтрация действий после их выполнения

Page 185: Java Server Pages

Повторная активизация форм 187

Код класса SensitiveActionListener показан в листинге 7.4,6.

Листинг 7.4,6. /WEB-INF/cla3sea/actions/SensitiveActionListener. Java

package actions;

import javax.servlet.SeсvletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;import actions.Action;import actions.events.ActionEvent;import actions.events.ActionListener;import beans.Token;

public class SensitiveActionListener implements ActionListener {public void befoceAction(ActionEvent event)

throws ServletException {Action action = (Action)event. getSource();

if [action.isSensitive() ) (HttpServletRequest req = event.getRequest();String requestToken = (String)req.getParameter("token"),

sessionToken = (String)req.getSession().getflttribute("token");

if [sessionToken == null | | requestToken == null | |!sessionToken.equals(requestToken)) {

throw new ServletException("Sorry, but this is a sensitive page " +•"that can't be resubmitted.");

public void afterAction(ActionEvent event)throws ServletException {

Action action = (Action)event.getSource(};HttpServletRequest req = event.getRequest();HttpSession session = req.getSession();

if(action.hasSensitiveForms()) (Token token = new Token(req);

session.setAttribute("token", token.tostring());req.setAttribute("token", token.toString());

}if(action.isSensitive()) {

session.removeAttribute("token");

Page 186: Java Server Pages

188 Глава 7. Поддержка событий

Применение маркеров в программахТеперь, когда вы знаете, как работают с маркерами объекты прослушивания, рас-

смотрим вопросы использования маркеров в программах. В первую очередь надо

идентифицировать действие как чувствительное. Соответствующий код для действия

new-accaurvt-action приведен ниже.

// Пропущенные фрагменты кода можно найти в листинге 7.2,а.

public class NewAccountAction extends ActionBase Ipublic NewftccountActicn() (

isSensitive = true; // чувствительное действие

Аналогично, query-account-action идентифицируется как действие с чувстви-

тельной формой.

// Пропущенные фрагменты кода можно найти в листинге 7.2,6.

public class QueryAccountAction extends ActionBase {public QueryAccountAction[) {

// Данное действие перенаправляет запрос JSP-документу// с чувствительными формамиhasSensitiveForms • true;

В файле newAccount. j sp, содержимое которого показано в листинге 7.4, содер-жится чувствительная форма, для которой в качестве значения атрибута action ука-зано действие new-account-action. К моменту загрузки JSP-документа обработчиксохраняет маркер в области видимости запроса и сеанса. Поскольку при активизацииформы генерируется новый запрос, пользовательский дескриптор извлекает маркери включает его в новый запрос как скрытый элемент формы.

ЛИСТИНГ7.4,В. /newAccount.jsp

<htmlxheadxtitle>New Account</title><%@ taglib uri='/WEB-lNF/tlds/utilities.tld' prefix-'util1 %>

</head><body><font size='5' colors'blue'>Open a New Account</font><hr><form action^' <%=response.encodeURL("new-account-action.do")%> '

method='post'><tablextr>

<td> User Name: </td><tdxinput type='text' name='userName'x/tci>

</trxtr><td> Password: </td><td><input type='password1 name= 'password1 size='S 'x/td>

</trxtr>

Page 187: Java Server Pages

Повторная активизация форм 189

<td> Confirm Password: </td><td><input type='password' name='сonfirm-password'

size='8'x/td></tr><tr>

<td> Password Hint: </td><td><input type='text' name='password-hint'></td>

</tr></table><br><input type='submit' value='create account'><util: tofcen/>

</form></body></html>

В результате активизации формы вызывается действие new-account-action.Обработчик, связанный с чувствительным действием, проверяет маркеры для запро-са и сеанса. В результате действие new-account-action будет выполняться толькопри активизации чувствительной формы и не будет реагировать на переходы по за-кладке и кнопке Reload.

Чтобы закончить обсуждение данного вопроса, нам осталось рассмотреть структу-ру пользовательского дескриптора u t i l : token. Класс поддержки для данного деск-риптора приведен в листинге 7.5,

ЛИСТИНГ 7.5./WEB-INF/classes/tags/TokenTag.Java

package tags;

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

public class TokenTag extends TagSupport (private String property;

public int doStartTagd throws JspException {ServletRequest req = pageContext.getRequest();String value = (String)req.getAttribute("token");

if(value = null}throw new JspExceptionC'Ko token in request scope");

try {

pageContext.getOut().print("<input type='hidden1 " +

"narae='token' " + "value -'" + value + n (

> " ) ;}catcMjava. io. IOException ex) {

throw new JspException(ex.getMessage());}

return SKIP_BODY;

1

Page 188: Java Server Pages

190 Глава 7. Поддержка событий

Дескриптор token генерирует HTML-код скрытого элемента формы. Значениемэтого элемента является маркер, который обработчик, связанный с чувствительнымдействием, сохранил в области видимости запроса.

СоветНе забывайте предусмотреть перехват повторнойактивизации чувствительных форм

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

Перехват повторной активизации без использованиябазового набора классов

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

Пользовательские дескрипторы, предназначенные для перехвата повторной акти-визации чувствительных форм, перечислены в табл. 7.1.

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

Дескриптор Описание

create-tokens Создает маркеры и сохраняет их в области видимости запросаи в области видимости сеанса

token Помещает маркер в форму в виде скрытого поля

check-tokens Проверяет корректность маркера

Дескрипторы, приведенные в табл. 7.1, просты в применении; например, в лис-тинге 7.6,а представлен JSP-документ, который использует первый и третий из пере-численных выше дескрипторов.

Page 189: Java Server Pages

Повторная активизация форм 191

Листинг 7.6,а. Применение пользовательских дескрипторов create-tokensИ

<htttLl><headxtitle>New Account</title><%@ taglib uri='/WEB-INF/tlds/utilities.tld1 prefix='util ' l><%@ taglib uri='/WEB-lNF/tlds/tokens.tld' prefix='tokens' %><tokens:create-tokens/>

</head>

<body><font s i2e='5 ' color='blue">Open a New Account</font>

<f orm action^'createAccount.j sp' method^'post'><table>

<tr><td>User Name:</td><tdxinput type=' text' name=' userName' ></td>

</tr><tr>

<td>Password:</td><td><input type='password1 name=' password1 size='8 'x/td>

</tr><tr>

<td>Confirm Password:</td><td><input type=1password' name='confirm-password'

size= I8'x/td></tr><tr>

<td>Password Hint:</td><tdxinput type=' text' name=' password-hint' x/td>

</tr></table><br><input type='submit' value^'create account'><tokens:token/>

</form></body></html>

В JSP-документе, приведенном выше, дескриптор create-tokens создаст марке-ры для сеанса и запроса, а дескриптор token передаст маркер запроса действию,предназначенному для создания новой учетной записи. Действие передается какскрытое поле (скрытое поле необходимо использовать потому, что при активизацииформы генерируется новый запрос). Дескриптор token был использован ранее влистинге 7.5.

Дескриптор check-tokens проверяет идентичность дескрипторов запроса и се-анса. Если дескрипторы отличаются друг от друга, генерируется исключение. JSP-до-кумент, код которого представлен в листинге 7.6,6. использует данный дескриптордля того, чтобы исключить возможность дублирования учетных записей.

Page 190: Java Server Pages

192 Глава 7. Поддержка событий

Листинг 7.6,6. Использование дескрипторов check-tokens и reraove- seas ion -token,

tagLib uri='/WEB-INF/tlds/utilities•tld' prefix='util' %><%Э taglib uri='/WEB-INF/tlds/tokens.tld' prefix='tokens• %>

<tokens:check-tokens/>

<jsp:useBean id='bean' class='beans.CreateAccountBean'/><l bean.createAccount(request, application); *>

<font size='4' color='blue'>An account has been created for<%= request.getParameter("userName") %>

</font>

<p><%@ include file='login-forra.jsp' %></p>

Код класса поддержки дескриптора create-to kens приведен в листинге 7.6,в.

Листинг 7.6,в. /WEB-INF/class©s/tags/CreateTokensTag. Java

package tags;

import javax.servlet.ServletRequest;import javax.servlet.http.HttpServletRequest;import jsvax.servlet.jsp.JspException;import javax.servlet.jsp.PageContext;import javax.servlet.jsp.tagext.TagSupport;import beans.Token;

public class CreateTokensTag extends TagSupport {private String property;

public int doEndTag() throws JspException {ServletRequest request = pageContext.getRequest();

try (Token token = new Token((HttpServletRequest)request);

pageContext.setAttribute("token", token.toString [),PageContext.SESSION_3COPE)

pageContext.setAttribute("token", token.toString(),PageContext. REQtfEST_SCOPE)

}catch(Exception ex) {

throw new JspException(ex.getMessage ());)retUtn EVAL_PAGE;

Page 191: Java Server Pages

Повторная активизация форм 193

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

Код класса поддержки дескриптора check-tokens приведен в листинге 7.6,г.

Листинг7.6,Г. /WES-IKF/classes/tags/CheckTokensTag.Java

package tags;

import javax.servlet.ServletRequest;import javax.servlet. jsp,JspException;import javax.servlet. jsp.tagext.TagSupport;

public class CheckTokensTag extends TagSupport {private String property;

public int doEnctTag 0 throws JspException {ServletRequest req • pageContext.getRequest t ) ;String sessionToken ™ (String)req.getParameter("token");String requestToken • (String)pageContext.getSession().

getAttr ibute("token");

if(requestToken == null || cequestToken >•• null ||!sessionToken.equals(requestToken))throw new JspException("Sorry, but th i s sensit ive page" +

" c a n ' t be resubmitted.");

return EVAL_PAGE;

Класс поддержки данного дескриптора извлекает два маркера и сравнивает их. Ес-ли дескрипторы отсутствуют или не совпадают, класс поддержки генерирует исклю-чение.

РезюмеВ данной главе рассматривались три основных вопроса.

• Обработка событий в базовом наборе классов Model 2.

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

• Перехват повторной активизации чувствительных форм без применениясредств обработки событий и архитектуры Model 2.

Рассмотрение первого вопроса сводится к обсуждению модели делегирования со-бытий Java и реализации базового набора классов в соответствии с образом разработ-ки Front Controller. Согласно этому образу разработки, поддержка событий должнаосуществляться централизованно, и средства обработки событий реализуются в со-ставе сервлета. В данной главе используется базовый набор классов Model 2, но вы без

Page 192: Java Server Pages

194 Глава 7. Поддержка событий

труда сможете адаптировать механизм обработки событий в другом наборе, соответ-ствующем образу разработки Front Controller.

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

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

Page 193: Java Server Pages

I18N

л.

В этой главе...

Unicode.

Кодировки.

Поддержка регионов.

Наборы ресурсов.

- Класс ListResourceBundle.

- Класс PropertyResourceBundle.

- Использование нескольких наборов ресурсов.

Форматирование данных, чувствительных к региону

Языки, поддерживаемые броузерами.

- Определение регионов.

- Получение набора ресурсов.

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

- Дескриптор message.

- Дескриптор format.

Page 194: Java Server Pages

На заре развития World Wide Web большинство Web-узлов создавалось на анг-лийском языке, однако со временем поддержка нескольких языков стала обы-денным явлением. Авторы начали создавать свои Web-страницы на языке

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

В данной главе рассматриваются вопросы интернационализации (internationalization)и локализации (localization) содержимого Web-страниц. Для обозначения интернациона-лизации часто используют сокращение il8n, а для обозначения локализации — ПОп, В этихаббревиатурах присутствуют первая и последняя буквы термина {internationalization илиlocalization) и количество символов, находящихся между ними. Интернационализация —это процесс создания приложений, поддерживающих несколько языков, и специфическиеособенности представления информации, характерные для страны, а локализация пред-полагает адаптацию данных для конкретного региона.

Начиная с версии 4,0 как Netscape Communicator, так и Internet Explorer поддер-живают приоритетный список языков. В данной главе вы познакомитесь с созданиеммного языкового содержимого, базирующегося на таком списке. Кроме того, вы уз-наете, как выполняется форматирование объектов, чувствительных к региону, на-пример чисел и дат. В конце главы будут описаны два пользовательских дескриптора,предназначенных для доступа к локализованному тексту и форматирования объектовс учетом конкретного региона. Но сначала необходимо рассмотреть основы интерна-ционализации: набор символов Unicode и кодировки.

UnicodeВ языке программирования Java для представления символов используется набор

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

1 Дополнительную информацию о Unicode вы можете найти по адресу hup://iaam>. uniiode.org/. -Прим. авт.

Page 195: Java Server Pages

198 Глава 8. I18N

лов, отсутствующих на клавиатуре, можно применять управляющие последовательно-сти. Например, на рис. 8.1 показан JSP-документ, содержащий фразу "Интернаци-онализировано ли ваше приложение" на испанском языке.

i-'llncaie Escape Sequences - MiciojofUntanat EMpioref - I D 1 x[

j Fife gdlt View Fnvorltts Tool! №ф

'• AsJcken j £ | hltpWIoealhost:MMVil Bn-ctiaisetsytesl. jsp J^J <?Go I

^Sus usos son internacionalizados?

,f>r Local rtianei

Рис. 8.1. Использование управляющих после-довательностей Unicode

Код JSP-документа, показанного на рис. 8.1, представлен в листинге 8.1.

Листинг 8.1. Пример применения управляющих последовательностей Untcode

<htmlxhead><title>Unicode Escape Sequences</title><%S page contentType='text/html; eharset=IS0-8859-1' %><% response.setHeader("Content-Language", "es") ; %>

</head><body>

<font size-' 4'><%= "\uOOBFSus usos son internacionalizados?" %>

</font>

</body></html>

В приведенном JSP-документе для представления перевернутого вопросительногознака используется управляющая последовательность \uOOBF. Управляющие после-довательности задаются в формате \uXXXX, где ХХХХ— шестнадцатеричное число.Список последовательностей для символов, соответствующих набору Latin-1, содер-жится в документе http://www.unicode.org/charts/PDF/UOOOO.pdf.

Помимо управляющих последовательностей Unicode, вы можете использоватьспециальные обозначения HTML, так, например, перевернутый вопросительныйзнак представляется последовательностью символов &iquest, (Обозначения симво-лов по соглашению HTML можно найти в документе http://www.utoronto.ca/webdocs/HTMLdocs/NewHTML/iso_table.html.) Специальные обозначения HTMLдопустимы только в составе HTML-кода, поэтому в сервлстах и JSP-документах, кото-рые могут генерировать не только HTML, но и документы в других форматах, жела-тельно применять управляющие последовательности Unicode.

Page 196: Java Server Pages

Кодировки 199

JSP-документ, представленный в листинге 8.1, устанавливает содержимое поля за-головка Content-Language, равным es. Это означает, что в составе ответа находит-ся текст на испанском языке. Несмотря на то что данный заголовок рекомендуетсявключать в состав ответа, броузеры часто игнорируют его. В данном примере, еслизаголовок Content-Language будет отсутствовать, Netscape и Internet Explorer ото-бразят тот же результат.

КодировкиБроузеры отображают числовые значения в символы или их фрагменты, В приме-

ре, приведенном в листинге 8.1, код \uOOBF представляется как перевернутый вопро-сительный знак. Отображение числовых значений в символы поддерживается по-средством кодировки. В RFC 2278 кодировка определена как способ преобразованияпоследовательности октетов в последовательность символов (RFC 2278 можно найтипо адресу http://www,cis .ohiostate.edu/htbin/rfc/rfc2278.html). В приме-ре, приведенном в листинге 8.1, используется кодировка ISO-8859-1, которая приме-няется по умолчанию, поэтому явное указание кодировки в примере отсутствует.

JSP -документы, написанные на языках,использующих буквы, отличные от латинских

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

pit Edit view Tooh Hslp

"HIpVflocalhostSCTOAiarrchatsets/test.isp

Щ

*» Locdinnanet

Рис. 8.2. Использование корейской кодировки

Page 197: Java Server Pages

200 Плавав. I18N

Код JSP-документа, показанного на рис, 8.2, приведен в листинге 8.2.

Листинге.2. Пример явного указания кодировки

<html><headxtitle>Character Sets</tltle><%9 page contentType='text/html; charset=EUC-KR' %><% response.setHeader("Content-Language", "ko"); %>

</head><body>

<%* "\uc548\ubl55\ud558\ucl38\uc694" %>

</body></html>

В JSP-документе, показанном на рис. 8.2, используется кодировка EUC-KR, посред-ством которой последовательность символов "\uc548\ubl55\ud558\ucl38\uc694"отображается в корейские иероглифы. Для того чтобы данный документ выводилсякорректно, броузер должен поддерживать кодировку EUC-KR и иметь доступ к корей-ским шрифтам. Чтобы эти требования удовлетворялись, необходимо установить до-полнительное программное обеспечение. Необходимые программы с инструкциямипо инсталляции можно найти по следующим адресам.

• Netscape Communicator: h t t p : //home .netscape.com/eng/intl

• Internet Explorer: h t t p : //www.microsoft. com/ie/intlhome.htm

Список наиболее часто используемых кодировок находится в документе h t t p : //www.w3.org/International/0-charset-lang.html.

В соответствии со спецификацией Servlet 2.2, вы можете установить набор симво-лов с помощью метода ServletResponse . setLocale. Дополнительная информацияпо данному вопросу будет приведена далее в этой главе.

Многоязыковые JSP-документыДля отображения текстов на различных языках в одном документе надо восполь-

зоваться UCS (Universal Character Set — универсальный набор символов), утвержден-ным в 1993 г. ISO (International Standards Organization) и IEC (International Electro-technical Commission).

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

Благодаря совместимости UCS и Unicode появляется возможность выполнятьпреобразования. В настоящее время наиболее популярным является формат UTF-8(UCS Transformation Format 8— формат преобразования UCS 8), который позволяетконвертировать UCS в одно-, двух- или трехбайтовое представление. ASCII-символыl'TF-8 позволяю! преобразовывать в одвобайтовые значения, которые обрабатыва-

Page 198: Java Server Pages

Поддержка регионов 201

ются намного эффективнее, чем UCS. Благодаря наличию описанных свойств UTF-8широко применяется для отображения многоязыковых документов.

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

Поддержка регионовОдной из основных задач, связанных с интернационализацией приложений, явля-

ется идентификация географических, политических и культурных регионов. В языкепрограммирования Java регионы представляются посредством класса j a v a . u t i l .Locale; экземпляры этого класса используются объектами, чувствительными к ре-гионам, для форматирования данных.

В классе Locale определены два конструктора:

• Locale(String language, Str ing country)

• Locale(String language, String country, String var iant)

Первыми двумя параметрами обоих конструкторов являются ISO-коды. Первый па-раметр представляет язык, а второй — страну. Эти коды перечислены в документахhttp://www.ics.uci.edu/pub/ietf/http/related/iso639.txt и h t t p : / / u s e r -page .cheraie.fuberlin.de/diverse/doc/ISO_3166.html.

Третий параметр второго конструктора часто называют вариантом (variant). Ва-риант представляет информацию, специфическую для броузера и поставщика про-дукта, например, значение WIN соответствует Windows, a MAC — Macintosh. На практи-ке вариант применяется редко.

Для обозначения часто используемых регионов класс Locale предоставляет ста-тические константы, которые перечислены в табл. 8.1.

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

Константа Код страны Код языка

Locale.CANADA

Locale.CHINA

Locale.CHINESE

Locale.ENGLISH

Locale.FRANCE

Locale.GERMANY

Locale.ITALY

Locale.JAPAN

Locale.KOREA

Locale.SIMPLIFIED_CHINESE

Locale.TAIWAN TW

Locale.TRADITIONAL_CHINESE

Locale.UK UK en

Locale.US US en

CACN

zh—FR

DEITJPKP

enzh

en

fr

de

it

jako

Page 199: Java Server Pages

202 Глава 8.118N

Константы, перечисленные в табл. 8.1, избавляют пользователя от необходимостисоздания объектов Locale. Например, каждое из приведенных ниже выражений мо-жет использоваться для представления языка United States English.

Locale locale_l =• new Locale("US", "en");Locale locale_2 = Locale.US;

Часто для определения региона используется только название страны, однако вклассе Locale отсутствует конструктор с одним параметром. Если вы хотите создатьобъект Locale, указывая только страну, то вместо кодового обозначения языка надозадать пустую строку, например:

// Создание объекта Locale для БолгарииLocale locale - new LocaleC'BG", "") ;

В спецификации Servlet 2.2 к классу ServletResponse добавлены методыsetLocale и getLocale. Метод setLocale устанавливает поле заголовка Content-Language и задает кодировку, соответствующую содержимому ответа. В листинге 8.3демонстрируется использование данного метода для установки региона. Этот примерныполнят тс ж<_- действия, что и код и листинге Н.Ч, и котором поле заголовкаContent-Language и кодировка устанавливаются явно.

Листинг 8.3. Установка данных о регионе в составе ответа

<htmlxheadxtitle>Character Sets</title><% response.setLocale(java.util.Locale.KOREAN); %>

</head><body>

<%= "\uc548\ubl55\ud558\ucl38\uc694" %>

</body></html>

Внешне данный документ выглядит так, как показано на рис. 8.2.

Наборы ресурсовТеперь вы знаете, как использовать управляющие последовательности Unicode

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

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

Page 200: Java Server Pages

Наборы ресурсов 203

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

Набор ресурсов, поддерживаемый посредством класса j ava. u t i l . Resource-Bundle, представляет собой хэш-таблицу, в которой содержатся пары ключ-зна-чение. Ключом набора ресурсов является строка, которая однозначно идентифици-рует ресурс. Значением может быть любой Java-объект, однако чаще всего в качествезначения применяется строка или массив строк, представляющие информацию дляконкретного региона. Наиболее часто используемые методы класса ResourceBundleперечислены ниже.

• s t a t i c ResourceBundle getBundle(String base)

• s t a t i c ResourceBundle getBundle[String base, Locale)

• Object getObject(Str ing key)

• Str ing g e t S t r i n g ( S t r i n g key)

• Str ing[] getStr ingArray(Str ing key)

Первые два из приведенных выше методов, объявленные как s t a t i c , возвращаютобъект ResourceBuncile. Первый метод начинает поиск, используя регион по умол-чанию, а при вызове второго метода ему передается объект Locale. Если набор ре-сурсов, связанный с указанным регионом, не может быть найден, второй метод про-должает поиск с использованием региона по умолчанию.

Оба указанных метода ищут наборы ресурсов с именами, представленными в фор-мате package.Bundle_la_C0_va, где package.Bundle— это базовое имя, котороезадается посредством параметра base, la — двухбуквенное обозначение языка, СО —двухбуквенное обозначение страны, a v a - список вариантов (элементы списка разде-лены знаками подчеркивания). Если поиск заканчивается неудачей, последний ком-понент отбрасывается и поиск повторяется. Для примера рассмотрим следующее вы-ражение:

ResourceBundle bundle = ResourceBundle.getBundle("Resources",new Locale("fr","CH"));

При выполнении приведенного выше фрагмента кода начинается поиск набораресурсов для Swiss French (французский (Швейцария)). Если регион по умолчаниюсоответствует Australian English {английский (Австралия)), самая длинная из возмож-ных процедур поиска будет выглядеть следующим образом:

Resources_fг_СНResources_frResources_en_AUResources_enResources

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

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

Page 201: Java Server Pages

204 Глава 8. MSN

в приведенных ниже выражениях из набора ресурсов извлекаются соответственнообъекты Object, Str ing и массив объектов String.

Object object = bundle.getObject("key_l");String string = bundle.getString("key_2");String[] array = bundle.getStringArray("key_3");

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

Набор ресурсов, возвращаемый методом ResourceBundle .getBundle, можетбыть либо экземпляром класса ListResourceBundle, либо экземпляром Property-ResourceBundle. В последующих разделах рассказывается о создании и использова-нии наборов ресурсов этих типов.

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

ResourceBundle. В листингах 8.4,а и 8.4,6 приведены примеры подклассов класса

ListResourceBundle.

Листинг в. 4,a. /WEB-INF/classes/Resources . Java.

import java.util.ListResourceBundle;

// "Аварийный" набор ресурсов, не связанный// с конкретным регионом.

public class Resources extends ListResourceBundle {static final Object[][] contents = {

{ "message.welcome", "Welcome" ),{ "picture.flag", "graphics/usa_flag.gif" ),

>;public Object[][] getContents() {

return contents;

1I

Листинг 8.4,6. /WEB-INF/classes/Reaources_fr. Java

import java.util.ListResourceBundle;

// Набор ресурсов для региона Франции.

public class Resources_fr extends ListResourceBundle {static final Object [][] contents = {

{ "message.welcome", "Bienvenue" ),1 "picture.flag", "graphics/french_flag.gif" }

};

public Object[][] getContents0 {

Page 202: Java Server Pages

Наборы ресурсов 205

return contents;

Набор ресурсов, представленный в листинге 8.4,а, возвращается в том случае, еслидругие наборы не найдены. Метод getBundle обнаруживает его по совпадению име-ни класса с базовым именем. Такой набор ресурсов называют "аварийным" набором,поскольку он представляет собой последнюю возможность для обработки запроса.В листинге 8.4,6 приведен набор ресурсов для объекта Locale, соответствующегоФранции; имя класса заканчивается символами _£г,

В обоих представленных выше наборах ресурсов содержатся приветственное со-общение и ссылка на изображение в формате GIF. Оба набора ресурсов реализуютgetContents — единственный абстрактный метод, объявленный в классе L i s t -ResourceBundle. Этот метод возвращает массив пар ключ-значение.

На рис. 8.3 показаны JSP-документы, в которых используются рассмотренные вы-ше наборы ресурсов.

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

! pit BH И» FfcwrHe Joob

•Atfduwj

\

Select a Language:

1 English ^|

Show Massage

1 1 •

j

P-Мсга

pki Edit Jltw Families Iwb

Welcome

i•Tx Local hUanet

e'lUmgRMejiolSmfc. With JSP. Мвяч*

j Ella Edit J£i«r Tfmni»' i « t

j 4dmi |Щ hltpi/ftKshBstBMO/il&vtMiAM

Select a Language:

| French ^J

Show Message

Ь] ••> Lr

Л.й,.р^| ^Go j

cdrtrewt

^ м и Buxln V* JSP • Mcnnll lni«m« E«... j j n i j t j

£dlt yigw FflrtifltB . look

IIBienvenue

Рис. S.3. Использование класса ListResourceBundle

Page 203: Java Server Pages

206 Главав. I18N

Код JSP-документа, показанного на рис. 8,3 слева, представлен в листинге 8.4,в.

Листинг8,4,в. /test.jsp

<html><title>Using Resource Bundles With JSP</title><head><%@ page import='Java.uti l .Locale' %></head><body>

<font size=M'>3elect a Language:</font>

<form action='showMessage.jsp'><select name='language'>

<option value='en'x%= Locale.ENGLISH.getDisplayNameO %><option value='f r'><%= Locale.FRENCH.getDisplayName() %>

</select><pxinput type='submit' value='Show Message'/></p>

</form><7p></body></html>

JSP-докумеит, показанный на рис. 8.4,в, использует для заполнения раскрывающе-гося списка метод Locale . getDisplaybiame. Этот метод возвращает строку, завися-щую от региона по умолчанию. Если по умолчанию установлен регион en_US, методgetDisplayName возвращает строку "English", а для региона f r возвращается стро-ка "anglais".

В качестве значения атрибута act ion формы, содержащейся в листинге 8.4,в, ука-зан документ showMessage . j sp, код которого приведен в листинге 8.4,г.

Листинг8.4,Г. /showMessage. jap

<html><title>Using Resource Bundles With JSF</title><head>

<%9 page import='Java.util.Locale' %><%@ page impoi:t='Java.util.ResourceBundle' %>

</head><body>

<% String language = request.getParameter("language");Locale locale = new Java.util.Locale(language, " " ) ;ResourceBundle bundle •»

ResourceBundle.getBundle("Resources", locale);%>

<img src='<%= bundle.getString("picture.flag") %>'>

<font size='4'>

<b= bundle.getString("message,welcome") %></font>

</body></html>

Page 204: Java Server Pages

Наборы ресурсов 207

JSP-документ, представленный в листинге 8.4,г, получает в качестве параметра запросавыбранный язык и использует его для создания объекта Locale. Далее этот объект указы-вается в качестве параметра метода getBundle, возвращающего набор ресурсов. Наборресурсов позволяет получить URL изображения и приветственное сообщение.

Поскольку набор ресурсов, приведенный в листинге 8.4,а, представляет собой"аварийный" набор, он используется для любого языка, кроме французского. Если вкачестве параметра запроса будет указан испанский язык (Spanish), в окне все равноотобразятся американский флаг и приветственное сообщение на английском «зыке.Это произойдет потому, что набор ресурсов для региона Spanish отсутствует. Еслибы имя класса было не Resources, a Re s o u r c e s__en, этот набор соответствовал бытолько английскому языку, однако в этом случае мы бы остались без "аварийного" на-бора и при указании в качестве параметра запроса испанского языка было бы сгене-рировано исключение Miss ingResourceExcept ion.

Использование констант в качестве ключевыхзначений

В наборах ресурсов, приведенных в листингах 8.4,а и 8.4,6, в качестве ключей ис-пользуются С1роковые литералы; соответственно для доступа к значениям, связанным сэтими ключами, JSP-документ, показанный в листинге 8.4,г, использует строки симво-лов. Применение строковых литералов означает, что ошибка в строке будет обнаруженатолько во время выполнения приложения, когда при очередном обращении возникнетисключение MissingResourceException. Чтобы допущенные ошибки выявлялиськомпилятором, вместо строковых литералов надо использовать константы.

JSP-документ, содержащийся в листинге 8.5,а, представляет собой новый вариантдокумента, приведенного ранее в листинге 8.4,г.

Листинг 8.5,a. /showMessage.jsp

< h t m l x t i t l e > U s i n g Resource Bundles With J S P < / t i t l e ><head>

<%@ page i m p o r t ^ ' J a v a . u t i l . L o c a l e ' %><%@ page i m p o r t = ' J a v a . u t i l . R e s o u t c e B u n d l e ' %>

</head><body>

<% S t r i n g language = r e q u e s t . g e t P a r a m e t e r ( " l a n g u a g e " ) ;Locale l o c a l e = new J a v a . u t i l . L o c a l e ( l a n g u a g e , " " ) ;ResourceBundle bundle •

ResourceBundle.getBundle ("Resources", l o c a l e ) ,-%>

<img src='<%= bundle.getString(Resources.FIAG_PICTURE) %>'>

<font s i z e = ' 4 ' ><%= bundle.getString{Resources.WELCOME) %>

</font>

</body></html>

Page 205: Java Server Pages

208 Глава 8.118N

В данном примере вместо строковых литералов методу ResourceBundle .Get-Str ing передаются строковые константы. Эти константы определены в классеResources, который используется для создания "аварийного" набора ресурсов. Кодкласса Resources приведен в листинге 8.5,6,

ЛИСТИНГ 8.5,6. /WEB- INF/classes /Resource в , Java

import java.util.ListResourceBundle;

// Данный набор ресурсов не связан с конкретным регионом,// поэтому он играет роль "аварийного" набора.

public class Resources extends ListResourceBundle (public s t a t i c final String WELCOME = "message.welcome";public s t a t i c final String FLAG_PICTURE = "picture . f lag" ;

s t a t i c f inal Object!)[] contents щ {( WELCOME, "Welcome" ),{ FLAG_PICTURE, "graphics/usa_£lag.gif" ),

);public Object[][] getContents () {

return contents;

Новая версия набора ресурсов, представленного ранее в листинге 8.4,6, содержит-ся в листинге 8.5,в. В нем, как и в классе Resources и в JSP-документе, вместо строко-вых литералов используются константы.

Листинг 8.5,в. /WEB- INP/ classes /Re sour ces_f r. Java С использованием

констант

import Java.util.ListResourceBundle;

// Набор ресурсов для региона Франции.

public class Resources_fr extends ListResourceBundle {static final Object!][] contents • {

{ Resources.WELCOME, "Bienvenue" J,{ Resources. FLAG__PICTURE, "graphics/f rench_flag. gif' }

I ;public Object!)[] getContents() (

return contents;

}

Класс PropertyResourceBundleВместо реализации набора ресурсов в виде Java-классов вы можете указывать пары

ключ-значение в файле свойств. Так, например, в листингах 8.6,а и 8.6,6 представле-но содержимое файлов свойств, которые заменяют Java-классы, приведенные ранее влистингах 8.4,а и 8.4,6.

Page 206: Java Server Pages

Наборы ресурсов 209

Листинг8.6,a. /WEB-INF/classes/Resources.properties

message.welcome^Welcomepicture.flag=usa_flag.gif

ЛИСТИНГ 8.6,6. /WEB-INF/claases/Resources_f r .properties

message.welcome-Bienvenuepicture.flag=french_flag.gif

Помимо поиска классов, соответствующих наборам ресурсов, метод Resource-Bundle . getBundle также ищет файлы свойств, содержащие пары ключ-значение вформате шк№=значение (классы, соответствующие («борам ресурсов, имеют более вы-сокий приоритет по сравнению с файлами свойств). Если файл свойств найден, соз-дается экземпляр класса PropertyResourceBundle, а данные из файла свойств ко-пируются в набор ресурсов, который возвращается методом ge tBundle.

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

Использование нескольких наборов ресурсовИнтернационализация приложений часто предполагает использование несколь-

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

В листинге 8.7 содержится код JSP-документа, который выполняет те же функции,что и документ, представленный в листинге 8.4,г; отличается новый документ тем,что в нем для сообщения и для URL изображений используются различные наборыресурсов.

Листинг 8.7,а. Использование нескольких наборов ресурсов

<htmlxtitle>Using Resource Bundles With JSP</title><head>

page import='java.util.Locale' %>page import=' java.util.ResourceBundle' %>

</head><body>

Page 207: Java Server Pages

210 Глава 8.118N

<% String language = request.getParameter("language");Locale locale = new java.util.Locale{language, " " ) ;ResourceBundle mes3ages_bundle =

ResourceBundle.getBundle("messages",locale);ResourceBundle piatures_bundle =

ResourceBundle.getBundle("pictures", locale);%>

<img src='<%= pictures_bundle.getString("pictures.flag") %>'>

<font size='4'><%= messages_bundle.getString("message.welcome") %>

</font>

</body></html>

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

Кэш наборовJSP-документ, представленный в листинге 8.7,6, идентичен по своим функциям до-

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

Листинг 8.7,6. Использование кэша наборов

<html><title>Using Resource Bundles With JSP</title><head><%@ page import=

fjava.util.Locale' %></head>

<body>

<jsp:useBean ici='bundleCache' scope-'request'class=' beans.ilSn.BundleCache'/>

<% String language » request .getParameter ( "language") ,-Locale locale - new java.util.Locale(language, " " ) ; £>

<img src='<%= bundleCaone.getString("pictures", locale,"pictures.flag") %>'>

<font size='4'><%= bundleCache.getStringC'messages", locale,

"message.welcome") %></font>

</body></html>

Page 208: Java Server Pages

Наборы ресурсов 211

Кэш наборов предоставляет доступ к нескольким наборам ресурсов, не ссылаясь наэти наборы непосредственно.

Код класса, реализующего кэш наборов, приведен в листинге 8.7,в.

ЛИСТИНГ 8.7,В. /WEB-INF/clasaea/beans/ilBn/BundleCache. Java

package beans.ilBn;

import java.util.Hashtable;import java.util.Locale;import java.util.MissingResourceException;import Java.util.ResourceBundle;

public class BundleCache (private Hashtable bundles • new Hashtable ();

public Object getObject(String base, Locale locale, String key)throws MissingResourceException {

return getBundle(base, locale).getObject(key);)public String getString[String base, Locale locale, String key)

throws MissingResourceException {return getBundle(base, locale).getString(key);

}public String[] getStringArray(String base. Locale locale.

String key) throws MissingResourceException {return getBundle(base, locale).getStringArray(key);

}public ResourceBundle getBundle(String base, Locale locale)

throws MissingResourceException {String key - base + "_" + locale.toString();ResourceBundle bundle = (ResourceBundle)bundles.get(key);

if(bundle — null) (bundle •> ResourceBundle.getBundle(base, locale);bundles.put(bundle, key) ;

}return bundle;

}public void addBundle(ResourceBundle bundle, String base) (

Locale locale = bundle.getLocale ();bundles.put(bundle, base + "_" + locale.toString ());

\)

Кэш наборов содержит хэш-таблицу наборов ресурсов и предоставляет методы для

извлечения из этих наборов объектов, строк и строковых массивов, при этом непо-

средственная ссылка на набор не требуется.

Использование кэша наборов имеет два основных преимущества по сравнению с

непосредственным обращением к нескольким наборам ресурсов. Во-первых, код при-

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

ры. Во-вторых, извлечение данных происходит более эффективно, так как кэш набо-

ров поддерживает хэш-таблицу.

Page 209: Java Server Pages

212 Глава 8. MSN

Форматирование данных, чувствительныхк региону

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

Пакет j ava . t e x t содержит набор классов и интерфейсов, предназначенных дляпоиска и сортировки строк, а также форматирования объектов, чувствительных к ре-гиону. Подробное рассмотрение пакета J a v a , t e x t не входит в задачу данной книги,поэтому мы ограничимся обсуждением форматирования чисел, дат и сообщений.

Дата и времяПравила форматирования даты и времени для разных регионов могут различаться.

Например, в США дата представляется в формате mm/dd/yy, где mm— месяц, dd-день месяца, а уу — год. В Англии формат отображения даты выглядит так: dd/mm/yy.

Класс Java, t e x t . Date Format предоставляет многочисленные методы для фор-матирования и разбора дат, представленных в различных форматах. В этом классетакже содержатся перечисленные ниже статические методы, каждый из которых воз-вращает экземпляр класса DateFormat.

• s t a t i c DateFormat ge t lns tance ()

• s t a t i c DateFormat getDatelnstance[)

• s t a t i c DateFormat getDateTimelnstance()

• s t a t i c DateFormat getTimelnstancet)

Существует четыре формата представления даты и времени: короткий, средний,длинный и полный. Первый из указанных выше методов возвращает дату и время длярегиона по умолчанию; как дата, так и время представляются в коротком формате.Остальные три метода возвращают сведения для региона по умолчанию в формате,принятом по умолчанию. В классе DateFormat также содержатся варианты послед-них трех методов, позволяющие задавать регион и формат.

В классе DateFormat предусмотрены константы для определения формата. Этиконстанты перечислены в табл. 8.2.

Таблица 8.2.

Константа

SHORT

MEDIUM

LONG

FULL

Константы для определения формата

Описание Пример даты и времени для США

Короткий формат 10/19/00 6:07 РМ(числовое представление)

Средний формат

Длинный формат

Полный формат

Oct 19, 2000 6 :05 :55 РМ

October 19, 2000 6 :07:01 РМ MDT

Thursday, October 19, 20006 : 0 4 : 3 3 РМ MDT

Page 210: Java Server Pages

Форматирование данных, чувствительных к региону 213

На рис, 8,4 показан JSP-документ, который представляет дату и время в форматах,принятых в США, Англии, Франции и Японии.

£] Fotmafog D ie t and Ttnta -Microsoft Нети! Еиркхя

file Edit Slew Firm rites Tools Help

:Ad4sn|g] http://locislhDsta080/i1 en-dafe

English (United States): Friday. October 20.2000 3:45:26 PM MDT

French (France): vendredi 20 octobre 200015 h 45 MDT

Japanese (Japan): 2000^10Я 20 В 158$45#26$MDT

jj Done *•; Local rhtrri

Рис. 8.4. Форматирование даты и времени

Код JSF-документа, показанного на рис, 8,4, представлен в листинге 8.8,

Листинг 8.8. Форматирование даты и времени

<html><headxtitle>Format:ting Dates and Times</title><%@ page contentType=' text/html,- charset=UTF-8'%><%@ page import='Java.text.DateFormat' %><%@ page import='java.util,*' %></head><body>

<% DateFormat frenchFormat - DateFormat.getDateTimelnstance(DateFormat.FULL,DateFormat.FULL,Locale.FRANCE);

DateFormat englishFormat = DateFormat.getDateTimelnstance(DateFormat.FULL,DateFormat.FULL,Locale.US);

DateFormat japaneseFormat = DateFormat.getDateTimelnstanceDateFormat.FULL,DateFormat.FULL,Locale.JAPAN); %>

<font size='4'><%= Locale.US.getDisplayNameO %>:<%= englishFormat.format(new Date(J) %><p>

<%= Locale.FRANCE.getDisplayNameО %>:<%= frenchFormat. format [new Date () ) %></pxp>

<l= Locale.JAPAN.getDisplayName() *>:<%-= japaneseFormat.formatfnew Date()] %></p>

</font>

</body></html>

Page 211: Java Server Pages

214 Глава 8.118N

JSP-документ, показанный в листинге 8.8, получает объект DateFormat, причемкак для даты, так и для времени указывается полный формат (FULL). Методу formatэтого класса передается дата, в результате метод format возвращает форматирован-ную строку, представляющую дату и время.

JSP-документ, показанный на рис. 8.4, также иллюстрирует отображение текста нанескольких языках с использованием кодировки UTF-8.

Числа, денежные единицы и процентыПодобно дате и времени, числа, денежные единицы и проценты также могут форма-

тироваться по-разному для различных регионов. В состав пакета Java, text входиткласс NumberFormat, содержащий методы для форматирования и разбора чисел. Как иDateFormat, Number Format предоставляет статические методы, перечисленные ниже.

• s t a t i c KumberFormat get lnstance()• s t a t i c NumberFormat getCurrencylnstance()• s t a t i c NumberFormat getNumberlnstance()• s t a t i c NumberFormat getPercent lnstance()

Метод getNumberlnstance возвращает универсальный формат для региона поумолчанию. Подобно DateFormat, класс NumberFormat предоставляет варианты по-следних трех методов, при вызове которых задается регион. Например, обращениеgetNumberlnstance () предполагает работу с регионом по умолчанию, a getNumber-lnstance (Locale) - с регионом, указанным в качестве параметра. В отличие от датыи времени, формат чисел не подразделяется на короткий, средний, длинный и полный.

В листинге 8.9 показан JSP-документ, отображающий числа в форматах, принятыхв Англии и Германии, английские и японские денежные единицы, а также процентыдля Англии и Кореи. Внешний вид этого документа представлен на рис. 8.5.

j File £d,t View Fsvofltei Toot Help

I АДОвя | Ё ] htip //loeaihosi 8Q8Q/j1 Bn™mtimAesl isp 2]

Numbers:English (United States): 150,012.88German (Germany): 150 012.88

Currency:English (United States): $29.99Japanese (Japan): ¥30

Percents:English (United States): 73%Korean (South Korea): 737a

ЩоЗ* & Local nlionet

Ряс. 8.5. Форматирование чисел, денежных единиц ипроцентов

Page 212: Java Server Pages

Форматирование данных, чувствительных к региону 215

В листинге 8.9 представлен код JSP-докумеита, показанного на рис. 8.5.

Листинг 8.9. Пример форматирования чисел, денежных единиц и процентов

<htmlxhead><title>Number. Currency and Percent Formatting</title><%@ page contentType='text/html; charset=UTF-8' %><l@ page import='Java,text.*' %><l@ page import='java.util.Locale' %></head><body><*

NumberFormat enjnumber_fmt =NumberFormat.getNumberInstance(Locale.US);

NumberFormat fr_number_fmt —NumberFormat.getNumberInstance(Locale.GERMANY);

NumberFormat en__currency_fmt -NumberFormat.getCurrencyInstance{Locale.US);

NumberFormat fr_currency_fmt =NumberFormat.getCurrencylnstance(Locale.JAPAN);

NumberFormat en_percent_fmt =NumberFormat.getPercentInstance(Locale.US);

NumberFormat fr_percent_fmt =NumberFormat.getPercentInstance{Locale.KOREA);

<font size='4'>Numbers:<br><%= Locale.US.getDisplayName() %>:<%= en_number_fmt.format(150012.88) %><br><%= Locale.GERMANY.getDisplayName() %>:<%= fr_number_fmt.format{150012.88} %>

<p>Currency:<br><%= Locale.US.getDisplayName() %>:<*= en_currency_fmt.format(29.99) %><br><%= Locale.JAPAN.getDisplayName() %>:<%= fr_currency_fmt. format {29. 99) %x/p>

<p>Percents:<br>

<%- Locale.US.getDisplayName() %>:<%= en_percent_fmt.format(.73) %><br><%= Locale.KOREA.getDisplayName() %>:<%= fr__percent_fmt.format (.73) %></p>

</font>

</body></html>

В рассматриваемом примере создаются объекты NumberFormat для форматиро-вания чисел, денежных единиц и процентов. Затем с помощью метода format классаNumberFormat выводятся соответствующие данные.

Page 213: Java Server Pages

216 ГлаваЗ. I18N

СообщенияФразу "I'm going to store" ("Я иду в магазин") можно записать следующим образом;

"I'm going to {0}", где {0} представляет store (магазин). Аналогично, вы можете запи-сать "I'm going to {0} to get {1}" ("Я иду в {0} за {!}").

В данном случае вы применяете средство под названием параметризованныйтекст3. В частности, в английском варианте данной книги неоднократно использова-лось выражение "TheJSP page shown in {0} is listed in {1}" {КодJSP-документа, пока-занный на {0), приведен в {1}.)

Поддержка параметризованного текста осуществляется с помощью классаJava . t e x t .MessageFormat. Для использования этого класса надо задать шаблон(строку, содержащую {0}, [1], ••• ,{п})и массив значений параметров.

MessageFormat fmt = new MessageFormat("I'm going to {0] for { I ! " ) ;Object [] parameters = {"the d r u g s t o r e " , " r a z o r s " } ; %>

При вызове конструктора MessageFormat ему передается шаблон. Данный щаб*лом обрабатывается в процессе выполнения метода MessageForraat. format, приэтом выражения {п} заменяются реальными значениями параметров. Например, ес-ли экземпляр класса MessageFormat и массив значений заданы так, как показано вы-ше, то строка formattedStrirtg в следующем выражении примет значение "I'mgoing lo drugstore for razors".

Str ing formattedstring = fiat .format (parameters) ;

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

MessageFormat frat = new MessageFormat("I went to 10) on 11,date)");Object[] parameters = ("the drugstore", new J a v a . u t i l . D a t e () }; %>String formattedstring = fmt.format(parameters);

Для форматирования даты здесь используется стиль. Так, например, для датыможно задавать {n, t ime), а для числа— {n, percent} . При форматировании пара-метров, заданных с помощью стилей (например, {n, time}), используется соответ-ствующий формат даты или числа. При этом форматирование выполняется с учетомрегиона, который можно указать посредством метода MessageFormat. setLocale.Если вы зададите регион для формата сообщения, вам необходимо вызвать методHessageFormat. applyPattern, который обновит форматы в соответствии с вновьустановленным регионом.

На рис. 8.6 показан JSP-документ, который показывает сообщение с параметрами,В качестве параметров используются число, дата и время.

Приложение, представленное на рисунке, состоит из двух JSP-документов; один изних позволяет выбрать язык (регион), а другой выводит параметризованное сообще-ние. Код документа, который отображается в окнах, расположенных слева, приведенвлистинге8.10,а.

2 В русском языке из-за наличия падежей преимущества параметризованного текста практиче-ски полностью теряются. -Прим. ред.

Page 214: Java Server Pages

Форматирование данных, чувствительных к региону 217

^FomettiroMttsasej-Miaasoit Internet Еиркга _ j П | х|

] £ ib Edit View Favorites Toolt fktlp

А£А е я |£] ocddhosieOBOMBn-rriEssages/lesLisp^ (^Go •

dSelects Language:1 !

| Spanish zi

Show Message ... |_d

isJFormaeingfcleMagej-MiaOTrft Internal Enototer , - j E j *l

; file Edit view Fgrarfl« IooEs Цд|р

:ДЙ4вв|е] hUpiy/locaho^SOSO/iiBn^messsges/J r'Go

Seiect a Language:

| English 21

Show Message

i,

tjFoimeHtngMewagej-M(cfoeo(tlWet™i€Kpkifet _ _ ! • ! x |

: File Edit View Fgrarllei Toots Vtolp

Adcbsw |J0J ages^5howMe3Eage.J3p?lartguage*es_^J t^Go |

Disque питёгоЗ rempii sur20:49:29 aviernes 27 de octubre de 200Q

1С Done Ji> Local intranet s,

^iFotmatling Hettsa« - Мв№5<1 Internet Enpfcmi _ ^ Ю ! J<J

File Edit View Fgvarttej ТмЬ Holp

: : A^dett j ^ J ages/3howMe?sdafi.js[)?tflrtqu^Qe-en T | iy Go

Disk number 3 filled up at 8:44:12 PM onOct27,2000

ИОН» Ti Lcealintieiel

РИС. S.6. Форматирование сообщений

Листинг8.10,a. /test.jsp

<htmlxtitle> Format ting Messages</title><head><%@ page import='Java.util.Locale' %></head>

<body>

<font size='4' coloc='blue'>Select a Language:</font>

<form action='showMessage.jsp'><select name='language' >

<option value='en'><%= (new Locale ("en","")).getDisplayName() %>

<option value='es'><%= (new Locale("es",""]).getDisplayName () %>

</select><pxinput type='submit' value='Show Message' /></p>

</form>

</body></html>

Page 215: Java Server Pages

218 Глава 8.118N

В данном документе содержится форма. В качестве значения атрибута actionэтой формы указан документ showMessage . jsp. Код этого документа представлен влистинге 8.10,6.

ЛИСТИНГ 8.10,6. /showMessage.jsp

<html><head><title>Formatting Messages</title><%@ page import='Java.text.MessageFormat' %><%9 page import^'java.util.Locale' %><%@ page import^'java.util.ResourceBundle' %>

</head><body>

<% // Получение ссылок на язык, регион и набор ресурсов.String language - request.getParameter("language");Locale locale " new java.util.Locale(language, " " ) ;ResourceBundle bundle ~ ResourceBundle.getBundle(

"message_formats", locale);

// Получение шаблона сообщения из набора ресурсов и ...String pattern • bundle.getString("message.diskFull");

// ... создание формата сообщения с использованием// строки-шаблона.MessageFormat fmt *• new MessageFormat(pattern);

// Когда шаблон применяется, параметры, приведенные ниже,II заменяют выражения {0} {1} ... {п} на// parameters [0] parameters[1] ... parameters[n]Object[] parameters = (new Long(3), new java.util.Date()}; %>

<font size=f 4'>

<% II Дата (parameters[1]) форматируется в соответствии// с данным регионом,fmt.setLoeale(locale) ;

II Включение параметров и форматирование.fmt.applyPattern (pattern) ,- %>

<%= fmt.format(parameters) %></p></font>

</body></html>

JSP-документ получает данные о формате из набора ресурсов, связанного с региономEnglish или Spanish. Строка-шаблон, определяющая формат, передается конструкторукласса MessageFormat, затем метод format форматирует параметры и включает их вшаблон. В частности, параметры заменяют выражения {О) и {1} в составе шаблона.

В листингах 8.10,в и 8.10,г показано содержимое файлов свойств для двух регионов.

Листинг 8.10,в. /WEB-INF/claases/app_arL.properties

message.formatstring=Disk number <b>{0}</b> on computer <b>{l)</b> is full

Page 216: Java Server Pages

Языки, поддерживаемые броузерами 219

Листинге.10,г. /WEB-INF/classes/app es.properties

message.formatString=El numero <b>{0}</b> de disco sobre la computadora el<b>U)</b> es lleno

Языки, поддерживаемые броузерамиНачиная с версии 4.0, и Netscape, и Internet Explorer дают возможность пользова-

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

Определение регионовЕсли пользователь установил в броузере список регионов, вам надо по возможности

учесть его пожелания. Для определения регионов, заданных в броузере, можно исполь-зовать методы ServletRequest.getLocales или ServletRequest.getLocale.Ме-тод getLocales возвращает объект Enumeration, содержащий регионы, заданныепользователем, а метод getLocale возвращает самый приоритетный регион.

На рис. 8.7 показаны диалоговое окно Language Preference броузера InternetExplorer и JSP-документ, который отображает языки, установленные пользователем.

Language Pidnencc .2Ш

Some Web stet offer vxtan in (n*pte languages. Von canchoose ieve<d linguegei below; Чту н4 te treated ri ordec Ыpurity.

Language

[ko]Fiencn(Fran«lili)Chinese [*]Croatian [hr|Сксп [cs]Danish [da]English [Urated S U I K I |en-u:]

6*1.

Menyt and duiog txjwjs eie cuih E ^ h t U d S )

Orat

^jDetecttigLocatetFiomBreiwtetSeWna!• MiciospdInter... ^

frte £d.t Visw Fovorltei look »b

Locales:

KoreanFrenchChineseCroatianCzechDanish

English (United Stales)

'f Local intrant

Рис. В. 7. Определение регионов, установленных в броузере

Код^Р-документа, показанного на рис. 8.7, представлен в листинге 8.11.

Листинг8.11. Определение регионов

<html><title>Detecting Locales From Browser Settings</title><head>

<%@ page import=' Java.util.Enumeration' %>

Page 217: Java Server Pages

220 Глава 8.118N

<%@ page Import^'Java.util.Locale'<%@ page import='Java.util.Vector'

</head><body>

%>%>

<font >Locales:</font>

<% Enumeration eri = request. getLocales {) ;

while(en.hasMoreElements[)} fLocale locale = (Locale)en.nextElement();<£= locale.getDisplayNam^O %><br>

%>

</body></html>

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

Получение набора ресурсовЗная регионы, предпочитаемые пользователями, вы можете использовать их для

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

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

p] Locattis Bunds» Given a Lit! ol

| Fi» £dit Klcw Firants

] Addren | g ] КИр./Лосгаюй-воа:

Locales:

FrenchEnglish ^United States)FinnishChineseCroatianCiechDanish

Got a bundle for English

Locate! • Micro

1 loots Щ

l/iiBn-bunde-toi

••• L^cdinlr

МЙ... - IDtxj

anet

0}LocMinoBuntto» Given a Lirt ol Locates - Miowcft... . IDl x|

j[ Eib ^ i t yisw Fjyorltes IOQIS №^

! •

Locales:

FrenchEnglish (United States)FinnishChineseCroatianCzechDanish

Got a bundle for Czech

I Dane "

Puc. 8.8. Выбор наборов ресурсов в соответствии с регионами, установленными в броузере

Page 218: Java Server Pages

Языки, поддерживаемые броузерами 221

Код JSP-документа, показанного на рис. 8.8, приведен в листинге 8.12,а.

Листинг8,12,a. / t e s t . j a p

<htmlxtitle>Locating Bundles Given a List of Locales</title>

!<head><ii taglib uri='/WEB-INF/tlds/il8n.tld' prefix='il8n' %><%@ page import='java.util.*' %></head>

<body>

<font size='4'>Locales:</font>

p

<4 Enumeration en = request.getLocales{);

while(en.hasMoreElements{)) {Locale locale = (Locale)en.nextElement(); %><%= locale.getDisplayName[) %><br>

<jsp:useBean id=' locator' scope='request'

class='beans.ilBn.BundleLocator' />

<% ResourceBundle bundle = locator.locateBundle(request, "app");

if(bundle == null) t %>No bundle found

<% ) else ( %>Got a bundle for: <%= bundle.getLocale().getDisplayName(} %>

<% } %>

</body></html>

Рассматриваемый JSP-документ получает регионы, установленные пользователем,используя для этого метод HttpServlet Re quest . getLocales. Каждый регион пе-редается конструктору класса BundleLocator и впоследствии используется для по-иска набора ресурсов.

Локатор наборов, реализуемый в виде класса BundleLocator, показан в листинге8.12,6.

ЛИСТИНГ 8.12,6. / WEB - INF/ classes /beans /il8n/B\indleLocato r. jiav a

package beans.ilSn;

import java.util.Enumeration;

import java.util.Locale;

import java.util.MissingResourceException;

import java.util.ResourceBundle,-

Page 219: Java Server Pages

222 Глава 8.118N

import javax.servlet.ServletRequest;import javax.servlet.jsp.JspException;

public class BundleLocator {public ResourceBundle locateBundle(ServletRequest request,

String base)throws JspException (

Enumeration en = request.getLocales();Locale defaultLocale - Locale.getDefault();ResourceBundle fallbackBundle = null;

try [fallbackBundle = ResourceBundle.getBundle(base,

defaultLocale);\catch(MissingResourceException ex) {

throw new JspException(ex.getMessage());

while(en.hasMoreElements ()) {Locale locale = (Locale)en.nextElement();ResourceBundle bundle = null;

try (bundle ~ ResourceBundle.getBundle(base, locale);

catch(MissingResourceException ex2) {

II пропущенные наборы игнорируются ...continue;

if(bundle != fallbackBundle)return bundle;

if (fallbackBundle !- null ss bundle =» fallbackBundle) (String lang - locale.getLanguage();String defaultLanguage - defaultLocale.getLanguage();

if(lang.equals(defaultLanguage)}return bundle;

return null;

Локатор наборов перебирает в цикле регионы и ищет наборы ресурсов, соответ-ствующие требованиям пользователя. Если для некоторого региона набор не найден,поиск продолжается для следующего региона. Если набор найден и он не является"аварийным" набором ресурсов, считается, что условия поиска выполнены. Если на-бор является "аварийным", регион представляет собой регион по умолчанию; в этомслучае также предполагается соответствие требованиям. Если набор ресурсов не най-ден, метод BundleLocator. locateBundle возвращает значение n u l l .

Page 220: Java Server Pages

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

Пользовательские дескрипторыJSP-документы, рассмотренные в этой главе, содержат Java-код, иллюстрирующий

процесс интернационализации приложений. Применяя пользовательские дескрип-торы message и format, вы можете удалить Java-код H3jSP-npKVMeHTOB.

Дескриптор messageДескриптор, предназначенный для извлечения строки из набора ресурсов, ис-

пользуется так, как показано ниже.

<%е t a g l i b u r i = ' / W E B - l N F / t l d s / i l 8 n . t l d p r e f i x = ' i l 8 n ' %>

<:il8n:message base=' Resources ' key='messages .welcome' />

<il8n:message base= F Resources ' key='messages.welcome'locale- '<%= Locale.UK %>'/>

Атрибут base представляет базовое имя набора ресурсов, например, если у васесть два набора— Resources_es . p r o p e r t i e s и Resources_f r . p r o p e r t i e s — ба-зовым именем является Resources . Атрибут key применяется для поиска сообщенияв наборе ресурсов.

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

Дескриптор message также выполняет форматирование сообщения. Для этогоиспользуется атрибут params, значением которого является массив объектов. Строкаиз набора ресурсов интерпретируется как шаблон, который форматируется с исполь-зованием содержимого массива params.

На рис. 8.9 показан JSP-документ, который использует все возможности дескрип-тора message. Этот документ отображает текст на нескольких языках с применениемкодировки UTF-8 и отображает данные в различных форматах, в зависимости от ус-тановок, заданных пользователем в броузере. Слева на рис, 8.9 показаны языки(регионы), выбранные пользователем, а соответствующие им данные отображаются вокнах, расположенных справа.

JSP-документ, показанный на рис. 8.9, содержит следующие строки:

<%@ t a g l i b u r i = ' / W E B - I N F / t l d s / i l 8 n . t l d ' p r e f i x = ' i l 8 n ' %><%@ page contentType="text/html; charset=UTF-8" %>

<il8n:message b a s e - ' a p p ' key='messages.welcome'/>

<il8n-.message base=' app' key='messages, i n t r o d u c t i o n 'locale='<%= Java.util.Locale.KOREAN %>'/>

<hr><il8n:messaqe base='app' key='messages.today'

locale='<%= Java.util.Locale.ENGLISH 4>'params='<%= new Object[] { new Java.util.Date () ) %>'/>

Page 221: Java Server Pages

224 Глава 8.118N

<ilSn:message key='errors.test' base='errors '/>

Some Web liCnotfc* corfcnt in mufciplc language* Vou unchooie tevqcd taguagn Wow: they *** be UMtod in otd« ofpriority.

Lano>«oe;

Crojtr#i|hr]Chinese t * ]Kenan PolEnglish [United S 1*1 es) [en-ut]Fiench [France] |«r)Czech [aiDanith [da]

MttMflttHHHBaMenui №d cUag bonn *t curgnVdiptatadnEnobh[UniledStelwl

OK

frtL

Chens»..

Cancel

- 1 t w i k n l u u i Mh JSP Си*» I IB> • Wo»

• WiilcjHfftuotmiii^^iiM.

Today i> 0:т 2В. 3000

riHML -Ml^l

D

™*« J г**" !

H

1

Some Web v'et oflff content TI mJtple LanguugeT. You cjnchoa» levoal langue$ei below, they и* be lie ned r adet cfpoa*li.

Cmalian |hi] Move Up

Kcrein |ko|English (Urted SlalesllHUJt!Ci»ch ( « )

Move [town

D^hldal ^

Menus and dWog bowt an currentV deployed r h , — - .kiEn*Ml)ri«JSrti™l i ienne...

OK Cancel

«-•' INofMcr^utcn i * i JSP Curtan Ti

Bonjour

TodJj i iOct ia.ZIOO

i > i m s s ^ s g n f l ^ r r c i i f <Cce.ii

-^J Done

^ • Hicio«*lrt*n»<£_. _ | D | К

ob ДО D |

i

J

РИС. fi.9. Использование дескриптора message

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

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

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

WEB-INF/classes/app_en.propertiesWEB-INF/classes/app_fr.propertiesWEB-INF/classes/app_ko.properties

WEB-INF/classes/errors_fr.propertiesWEB-INF/classes/errors_en.properties

Page 222: Java Server Pages

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

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

Для последнего дескриптора наилучшим выбором является английский язык. Длябазового имени e r r o r s существуют только два файла свойств с наборами ресурсов; изних English расположен в списке приоритетов выше, чем French.

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

В листингах 8.12,в-8.12,ж, приведены файлы свойств, содержащие наборы ресур-сов. Эти наборы ресурсов используются Java-документом, показанным на рис. 8.9.

Листинг 8.12,в. /WEB-INF/classes/app an.properties

messages.welcome=Good Morningmessages.introduction=Welcome to the meeting

Листинг8.12,г. /WEB-XNF/claases/app_£r.properties

messages.welcome=Bonjourmessages.introduction-Bienvenue a meeting

ЛИСТИНГ8.12,Д. /WEB-XNF/elasaes/appJco.properties

messages.introduction=\ud68c\uc758 \ucc38\uclld \uc744\ud658\uc601\ud569\ub2c8\ub2e4

Листинг 8.12,6. /WEB-XKF/classes/errors_en.properties

errors.test=A test error message

ЛИСТИНГ 8.12,Ж. /WEB-INF/classes/errora_fr.properties

errors.test=Un message d'erreur d'essai

Класс поддержки дескриптора message представлен в листинге 8.12,з.

Листинга. 12,3. /WEB-INF/classes/tags/ilSn/MessageTag. Java

package tags.i!8n;

import Java.text.MessageFormat;

Page 223: Java Server Pages

226 Глава 8. 118N

import java.util.Locale;import java.util.ResourceBundle;

import javax.servlet.jsp.JspException;

import javax.servlet.http.HttpServletRequest;import javax.servlet.jsp.tagext.TagSupport;

import beans.il8n.BundleLocator;import beans.il8n.BundleCache;

public class MessageTag extends TagSupport {private static BundleCache bundles = new BundleCache ();

private String base, key;private Locale locale;private Obj ect[] params;

public void setBase(String base) { this.base = base; )public void setLocale(Locale locale) { this.locale = locale; )public void setKey(String key) { this.key = key; )public void setParams(Object[] params) { this,params = params;}

public int doEndTag() throws JspException {MessageFormat fmt = null;String message • null;

ifflocale !- null)message = bundles.getstring(base, locale, key);

if(message == null)message = getMessageUsingBrowserLocales();

iffparams != null) {fmt = new MessageFormat(message);

ifdocale != null) {fmt.setLocale(locale);fmt.applyPattern(message);

)message = frat.format[params);

}try (

pageContext.getOut().write(message);}catch(Exception ex) ( // lOException или

// MissingResourceExceptionthrow new JspException(ex.getMessage());

)return EVAL_PAGE;

)private String getMessageUsingBrowserLocales()

throws JspException (String message = null;BundleLocator locator = new BundleLocator();ResourceBundle bundle = locator.locateBundle (

Page 224: Java Server Pages

if[bundle != null) (bundles.addBundle(bundle, base);message = bundle.getString(key);

1return message;

// Подготовка к повторному использованию

public void release() (base = key = null;locale = null;params = null;

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

pageContext.getRequest(), base);

В дескрипторе message предусмотрены четыре атрибута: base, key, loca le иparams. Первые два атрибута являются обязательными, а последние два могут отсут-ствовать. Дескриптор message выполняет следующие действия.

• Находит набор ресурсов и извлекает из него строку символов.

• При необходимости форматирует строку, полученную на предыдущем шаге.

• Выводит строку, пользуясь предопределенной переменной out.

При наличии атрибута locale кэш дескрипторов предпринимает попытку найтинабор ресурсов для указанного региона. Если набор найден, возвращается строка, со-ответствующая ключевому значению, заданному с помощью атрибута key. Если ре-сурс не найден или если ключ в наборе отсутствует, возвращается значение n u l l .Подробно работа кэша наборов была описана выше в данной главе.

Если кэш наборов не может найти строку, вызывается метод getMessageUsing-BrowserLocales. Этот метод использует локатор наборов для поиска набора ресур-сов в соответствии с приоритетами, определенными пользователем. Компонент bean,исполняющий роль локатора ресурсов, был рассмотрен ранее в этой главе.

Если задан атрибут params, дескриптор message форматирует строку, получен-ную из набора ресурсов.

И, наконец, последнее, что делает дескриптор message — выводит строку, исполь-зуя для этого предопределенную переменную out.

Дескриптор formatДескриптор format, обсуждению которого посвящен данный раздел, предназначен

для форматирования чисел, обозначений денежных единиц, процентов, а также ин-формации о дате и времени. Данный дескриптор дополняет дескриптор message; вме-сте они предоставляют набор базовых средств для интернационализации приложений.

Два^Р-докумснта, использующие дескриптор format, показаны на рис. 8.10.

Page 225: Java Server Pages

228 Глава 8.118N

Currency:

English [United States): $134,588.99

E l f i s h (United Kingdom). £134.588 99

Numbers;

English (United Stales): 134.538 93

English (United Kingdom). 134.588.99

Percents:

English (United States): 99%

Korean 88%

*J

IJ

_

.-J

i:';CUKmr4.taHi»iFw«rtog.MK™*lnli«MI£<pk>« . ^JffljSJ

ГШ Edii J£-«. l ^ m t o дао» №|р

Times:

English (United States): 7:2115 PM

French:721:15PM

Chinese: 7:21:15 PM

Dates;English (United States)- Oct 28.2000

German: Samstag.IS. Oktcber2000

Chinese: 2 0 0 0 ^ 1 0 ^ 2 8 В

DateЯimes:English (United States): Oct 23,2000 7:21:15 PM

Italian Oct28,2O00 7:2V15PM

Japanese 2000 Ф1 OS 28 В 198#21^15WMDT

Рис. 8.10. Применение пользовательского дескриптора для форматированиячисел, даты и времени

JSP-документ, показанный в левом окне на рис. 8.10, использует дескрипformat следующим образом:

<i l8n: format currency='13458B.99'/>

<ilBn:format currency='134588.99' loca le='<$= Locale.UK %>'/>

<i l8n: format number^'1345B8.99'/>

<i l8n : format number^'134588.99' locale='<%= Locale.UK %>'/>

<i l8n : format percent=' .99 '/>

<i l8n : format percent=' .88 ' locale='<%= Locale.KOREAN %>'/>JSP-документ, показаний справа на рис. 8.10, содержит следующий код:<i l8n : format time='<%= new J a v a . u t i l . D a t e ( ) %>'/>

<i l8n : format time='<%= new J a v a . u t i l . D a t e О %>'locale='<%= java.uti l .Locale.FRENCH %>'/>

<il8n; format time='<%= new J a v a . u t i l . D a t e { ) %>'locale='<%= Java.uti l .Locale.CHINESE £>'

dateStyle='<%= Java.text.DateFormat.FULL %>'/>

<i l8n:format date='<%= new J a v a . u t i l . D a t e ( ) %>'/>

<il8n;focmat date='<%= new j a v a . u t i l . D a t e ( ) %/>dateStyle='<%= java.text.DateFormat.SHORT %>'/>

Page 226: Java Server Pages

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

<il8n:forroat date='<%= new Java.util.Date() %>'locale=

f<%= java.util.Locale.CHINESE %>'

dateStyle='<%= 3ava.text.DateFormat.FOLL %>'/>

<il8n:format dateTime='<£= new Java.util.Date(} %>'/>

<il9n:format dateTime='<%= new Java.util.Date (J %>'locale='<%= Java.util.Locale.JAPANESE %>'/>

<il8n:format dateTime='<%= new Java.util.Date() %>'locale»'<%= Java.util.Locale.JAPANESE %>'

timeStyle='<%= Java.text.DateFormat.FULL %>'dateStyle='<%= Java.text.DateForraat.FULL %>'/>

Класс поддержки дескриптора format приведен в листинге 8.13. Поскольку в де-

скрипторе format предусмотрены девять атрибутов, объем кода достаточно велик,

но разобраться в его работе несложно.

ЛИСТИНГ 8.13./WE8-INE7classea/tag3/il8n/FormatTag

package tags.il8n;

import Java.text.*;import Java.util.Date;import Java.util.Locale;import javax.servlet.jsp.JspException;import javax.servlet.jsp.tagext.*;

public class FormatTag extends TagSupport {private static DateFormat dateFormat;private static NumberFormat numberFormat;private static final int UNASSIGNED=-1,

TIME=0, DATE=1, DATE_TIME=2y

NUMBER=3, CURRENCY=4, PERCENT=5;private Date date;private double number = (double)UNASSIGNED;

private Locale locale = null;

private int dateStyle - UNASSIGNED,timeStyle = UNASSIGNED,

formatStyle - UNASSIGNED,numberStyle = UNASSIGNED;

// doEndTagO

public int doEndTag() throws JspException {if(date != null) { // атрибут date установлен

if(formatStyle == TIME) processTime();else if(formatStyle •= DATE) processDate();else if(formatStyle == DATEJTIME) processDateTime()

showFormatted[dateFormat.format(date));

Page 227: Java Server Pages

230 Глава 8.118N

if(number != UNASSIGNED) { // атрибут number установленif(numberStyle = NUMBER) processNumber();else if[numberStyle = CURRENCY) processCurrency() ;else if(numberStyle == PERCENT) processPercentt);

showFormatted(numberFormat.format(number));

)

return EVAL_PAGE;

)private void showFormatted(String string) throws JspException {

try (pageContext.getOut().write(string);

}catch(Exception ex) {

throw new JspException(ex.getMessage());

// Set-методы атрибутов.

public void setLocale(Locale locale)I this.locale • locale; }public void setDateStyle(int style) { this.dateStyle = style; )public void setTimeStyle(int style) { this.timeStyle = style; )

public void setNumber(Double number)(this.number •= number.doubleValue(); numberStyle = NUMBER;

)public void setCurrency(Double number) (

setNumber. (number) ; numberStyle = CURRENCY;}public void setPercent(Double number) {

setNumber(number); munberStyle = PERCENT;Ipublic void setDate(Date date) {

this.date = date; formatStyle = DATE;}public void setDateTime(Date date) {

this.date = date; formatStyle = DATEJTIME;)public void setTime(Date date) {

this.date = date; formatStyle = TIME;

}

// Методы, предназначенные для обработки атрибутов.

private void processNumber() (numberFormat = (locale = null) ?

NumberFormat.getNumberInstance () :NumberFormat.getNumberInstance(locale);

)

private void processCurrency() InumberFormat <• (locale == null) ?

NumberFormat.getCurrencylnstance() :NumberFormat.getCurrencylnstance(locale);

Iprivate void processPercent() {

Page 228: Java Server Pages

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

numberFormat = (locale == null) ?NumberFormat.getPercentInstance() :NumberFormat.getPercentInstance(locale);

>private void processTime() (

if(timeStyle = UNASSIGNED) {dateFormat = DateFormat.getTimelnstance();

>else (

dateFormat = (locale == null) ?DateFormat.getTimelnstance(timeStyle) :DateForraat.getTimelnstance(timeStyle,locale}

)>private void processDate() (

if(dateStyle == UNASSIGNED) (dateFormat = DateFormat.getDatelnstance();

}else t

dateFormat = (locale == null) ?DateFormat.getDatelnstance(dateStyle} :DateFormat.getDatelnstance(dateStyle,locale)

private void processDateTime() throws JspException (if(timeStyle == UNASSIGNED Si dateStyle — UNASSIGNED) {

dateFormat - DateFormat.getDateTimeInstance();}else {

if(timeStyle == UNASSIGNED N dateStyle == UNASSIGNED) {throw new JspException ("You must assign both" +

"timeStyle and dateStyle, or" +"neither");

}dateFormat = (locale == null) ?

DateFormat.getDateTimeInstance(timeStyle, dateStyle) :

DateFormat.getDateTimelnstance(timeStyle, dateStyle, locale);

11 Подготовка к повторному использованию.

public void release() (numberFormat = nul l ;dateFormat = nul l ;

date = null;number = (double)UNASSIGNED;locale = null;

dateStyle = timeStyle -

formatStyle = numberStyle = UNASSIGNED;

Page 229: Java Server Pages

232 Глава 8.118N

Основные действия выполняются в теле метода FomTag.doEndTag. В зависимо-сти от значений атрибутов d a t e и number, doEndTag вызывает метод, который соз-дает объект, представляющий соответствующий формат. Этот объект используетсядля форматирования данных.

РезюмеПрочитав данную главу, вы, вероятно, сделали выводы о том, что в языке про-

граммирования Java содержатся мощные средства, позволяющие выполнять интер-национализацию приложений. Эти средства в сочетании с пользовательскими деск-рипторами предоставляют авторам Web-страниц возможность создавать современ-ные Web-узлы, используя привычные имструменты.

По мере развития World Wide Web все большее внимание уделяется вопросам ин-тернационализации и безо пас ЕГОСТИ. Многие компании, перенося существующиеприложения в среду Web, убедились, что их интернационализация — сложная задача,гораздо проще реализовывать поддержку регионов при написании программ. Как этосделать, вы узнали из данной главы.

Page 230: Java Server Pages

ЗАШИТА

В этой главе...

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

- Принципалы и роли.

- Декларативная аутентификация.- Переносимость.

j* - Типы аутентификации.

• Базовая аутентификация.

• Дайджест-аутентификация.

• Аутентификация на основе форм.

• Использование SSL и сертификата клиента.

• Настройка процедуры аутентификации.

- Сервер Resin.

- Сервер Tomcat 4.O.

• Элементы защиты Web-приложений.

• Программная аутентификация.

Page 231: Java Server Pages

При слове компьютерная безопасность большинство читателей сразу же пред-ставят себе противоборство хакеров и системных администраторов. С разви-тием World Wide Web сюда добавились вопросы правильного размещения

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

Вопросам безопасности, в том числе средствам защиты Java, посвящены многиекниги, но данная глава не является их пересказом. Речь пойдет о защите Web-приложений и ресурсов с использованием механизма аутентификации, описанного вспецификации Servlet 2.2 (ссылки на соответствующие материалы можно найти поадресу http://java.3un.com/products/5ervlet/download.html).

АутентификацияПроцедура аутентификации, выполняемая сервлетом, достаточно проста и вклю-

чает в себя следующие действия.

1. Пользователь пытается получить доступ к защищенному ресурсу, например, кJSP-документу.

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

3. Если имя и пароль не позволяют идентифицировать пользователя, отобража-ется сообщение об ошибке и пользователь получает возможность повторноввести идентификационные данные.

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

Page 232: Java Server Pages

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

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

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

Принципал может соответствовать одной или нескольким ролям, например по-требитель может быть, также и сотрудником. Ограничения, связанные с защитой, за-даются в файле WEB-INF/web. xml, в частности в этом файле роли связываются с за-щищенными ресурсами. Фрагменты файла web. xml приведены ниже.

<web-app>

<securifcy-constraint>< 1 — Защищенные Web-ресурсы —><web-resource-соllection>

<web-resource-name>Protected Resource</web-resource-name><ur1-pattern>/page_l.jsp</ur1-pattern>

</web-resource-collection>

<auth-constraint>< ! — Имена, идентифицирующие роли, которые позволяют

обращаться к указанным выше Web-ресурсам —><role-name>customer</role-name>

</auth-constraint></security-constraint>

<security-constraint>< ! — Защищенные Web-ресурсы —><web-resource-collection>

<web-resource-name>Protected Resource2</web-resource-name><url-pattern>/page_2.jsp</url-pattern>

</web-resource-collection>

<auth-constraint>< ! — Имена, идентифицирующие роли, которые позволяют

обращаться к указанным выше Web-ресурсам —><role-name>eniployee</role-name>

</auth-constraint></security-constraint>

<web-app>

В данном примере доступ к документам /page_l.jsp и /раде_2.j sp разрешен

только принципалам, которые принадлежат соответственно ролям customer и em-

ployee.

Принадлежность принципалов ролям выявляет контейнер сервлетов, в котором

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

ципалом и ролью. В сервере Tomcat для этого надо включить в файл tomcat-

users . xml запись наподобие следующей:

<tomcat-users>

<user name="rwhite" password="tomcat" roles="customer", "other"/>

</tomcat-users>

Page 233: Java Server Pages

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

Здесь задается принадлежность пользователя rwhite ролям customer и other;для идентификации данного пользователя применяется пароль tomcat. В соответст-вии с ограничениями, заданными выше, пользователь rwhite может обращаться к/page_l. j sp, но документ /page_2 , j sp ему недоступен.

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

В табл. 9.1 перечислены методы класса HttpServletRequest, позволяющие из-влекать информацию о принципалах и ролях.

Таблица 9.1. Методы класса HttpServletRequest для работыс принципалами и ролями

Метод Описание

Principal getQserPrincipal () Возвращает ссылку на объектJava . secur i ty .Pr incipa l

boolean isUserlnRole(String) Определяет, принадлежит ли пользовательроли, заданной посредством параметра

String getRemoteUser () Возвращает имя пользователя, которое былоуказано при регистрации

В составе API отсутствуют set-методы, соответствующие get-методам, представлен-ным в табл. 9.1. Это значит, что роли и принципалы может устанавливать только кон-тейнер сервлетов; приложения лишены возможности выполнять подобные действия.Этот факт может стать одним из аргументов в пользу программной аутентификации,которая будет рассмотрена далее в этой главе.

В табл. 9.2 приведены другие методы класса ServletRequest, которые предос-тавляют информацию, связанную с защитой.

Таблица 9.2. Методы класса ServletRequest, имеющие отношение кобеспечению безопасности {метод getAuthType принадлежит классуHttpServletRequest)

Метод Описание

String getAuthType () Возвращает тип аутентификации: BASIC, SSL или n u l l

boolean isSecure () Возвращает значение true, если соединение установ-лено посредством протокола HTTPS

String getSchemef) Возвращаемое значение представляет механизм пере-дачи данных: ht tp , https...

Page 234: Java Server Pages

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

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

Декларативная аутентификацияДекларативная аутентификация не требует создания дополнительного программ-

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

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

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

Примеры различных подходов к реализации процедуры аутентификации приве-дены далее в этой главе.

ПереносимостьСпецификация сервлетов не определяет ряд особенностей процедуры аутентифика-

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

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

Типы аутентификацииПри разработке Web-приложения на базе сервлетов могут быть использованы сле-

дующие средства аутентификации (они перечислены по возрастанию качества защиты).

" Базовая аутентификация.

• Аутентификация на основе форм.

• Дайджест-аутентификация.

• Аутентификация с использованием SSL и сертификата клиента.

Page 235: Java Server Pages

Базовая аутентификация 239

Все перечисленные типы аутентификации будут рассмотрены в данной главе.Подробные сведения о базовой аутентификации и дайджест-аутентификации можнонайти в документе RFC2617, расположенном по адресу f t p : / / f t p . i s i .edu/in-notes/rfc2617.txt .

Конкретный механизм аутентификации задается в файле /WEB-INF/web. xml так,как это показано ниже.

<web-app>

<login-config>

<auth-method>BASIC</auth-method><realm-name>Basic Authentication Example</realm-name>

</login-config>

</web-app>

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

Определить тип аутентификации можно с помощью метода HttpServletRequest.getAuthType.

Базовая аутентификацияМеханизм базовой аутентификации определен в спецификации HTML/1.1. Если

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

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

Процедура базовой аутентификации, выполняемая сервером Tomcat 4.0, показанана рис. 9.1.

Верхнее окно на рис. 9,1 соответствует попытке обратиться к защищенному доку-менту /protected-page, j sp, в результате которой отображается диалоговое окно,расположенное ниже и правее. После того как форма в диалоговом окне заполнена ипроведена аутентификация пользователя, отображается JSP-документ; на рисунке онпоказан в нижнем окне. Код JSP-документа приведен в листинге 9.1,а.

Листинг9.1,а. /protected-page.jsp

<htmlxheadxtitle>A Protected Page</title></head><body>

<S@ include file='show-security.jsp' %></p><p>

<% if(request.isUserlnRole("tomcat")) { %>You are in <i>tomcat</i> role<br/>

Page 236: Java Server Pages

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

<% } e l s e (%>You are <b>not</b> in <i>tomcat</ixbr/>

<% if(request.isUserlnRole("rolel")) ( %>You are in <i>rolel</ixbr/>

<% } else {%>You are <b>not</b> in <i>rolel</ixbr/>

</body></html>

fill £d.t Кчи fffnjilrn JeoSi a»

«1BI2J

El

Rtquti) Aulh»ntic»lBil with: BASIC,This connection It nol secure

Remoie Addr localhnstRemote Host localhoslRemote Addr 127 D.O.I

You ere in tomcat roleYou *ra net in TOief

Рис. 9. /. Базовая аутентификация, выполняемая сервером Tomcat 4.0

Рассматриваемый документ отображает некоторую информацию, в том числероль, которой принадлежит принципал: tomcat или r o l e l . В данный документвключается JSP-файл, содержимое которого представлено в листинге 9.1,6.

ЛистингЭ.1,6. /show-security.jsp

<font size='4' color='blue'>Security Information:

</font><br>

User principal: <%= request.getUserPrincipal[).getHame!) %>.<br/>User name: <%= request.getRemoteUser(} 4>,<br/>Request Authenticated with: <%= request.getAuthType() %>.<br/>

Page 237: Java Server Pages

Базовая аутентификация 241

<* i f ( r e q u e s t . i s S e c u r e ()) { %>This connect ion i s secure .<br/>

<t ) e l s e ( %>This connection is not secure.<br/>

<* } %>

Remote Addc: <l= request.getServerName() %><br/>Remote Host: <%= request.getRemoteHost() %><br/>Remote Addr: <%= request.getRemoteAddr() %>

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

Документ, представленный в листинге 9.1,а, объявлен в дескрипторе доставки при-ложения как защищенный ресурс. Дескриптор доставки приведен в листинге 9.1,в.

ЛИСТИНГЭ.1,В. /WEB-INF/web.xml

<?xml version="l.О" encoding="ISO-8859-l"?>

<!DOCTYPE web-appPUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN""http://Java.aun.com/j2ee/dtds/web-app_2.2.dtd">

<web-app><security-constraint>

<!— Защищенные Web-ресурсы --><web-resource-collection>

<web-resource-name>A Protected Page</web-resource-name><url-pattern>/protected-page.jsp</url-pattern>

</web-resource-collection>

<auth-constraint><!— Имена, идентифицирующие роли, которые позволяют

обращаться к указанным выше Web-ресурсам —><role-name>tomcat</role~name><role-name>rolel</role-name>

</auth-constraint></security-constraint>

<login-config><auth-method>BASIC</auth-method><realm-name>Basic Authentication Sxample</realm~name>

</login-config></web-app>

В данном дескрипторе доставки доступ к документу /protected-page . j sp огра-ничивается принципалами, принадлежащими ролям tomcat и ro le l . Здесь же объ-явлен тип аутентификации BASIC.

При работе с сервером Tomcat имена пользователей и пароли связываются с ро-лями в файле $TOMCAT_H0ME/conf/tomcat-users.xml, содержимое которого при-ведено влистинге 9.1, г.

Page 238: Java Server Pages

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

Листинг9.1,г. $ТОМСАТ HOME/eonf/tomcat-users .xml

<tomcat-users><user narae="tomcat" password="tomcat" roles="tomcat" /><user name="rolel" password="tomcat" roles="rolel" />•cuser name="both" password="tomcat" roles="tomcat,rolel" />

</tomcat-users>

В данном конфигурационном файле регистрационное имя tomcat и пароль tom-c a t , которые использовались в рассматриваемом приложении, связываются с рольюtomcat. По этой причине в окне на рис. 9.1 сообщается о том, что пользователь при-надлежит роли tomcat, но не роли r o l e l .

В файле, приведенном в листинге 9.1, г, также содержится запись для пользователяboth. Эта запись иллюстрирует принадлежность принципала двум ролям. Если бы в при-ложении, показанном на рис. 9.1, пользователь зарегистрировался как both, в окне ото-бразилось бы сообщение о том, что он принадлежит как роли tomcat, так и роли r o l e l .

Дайджест-аутентификацияДайджест^аутентификация напоминает базовую идентификацию, но для защиты

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

Рис. 9.2 иллюстрирует использование дайджест-аутентификации на сервере Tom-cat. Обратите внимание, что, в отличие от рис. 9.1, в диалоговом окне на рис. 9,2 вы-водится сообщение о том, что сервер защищен.

Для дайджест-аутентификации в дескрипторе доставки приложения задается типDIGEST.

<login-config><auth-method>DIGEST</auth-method><realm-name>Digest Authentication Example</realm-name>

</login-config></web-app>

Единственным отличием между базовой и дайджест-аутентификацией являетсяуказание типа.

Заметьте, что пример использования дайджест-аутентификации, рассмотренный вданном разделе, работает с Tomcat 4.0, но не работаете Tomcat 3.2.1.

Аутентификация на основе формАутентификация на основе форм позволяет контролировать внешний вид страни-

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

1 Дайджест-аутентификация оп/мделена в спецификации НТТР/1.1, которая представлена вдокументе ftp:/'/fllt.isi.edu/in-notes/rfc2617.txt. - П р и м . авт.

Page 239: Java Server Pages

Аутентификация на основе форм 243

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

SWIed Pa

] »••

j jAdjtfem |C] h»tp7/1oc

Tnii l e a n Web Sit (Jt todtmtj гянжм fou to too on

type Ux Ui« Name «id Patnmd Ibst ул им

Security Information.

User name. Iome at.Request Aulhenticated with: BASIC.This connection is not secure.

Remote Addn localhasiRemote Hosi- localhostRemote Mtiv 1 2 7 0 0 1

You 5r& m tomcat roleVou are not in rotef

РИС. 9.2. Использование дайджест-аутентификация при работе с сервером Tomcat

Как и базовая аутентификация, аутентификация на основе форм не обеспечиваетзащиты, так как пароль передается в незашифрованном виде. В отличие от базовой идайджест-аутентификации, аутентификация на основе форм определена не в специ-фикации HTTP, а в спецификации сервлетов.

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

Аутентификация на основе форм включает следующие действия.

1. Создание регистрационной Web-страницы.2. Создание страницы, которая должна отображаться в случае, если попытка ре-

гистрации оканчивается неудачей.3. Указание в дескрипторе доставки типа регистрации, а также документов, соз-

данных на первом и втором этапах.

Приложение, иллюстрирующее использование аутентификации на основе формы,показано на рис. 9.3.

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

Page 240: Java Server Pages

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

;••• 1 [.ogh Pige • М и м * Wane* Е ч*»в

Ed'' l i t " Т&огНп If»b

• Inlx;

AjJdun|jT] h«p//kKi*loil:

Please Login

Name: [badneme

Pes sword: r*~~

I

-HeromH internet Е*ркхя - I D l x l

hltprVkjcalhoilBOSflAocurly/fam/efra ^

3"he usemame and password you supplied are i inot valid.

Click 1 м а to ratty login

'•ЦлаЧИигЛ ,;

fill £dlr J l« .

Bl W«feoiiie - Mbwoft Irnmrf £4*1»

fill £dit sttw n n n i a lool:

• IBIxl

Please Login

Mams: | ID meat

Password: I

11

Welcome (om ca(

You are in fa meat role

You are not Inra/ef

You are not in tole2

' '•• Local ntunM

Рис. 9.З. Аутентификация на основе формы с использованием сервера Tomcat

Регистрационная форма, показанная на рис. 9.3, реализуется посредством доку-мента, код которого представлен в листинге 9.2,а.

Листинг9.2,a. /login.jap

<htmlxheadxtitle>Login Page</titlex/head><body><font size='5' color-'blue'>Please Login</font><hr>

<form action^'j_security_check' method-'post'><table><tr><td>Name:</td>

<tdxinput type-' text' name=' j_usemame' ></tdx/tr><trxtd>Password:</td>

<tdxinput type=' password' name-' j_password' size=' B' ></td></tr>

</table><br>

<input type='submit' valuer'login'></form></body></html>

Page 241: Java Server Pages

Аутентификация на основе форм 245

JSP-документ, приведенный выше, ничем не примечателен, за исключением имениформы и имен интерфейсных элементов. Имена j _ s e c u r i t y _ c h e c k , j_username иj_password определены в спецификации сервлетов и должны быть использованыпри создании формы, предназначенной для регистрации. Назначение этих имен опи-сано в табл. 9.3.

Таблица 9.3. Атрибуты формы, предназначенной для регистрации

Атрибут Описание

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

j_password Имя поля дли ввода пароля

i_security_check Значение атрибута action формы

Код Web-страницы, отображаемой при возникновении ошибки, приведен в лис-тинге 9.2,6.

Листинг9.2,6. /er ror . j sp

<html> <head> <title>Error! </titlex/head><body>

<font size='4' color=fredf>The username and password you supplied are not valid.

Click <a href='<%= response.encodeURL("login.jsp") %>'>here</a>to retry login

</body></form></html>

Данный документ отображает сообщение об ошибке и предоставляет ссылку наWeb-страннцу регистрации. Дескриптор доставки для рассматриваемого приложенияпредставлен в листинге 9.2,в.

ЛИСТИНГ 9.2,В. /WEB-INF/web.xml

version="1.0" encoding="ISD-88S9-l"?>

<!DOCTYPE web-appPUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//ЕЫ'"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">

<web-app><security-constraint>

<web-resource-collection><web-resource-name>A Protected Page</web-resource-name><url-pattern>/protected-page.jsp</url-pattern>

Page 242: Java Server Pages

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

</web-resource-collection>

<auth-constraint><role-name>tomcat</role-name>

</auth-constraint></security-constraint>

<login-config><auth-method>FOFM</auth-method><form-login-config>

<form-login-page>/login.jsp</form-login-page><form-error-page>/eri:or.jsp</form-error-page>

</form-login-config></login-config>

</web-app>

Дескриптор доставки, приведенный в листинге 9.2,в, ограничивает доступ к доку-менту protected-page . j sp. Обращаться к этому документу могут только принципа-лы, принадлежащие роли tomcat. В данном дескрипторе объявлен тип аутентифика-ции FORM, а также указаны Web-страницы, предназначенные для регистрации и ото-бражения ошибок.

Использование SSL и сертификата клиентаSSL {Secure socket layer— уровень защищенного гнезда) — это защищенный транс-

портный механизм, обеспечивающий безопасность данных и их целостность посред-ством шифрования. Кроме того, SSL выполняет идентификацию клиента и сервера.Дополнительную информацию о SSL можно найти в документе http://home.netscape.com/eng/ssl3/3-SPEC.HTM.

Средства SSL разработаны так, что они могут быть размещены "поверх" сущест-вующих серверов. Процедура включения SSL имеет свои особенности для различныхсерверов и описана в документации на сервер. Так, например, детальные инструкциипо использованию SSL на независимом сервере Resin можно найти в документеhttp://www,caucho.com/products/resin/ref/faq.xtp.

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

Настройка процедуры аутентификацииЗадачу аутентификации можно разделить на две подзадачи: получение у принци-

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

Page 243: Java Server Pages

Настройка процедуры аутентификации 247

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

Сервер ResinДля идентификации пользовательского имени и пароля сервер Resin использует ау-

тентификатор. Аутентификатор — это класс, реализующий интерфейс A u t h e n t i c a t o r .Аугентификатор Resin, используемый по умолчанию, воспринимает любое соче-

тание пользовательского имени и пароля. Это полезно в том случае, когда Resin ис-пользуется совместно с Apache или IIS; в этом случае аутентификация производитсясредствами Wcb-сервера. При работе с Resin как с независимым сервером необходимореализовать аутентификатор для базовой аутентификации.

Пример базовой аутентификации, выполняемой с помощью сервера Resin, пока-зан на рис. 9.4.

Kin» Fannie Bob. £rto Network Poinrat!

hHpV/bcoKjrtTjxJ

6*1 yitw Ioob

Wp /ЛхЫни) ШвО/

SeciBfty Wormaticin

User principal: resinUsarnama: resinRe4"«5t Auihenlicsteif wilh basic.This connection is not secure.

Hem»' .'•.*•! localhostRemote Hosl: localhosiRemoleAddr 127 0.0.1

You are in reein-uset rote

ияг пеги dnd p n v d

В вас Aiihamic^ai E»«(fe

РИС. 9.4. Реализация базовой аутентификации средствами Resin

Защищенный ресурс, процесс обращения к которому показан на рис. 9.4, пред-ставляет собой JSP-документ (его код представлен в листинге 9.3,а).

Листинг9.3,a. /protected-page.jsp

<htmlxheadxtitle>A Protected Page</title></head><body>

<%@ include file='show-security.jsp' %></p>

Page 244: Java Server Pages

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

<% if(request.isUserlnRole("resin-user")) { %>You are in <i>resin-user</i> role<br/>

<% ) else (%>You are <b>not</b> in <i>resin-user</i> role<br/>

</body></html>

Данный документ включает JSP-файл show-security, j sp, который рассматри-вался при обсуждении базовой аутентификации. В этом файле содержится код, ото-бражающий информацию о пользователе соединении и процедуре аутентификации.Документ, представленный в листинге 9.3,а, также выполняет проверку роли.

В листинге 9.3,6 представлен дескриптор доставки для данного приложения.

ЛИСТИНГ 9.3,6. /WEB-INF/web.xml

<?xml version="l.0" encodings"ISO-8859-l"7>

<!DOCTYPE web-appPUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN""http://Java.sun.com/j2ee/dtds/web-app_2.2.dtd">

<web-app><security-constraint>

<!— Защищенные Web-ресурсы —><web-resource-collection>

<web-resource-name>A Protected Page</web-resource-name><url-pattern>/protected-page.j sp</url-pattern>

</web-resource-collection>

<auth-constraint><role-name>resin-user</role-name>

</auth-constraint></security-constraint>

<login-config><auth-method>BASlC</auth-method><realm-name>Basic Authentication Example</realm-name>

<!— Дескриптор, специфический для сервера Resin —><authenticator id='beans.SimpleAuthenticator'/>

</login-config></web-app>

В дескрипторе доставки указан тип аутентификации BASIC, а доступ к документу/protected-page. j sp разрешен только принципалам, принадлежащим ролиres in-user . В составе дескриптора доставки также содержится ХМL-дескрипторauthent icator , с помощью которого задается аутентификатор для выбранного типааутентификации. Код аутентификатора приведен в листинге 9.3,в.

Page 245: Java Server Pages

Настройка процедуры аутентификации 249

Листинг 9.3,в. /WEB-INF/classes/beans/SimpleAuthenticator. Java

package beans;

import com.caucho.server.http.AbstractAuthenticator;

I import com.caucho.server.http.BasicPrincipal;import Java.security.Principal;

public class SimpleAuthenticator extends AbstractAuthenticator {public Principal authenticate(String user, String password) {

boolean valid «• password != null &&password.equals("resin") ssuser != null 5b user.equals("resin");

if(valid) return new BasicPrincipal(user);else return null;

1public boolean isUserlnRole(Principal user, String role) (

return user.getName().equals("resin") &&role.equals("resin-user");

Аутентификатор, приведенный в листинге 9,3,в, представляет собой подкласскласса AbstractAuthenticator; в нем переопределены методы a u t h e n t i c a t e иisUserlnRole. Оба этих метода объявлены в интерфейсе Authenticates, а их реа-лизация в классе AbstractAuthenticator используется по умолчанию.

Если пользовательское имя и пароль распознаны, метод a u t h e n t i c a t e возвраща-ет экземпляр класса BasicPrincipal, в противном случае возвращается значениеnull. Класс BasicPrincipal содержится в пакете com.caucho. server, h t t p .

Сервер Tomcat4.0В сервере Tomcat 4.0 используется область аутентификации (realm), которая дейст-

вует по тому же принципу, что и аутентификатор Resin. В отличие от Resin, для сервераTomcat не включаются специальные дескрипторы в файл /WEB-INF/web.xml, вместоэтого область аутентификации задается в файле $TOMCAT_HOME/conf / s e r v e r . xml.

<\— Фрагмент файла $TOMCAT_HOME/conf/server.кт1. —><!-- Пример конфигурационного файла. —>< ! — Заметьте, что компоненты вложени и между ними существует

отношение "родительский/дочерний" —>

«Server port="8005" shutdown="SHUTDOWN" debug="0">

< ! — Поскольку класс представляет объект аутентификации,его экземпляр должен быть разделяемым

<Realm className="org.apache.catal ina.realm.MemoryRealm" />—>

<ReaIm className="CuatomRealm"/>

</Server>

Page 246: Java Server Pages

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

В составе элемента Server задана область аутентификации по умолчаниюorg .apache . c a t a l i n a . realm.MemoryRealm, которая совместно используется все-ми контекстами. (При необходимости область аутентификации Tomcat может бытьопределена для отдельного приложения.) Для того чтобы заменить область по умол-чанию, надо закомментировать дескриптор, с помощью которого задается org.apache, c a t a l i n a . r e a l m . Memo г у Re aim, и включить требуемую область аутентифи-кации. Пример замены области показан в листинге, приведенном выше.

Области аутентификации, определяемые разработчиком, обычно создаются какподклассы абстрактного класса RealmBase, который реализует интерфейс Realm,В классе RealmBase объявлены три абстрактных, метода, которые должны быть реа-лизованы в подклассах. Эти методы перечислены в табл. 9.4.

Таблица 9.4. Абстрактные методы класса RealmBase

Метод Назначение

boolean hasRole (Principal Возвращает значение true, если роль соответ-principal, String role) ствует принципалу

String getPassword {String Возвращает пароль, связанный с пользователемuser)

Principal Возвращает объект Principal, связанный сgetPrincipal {String user) пользователем

Код класса CustomRealm, на который ссылается файл server. xml {фрагмент это-го файла был приведен выше), представлен в листинге 9.4.

Листинг 9.4, Область аутентификации, определенная разработчиком

import Java.security.Principal;import org.apache.catalina.realm.RealmBase,-

public class CustomRealm extends RealmBase {public boolean hasRole{Principal principal, String role) (

String name = principal.getName(};

if[name.equals("tomcat"))return role.equals("tomcat");

if(name.equals("rolel"))return role.equals("rolel");

if[name.equals{"both"))return role.equals("tomcat") |[ role.equals("rolel");

return false;}protected String getPassword(String username) (

return "tomcat";

}protected Principal getPrincipal[String username) (

Page 247: Java Server Pages

Элементы защиты Web-приложений 251

return new CustomPrincipal(username) ;)class CustomPrincipal implements Principal {

private final String name;

public CustomPrincipal(String name} {this.name = name;

)public String getNameO {

return name;}public String toString[) (

return getName();

Область аутентификации, приведенная в листинге 9.4, разработана в соответствии сданными, представленными в файле $T0MCAT_HOME/conf / t o m c a t - u s e r s .xrnl, кото-рый рассматривался ранее в этой главе. Например, метод hasRole возвращает t r u e ,если принципал и роль соответствуют данным, определенным в файле tomcat-users.xml. Метод g e t P r i n c i p a l возвращает экземпляр класса CustomPrincipal ,который реализует интерфейс Java, s e c u r i t y . P r i n c i p a l .

Области аутентификации, определенные разработчиком, должны быть доступнысерверу Tomcat во время его запуска, т.е. соответствующие классы должны содержатьсяBjAR-файлс, расположенном в каталоге $ТОМСАТ_НОМЕ/server. Для того чтобы рас-сматриваемый пример работал, надо скомпилировать файл CustoraRealm. Java, а соз-данные в результате два файла классов скопировать в каталог $TOMCAT_HOME/server.

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

Элементы защиты Web-приложенийВ спецификации Servlet 2.2 определены элементы, связанные с защитой. Эти эле-

менты присутствовали в примерах, рассмотренных в данной главе, например в деск-рипторе доставки, приведенном в листинге 9.1,в.

В табл. 9.5 перечислены элементы, входящие в состав элемента securi ty-constraint. Элемент secur i ty-const ra int является основным элементом деск-риптора доставки, определяющим защиту ресурсов.

Таблица 9.5. Элементы в составе s e c u r i t y - c o n s t r a i n t

Элемент Тип Описание

web-resource-collection + Ресурсы Web-приложения, к которым при-меняются ограничения

auth-constraint ? Ограничения по авторизации, помещенныев один или несколько наборов Web-ресурсов

Page 248: Java Server Pages

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

Окончание табл. 9.5

Элемент Тип Описание

user-data-constra int ? Описание защиты данных, которыми обме-ниваются клиент и контейнер

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

1 — один или более элементов;

? — элемент может отсутствовать.

Элемент web-resource-collection определяет один или несколько защищен-ных ресурсов, a auth-constra int задает одну или несколько ролей, для которыхразрешен доступ к этим ресурсам. Элемент user-data-const ra int определяет за-щиту данных при передаче.

Элементы, входящие в состав элемента web-resource-collection, перечисле-ны в табл. 9.6.

Таблица 9.6. Элементы в составе web-resource-collection

Элемент Тип Описание

web-resource-name 1 Имя Web-ресурса

descr ipt ion ? Описание Web-ресурса

u r l - p a t t e r n + URL-шаблон, связанный с Web-ресурсом

http-method ? HTTP-метод, связанный с Web-ресурсом

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

1 — один элемент;

? - элемент может отсутствовать;

+ — один или более элементов,

С каждым набором Web-ресурсов связывается имя и необязательное описание.Набор также включает один или несколько URL-шаблонов,

Для набора ресурсов также могут быть указаны НТТР-методы. Например, если за-дан HTTP-метод GET, ограничения по защите применяются только к запросам GET.Если HTTP-метод не указан, ограничения действуют на все типы HTTP-запросов кданным ресурсам.

Элементы, входящие в состав auth-constraint, приведены в табл. 9.7.

Page 249: Java Server Pages

Элементы защиты Web-приложений 253

Таблица 9.7. Элементы в составе auth-eonstraint.

Элемент Тип Описание

descr ipt ion ? Описание ограничений, связанных с автори-зацией

role-name + Роли, к которым применяются ограничения

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

г — элемент может отсутствовать;

+ — один или более элементов.

Элемент auth-const ra int описывает одну или несколько ролей, которым раз-решен доступ к защищенным ресурсам. При необходимости в состав данного элемен-та может быть включено описание.

Элементы, входящие в состав элемента user-data-constra int , описаны втабл. 9.8.

Таблица 9.8. Элементы в составе user-data-constraint

Элемент Тип Описание

description > Описание ограничений

transport-guarantee I NONE, INTEGRAL или CONFIDENTIAL

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

? — элемент может отсутствовать;

1 — один элемент.

В элементе u s e r - d a t a - c o n s t r a i n t содержатся элемент t r a n s p o r t - g u a r a n t e eи необязательное описание. Элемент t r a n s p o r t - g u a r a n t e e может иметь значениеNONE, INTEGRAL или CONFIDENTIAL. Значение NONE отменяет все ограничения напередачу данных, а значение INTEGRAL указывает на то, что контейнер сервлетовдолжен гарантировать неизменность данных при передаче. Значение CONFIDENTIALуказывает на то, что данные не могут быть прочитаны при передаче.

В спецификации сервлетов не указывается, как именно контейнер сервлетов дол-жен реализовывать транспортные ограничения, однако значения INTEGRAL иCONFIDENTIAL обычно предполагают передачу данных по защищенному каналу, на-пример SSL. Сервер Resin предоставляет доступ к данным, определенным какCONFIDENTIAL только в том случае, если метод S e r v l e t R e q u e s t . i s S e c u r e возвра-щает значение t r u e .

Page 250: Java Server Pages

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

Программная аутентификацияТермин программная в данном случае означает, что все средства, связанные с аутен-

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

Еще один недостаток программной аутентификации состоит в том, что разработ-чик не может использовать методы H t t p S e r v l e t R e q u e s t . g e t U s e r P r i n c i p a l ,Ht tpServ le tReques t .ge tRemoteUser и H t t p S e r v l e t R e q u e s t . i s U s e r l n R o l e .Выбр;\в программную аутентификацию, разработчик вынужден создать и использо-вать собственный API.

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

Механизм а\тснтификации, о котором пойдет речь в данном разделе, предполага-ет, что в составе JSP-документа содержится приведенный ниже пользовательский де-скриптор,

< ! — Защищенный JSP-документ -->

<%@ tagl ib='/WEB-INF/t lds/securi ty ' p r e f i x = ' s e c u r i t y ' %>

< ! — Атрибут errorPage - необязательный; при его отсутствии,если в процессе регистрации возникнет ошибка,управление вернется документу, указанному посредствоматрибута loginPage —>

<secur i ty :enforeeLogin loginPage='/login. jsp 'errorPage='/error,jsp' />

< i — Остальная часть файла доступна лишь в том случае, еслипользователь уже зарегистрировался в течение сеанса —>

Дескриптор enf orceLogin ищет информацию о пользователе в области видимостисеанса. Если такая информация присутствует, дескриптор не выполняет никаких дейст-вий, в противном случае он перенаправляет запрос Web-странице регистрации. Стра-ница, предназначенная для регистрации, задается как значение атрибута loginPage.

Если попытка регистрации оканчивается неудачей, отображается Web-страница ссообщением об ошибке. Эта страница определяется с помощью необязательного ат-рибута e r r o r P a g e . Если атрибут e r r o r P a g e отсутствует, страница регистрацииотображается повторно.

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

Диаграмма, которая представляет последовательность действий, выполняемых спомощью дескриптора enf orceLogin, показана на рис. 9.5.

Page 251: Java Server Pages

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

Документ, содержащийдв скрипта pEnfortcLcgin

з±а::::::: loginPage Й елоrPage являютсяатр иб утэ ми де с ф илтор а

[user !- mill]EVAL PAGE

User u£gr= getAttnbuEef user")

! ЕСЛИ пользовательi зарегистрировался в течениесеанса.обрабатываетсяостальной масть документа

[ и з о " " null] •--•"Suing reques!_uji = gelReque1

SKIP PAGE

I

Если пользянагтель незарегистрировался втечение•сеанса, упрае^ение передается

1URIQ t WeЬ-CTTJакине Регистрэили

selAMnbut&("eiror-

s etAll ri but efprot ect

(otward

Рис. 9.5. Диаграмма взаимодействия для дескриптора enforceLogin

В составе дескриптора enforceLogin могут присутствовать три атрибута, опре-деляющих порядок последующей обработки запроса. Эти атрибуты перечислены втабл. 9.9. Атрибуты loginPage и errorPage задают соответственно страницу реги-страции и страницу с сообщением об ошибке. Атрибут protected-page представля-ет URI защищенного ресурса.

Таблица 9.9. Атрибуты в составе дескриптора enforceLogin

Имя атрибута Описание

login-page

error-page

protected-page

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

Необязательный атрибут, который задает страницу, предна-значенную для отображения ошибок при регистрации

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

Page 252: Java Server Pages

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

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

На рис. 9.6 приведен пример использования программной аутентификации.

sJljggnPigC'ttawiltinmmEifilna -101 х|

: £Я« Sit B i * Fgwonus ЪяЬ иф

_lPlease Login

$}Name- |lred

Password: ^

Mole, valid name 19 « e l and «lid pjssworo is »Ham

aoon» Bis Ь"""*""1

Cibvitai-McnMtliumlEiifew iJBl*l

I pit £dn Ki™ fganut i « * a>f

А*»™ [а мц'.'л>ы1>)0'в11№и1тл1н/м<чпсл1 ^J <*ao

•• • SCBIHS:

i j j^rrram^ a i d Password aw rw y ^ d

Cbck naa ш гигу login

Je] •;«» "~ Loci ши

£14 £сы I n * Fawnfc I w t

• -• • • ( and L s « i ^ > r ; i •v-iSvsH'-irsKj: « 8 d

Please Login

Nime:

Nun: valid name 13 w e ! and valid password is

JflOon. ::

• , - • ^ - - 3 ^ i - •• ь' • • 1 • n • • • p • • -f -А п П н п г4Q9 ' MCfHAI HDW1Л t rvvv

Fit В » У ' " Ч И П ! » I « * ' * t >

This is a proreatd PSg* Wekrjme rttall

.ICIJii

KB

_)

J

Рис. 9.6. Программная аутентификация

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

Приложение работает с базой, содержащей данные о пользователях. База данныхпредставляет собой экземпляр класса LoginDB, а информация о пользователе инкап-сулирована в классе User. Классы LoginDB и User были рассмотрены в главе 5, их ко-ды содержатся в листингах 5.1,а и 5.1,6. В используемой в данном примере реализа-ции LoginDB добавлены пользователи, предусмотренные по умолчанию. Фрагментданного класса приведен влистинге 9.5,а.

Page 253: Java Server Pages

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

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

Данный сералет вызываетсяпри активизации формы,содержащейся в документе

Вл2И handЙ - ® web-inf - M f 1 v v = u / / %} Authentic

- : • „ . ! classes - ^ " ^ - •( Ibeans

beans - „ \ U J tags

tags

EnforceLoginTag

^ J S P 4/1 J a v a |^ЯXML [SJ Файлописакиябиблиотешдескрипторов

Р и с . 9 . 7 . Ф а й л ы , которые входят в состав приложения, д е м о н с т р и р у ю щ е г о

п р о г р а м м н у ю аутентификацию

Листинг9.5,a. /WEB-INF/classes/beans/IioginDB. Java

// Код класса User был приведен в листинге Ъ.1,а.

public c l a s s LoginDB implements J a v a . i o . S e r i a l i z a b l e {

p r i v a t e Vector u s e r s = new V e c t o r ( ) ;p r i v a t e User[] de fau l tUsers • (

new U s e r ( " w t e l l " , "wi l l iam", "my f i r s t name"),h

public LoginDB() (for tint i=0; i < defaultUsers.length; ++i)

users.add(defaultUsers[i]);)public void addUser(String uname, String pwd, String hint} {

users.add(new User(uname, pwd, hint));

// Остальная часть класса идентична коду LoginDB,// приведенному в листинге 5 .1 ,6 .

Page 254: Java Server Pages

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

В состав приложения, показанного на рис. 9.6, входит один защищенный доку-мент; его код приведен в листинге 9.5,6.

Листинг9.5,6. /protectedPage.jsp

<htmlxheadxtitle>A Protected Page</titlex/head><%@ taglib uri='security' prefix='security' %></body>

<J—- Если атрибут errorPage не указан, то в случае неудачиуправление возвращается странице регистрации. —>

<security: en force Login J.ogi;aPage=' /login, jsp'

errorPage='/error.j sp'/>

<jsp:useBean id='user' type='beans.User' scope='session'/>

This is a protected page. Welcome <%= user.getDserName f] %>.

</body></html>

Защищенный документ отображает лишь приветственное сообщение. В состав

этого документа входит пользовательский дескриптор enforceLogin. Класс под-

держки enf orceLogin представлен в листинге 9.5,в.

Листинг 9.5,в. /WEB-INF/classes/tags/EnforeeLoginTag. Java

package tags;

import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;import javax.servlet. jsp. JspException,-import javax.servlet.jsp.PageContext;import javax.servlet.jsp.tagext.TagSupport;

public class EnforceLoginTag extends TagSupport {private String loginPage, errorPage;

public void setLoginPage(String loginPage) {this.loginPage = loginPage;

}public void setErrorPage[String errorPage) (

this.errorPage = errorPage;)public int doEndTagf) throws JspException {

HttpSession session = pageContext.getSession();HttpServletRequest req = (HttpServletRequest)pageContext.

getBequest();String protectedPage = req.getRequestURI();

if(session.getAttributef"user") == null} (session.setAttribute("login-page", loginPage);session.setAttribute("error-page", errorPage];

Page 255: Java Server Pages

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

session.setAttribute("protected-page", protectedPage);

try {pageContext.forward(loginPage);return SKIP_PAGE;

}catch (Exception ex) (

throw new JspException(ex.getMessage());

return EVAL_PAGE;

)public void released (

loginPage = errorPage = null;

t

Если информация о пользователе находится в области видимости сеанса, методdoEndTag класса поддержки возвращает значение EVAL_PAGE, в результате чего об-рабатывается оставшаяся часть документа. Если данные о пользователе в области ви-димости сеанса отсутствуют, устанавливаются значения атрибутов, перечисленных втабл. 9.9, и управление передается документу регистрации.

Код JSP-документа, предназначенного для регистрации, приведен в листинге 9.5,г.

Листинг 9.5,г. /login, jap

<htmlxheadxtit le>Login Page</tit lex/head><%8 t a g l i b uri='/WEB-INF/tlds/security.tld' pre f ix=' secur i ty ' %><body>

<font size='4' color=' red' Xsecurity: showErrora/x/font>

<p><font size='5' color='blue'>Please Login</fontxhr><form action='<%= response.encodeORL("authenticate") %>'

method^'post' ><table>

<tr><td>Name:</td><tdxinput type=' text' name='userName' /></td>

</trxtr><td>Password:</td><tdxinput type=' password' name=' password' siee=' 8' ></td>

</tr></table><br><input type='submit' value='login'>

</formX/p>

Mote: valid name is <i>wtell</i> and validpassword is <i>william</i>

</body></html>

Page 256: Java Server Pages

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

Данные, введенные в форме, передают* я сервлету authent ica te , выполняющемуаутентификацию, Если попытка а; гснтификапии оканчивается неудачно, сервлет ге-нерирует сообщение об ошибке и сохраняет его в области видимо* ги сеанса. Данноесообщение отображается посредством дескриптора securityishcwErrors в верх-ней част \\ eb-стратщы регистрации. ( Отображение имени a u t h e n t i c a t e в сервлетаутентификации задастся вфанлсюеЬ.хгп!, код которого приведен в/пнтшт-

ЛИСТИНГ 9.5,Д. /WEB-INF/web.xml

<?xml version=**l. О " en ading "

<!DOCTYPE web-appPUBLIC "-//Sun Microsystems, Inc.// ! I Web Application 2.2//ЕЫ"" h t • : . is/web-a] • . .

<web-app><servlet>

<servlet-name>2iither. ticatie</servlet-Tiame><servlet-class>Autheni:icate3ervlet^ • et-class>

</servlet>

<s&rvlet-name I . /servlet-nai<url-pal • ;uthenticate</url-.

</servlet-mapping>

<ta«;

i B-INF/tltts/s<taglib-location>/WEB-INB I :

Код ссрЕшста а\тситификации предстдвлен в листинге 9.5,е.

Листинг9.5,е. /WEB-INF/classea/AuthenticataServlet. Java

import javax.sei • • •import javax.servl'-• . .import Java et.httjimport javax.servlc• pServletimpoi

1 • • • : onse;

import javoX.K-import Java.ic.Import beans.Loginl .import beans

public class An • Let (

private

; Lc void . •. . • . • ptionl

super. . ; -

:

Page 257: Java Server Pages

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

Данные, введенные в форме, передаются сервлету authent icate , выполняющемуаутентификацию. Если попытка аутентификации оканчивается неудачно, сервлет ге-нерирует сообщение об ошибке и сохраняет его в области видимости сеанса. Данноесообщение отображается посредством дескриптора s e c u r i t y : showErrors в верх-ней части Web-страницы регистрации. Отображение имени a u t h e n t i c a t e в сервлетаутентификации задается в файле web. xral, код которого приведен в листинге 9.5,д,

Листинг 9.5,д. /WEB-IMF/web.xml

<?xml version="1.0" encoding="ISO-8859-l"?>

<!DOCTYPE web-appPUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EK""http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">

<web-app><servlet>

<servlet-name>authenticate</servlet-name><servlet-class>AuthenticateServlet</servlet-class>

</servlet>

<servlet-mapping><servlet-name>authenticate</servlet-name><url-pattern>/authenticate</url-pattern>

</servlet-mapping>

<taglib><taglib-uri>/WEB-lNF/tlds/security.tld</taglib-uri><taglib-location>/WEB-INF/tlds/security.tld</taglib-location>

</taglib></web-app>

Код сервлета аутентификации представлен в листинге 9.5,е.

ЛИСТИНГ 9.5,е. /WEB-INF/classes/AuthenticateServlet. Java

import javax.servlet.ServletConfig;import javax.seuvlet .ServletException,•import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax,servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import Java.io.IOException;import beans,LoginDB;import beans,User;

public class AuthenticateServlet extends HttpServlet {private LoginDB loginDB;

public void init(ServletConfig config) throws ServletException[super.init(config];loginDB = new LoginDB();

Page 258: Java Server Pages

I

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

public void service(HttpServletRequest req,

HttpServletResponse res)

throws lOException, ServletException {HttpSession session • req.getSession();String uname - req.getParameter("usetWame");String pwci m req. getParameter ("password");User user = loginDB.getUser[uname, pwd) ;

if(user != null) ( // Авторизованный доступString protectedPage = (String)session.

getAttribute("protected-page");session,removeAttribute("login-page");session.removeAttribute("error-page");session.removeAttribute["protected-page");session.removeAttribute("login-error");

session.setAttribute("user", user);res.sendRedirect(res.encadeURL(protectedPage));

}else { // Неавторизованный доступ

String loginPage = (String)session.getAttribute("login-page");

String errorPage = (String)session.getAttribute("error-page"I;

String forwardTo = errorPage != null ? errorPage :loginPage;

session.setAttribute("login-error","Username and Password are not valid.");

getServletContext().getRequestDispateher(

res.encodeURL(forwardTo)).forward(req, res) ;

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

Если запись о пользователе отсутствует в базе данных, устанавливается атрибутlogin-error и запрос перенаправляется Web-странице ошибки, либо, если она незадана, странице регистрации. Последовательность действий, соответствующая не-удачной регистрации, показана на рис. 9.9.

Листинг9.5,ж. / e r r o r . j s p

<htmlxheadxtit le>Login Error</t i t lex/head><%@ tagl ib uri='/WEB-INF/tlds/security.tld' pref ix='secur i ty ' %><body>

<font size=M' color='red'>Login failed because;<p>

Page 259: Java Server Pages

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

<security:showErrors/x/font></p>

Click <a href= flogin.jsp'>here<7a> to retry login.

<7body></html>

login jsp Authentic ale-Se^e:

HttpSerdet-geauesi

Hup Session LmioSB Htlp$8rvtei-Response

seivicap

Siring unanne =

Sinng pw

String |

= gel Pa

TJUser user = gslUse/funame, pwd)

JtectedPage = gelAllnbulefprDl$cled-page")

remrweAllrib

Если информация о пользователеприсутглеувг е бгэа,данные опользователе сохраняются в облг:видимости сеанса, атрн8)ты маисудаляются и управление перрэащищенноыудокуманту

:

Г. user)

и

femoyaAttrrhuleClcigin-page"]

removBAlt nbulefeiror-pageT

removeAliritwl e(*Fogrn- error")

seiAtin bin ef user", user)

Рис. &.8. Действия, соответствующее успешной регистрации

Page 260: Java Server Pages

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

Authe mi rattvSaMsl

HtmServtet-Request

| Hi Ip Seas ion LoqinDB Reouast-Disoaicher

serviceO

String uname = DelParameteffuserNanne'1)

= getParamelerfpassword')

J

getUser(unama, p

g alAtl ribul e("l ogi n-paga")

getAfi ributefSrror- p ад a 'J

setAtlri but e("loji(Ve поГ, *., ")

;

(orwantQ

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

!Еглн энаУЕниа атрибута login-. e r n r - p i 3 0 •u;OG!3ian : j l l.i управление передается странице:ошкБкн.в лргдиеном случае: запрос пере направляет сиj t ip знице регистрации

Рис. 9.9. Действия, выполняемые в случае, если попыткарегистрации окончилась неудачей

Подобно странице регистрации, страница ошибки содержит дескриптор secur i ty :showErrors, класс поддержки которого приведен в листинге 9.5,з.

Листинг 9.5,3. /WEB-INF/elasses/tags/ShowErrorsTag. Java

package tags;

import javax.servlet-jsp.JspException;import javax.servlet.jsp.PageContext;import javax.servlet.jsp.tagext.TagSupport;

public class ShowErrorsTag extends TagSupport {public int doStartTagO throws JspException (

String error = (String)pageContext.getSession().getAttribute("login-error'

ifterror != null) {try 1

pageContext.getOut().print(error) ;>catch(Java.io.IOException ex) {

throw new JspException(ex.getMessage());

return SKIP BODY;

Page 261: Java Server Pages

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

Класс поддержки дескриптора showErrors выводит значение атрибута сеансаlogin-error, которое было установлено сервлетом аутентификации.

РезюмеСредства обеспечения безопасности— неотъемлемая часть приложения, которое

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

Вы, наверное, уже привыкли к тому, что использование JSP и языка Java обеспечи-вают переносимость создаваемых Web-приложений. Средства защиты являются ис-ключением из этого правила; при их реализации часто приходится создавать фраг-менты кода, ориентированные на конкретный контейнер сервлетов. Если требованиепереносимости является обязательным, средства обеспечения безопасности прихо-дится создавать "с нуля".

Page 262: Java Server Pages

РАБОТА С БАЗАМИДАННЫХ

Ж В этой главе...

• Создание базы данных.

• Источники данных.

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

- Дескриптор query.- Дескриптор columnnames.- Дескриптор column.

- Дескриптор rows.- Дескриптор release.

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

- Использование пула соединений.- Реализация простого пула соединений.

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

• Транзакции.

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

Page 263: Java Server Pages

Подавляющее большинство коммерческих "Web-узлов содержит в своем составереляционные базы данных. Для доступа к таким базам как нельзя лучше под-ходят сервлеты и JSP, поскольку они могут использовать средства JDBC (Java

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

Материал данной главы предполагает наличие у читателя общих знаний о реляци-онных базах данных, а также владение основами SQL и JDBC. Эти вопросы наложеныво многих книгах о JSP, если же вы хотите более глубоко изучить JDBC, обратитесь кизданию/ДВС API Tutorial and Reference (Addison-Wesley).

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

Таблица 1 0 . 1 . Пользовательские дескрипторы, рассматриваемыев данной главе

Имя дескриптора Описание

query

rows

Получает соединение с базой данных и выполняет запрос.Тело дескриптора интерпретируется как SQL-выражение

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

Page 264: Java Server Pages

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

Окончание табл. 10.1

Имя дескриптора Описание

columns Перебирает столбцы набора результатов

columntJames Перебирает имена столбцов набора результатов

release Освобождает соединение, полученное с помощью деск-риптора query

prepareStatement Создает заранее подготовленное выражение и сохраня-ет его в указанной области видимости

executePrepared- Выполняет заранее подготовленное выражение, соз-Statement данное с помощью дескриптора prepareStatcmenl

t ransact ion Выполняет транзакцию. Тело дескриптора интерпре-тируется как SQL-выражение для создания транзакции

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

Создание базы данныхВ данном разделе будет рассмотрено независимое Java-приложение, которое соз-

дает базу данных. Код приложения приведен в листинге 10.1. База данных, создавае-мая в процессе работы данного примера, будет использована далее в этой главе.

Листинг 10.1. Создание базы данных

import java.sql.Connection;import java.sql.DriverManager;import j ava.sql.Statement;import java.sql.SQLException;

public class CreateDB {private Connection conn;private Statement stint;

public s ta t ic void main(String argsfj) {new CreateDB();

)public CreateDB0 {

try {loadJDBCDriver();conn = getConnection("F:/databases/sunpress'stmt = conn.createStatement();

Page 265: Java Server Pages

Создание базы данных

createTables(stmt);populateTables(stmt);

stmt.close() ;conn.close();DriverManager.getConnection(

"jdbc:сloudscape:;shutdown=true")

catch(SQLException ex}ex.printStackTrace(

{

private void createTables(Statement stmt) (try {

stmt.execute("CREATE TABLE Customers (" +"Customer_ID INTEGER, " +"Name VARCHAR(25)"PhoneJSIumber VARCHAR (301 ) ") ;

stint.execute ("CREATE TABLE Orders (" +"Customer_ID INTEGER, " +"Order_ID INTEGER, ""Amount FLOAT)");

)catch(SQLException ex) [

ex.printStackTrace() ;

private void populateTables(Statement stmt) (try <

stmt.execute("INSERT INTO Customers VALUES"(1, 'William Dupont','(2, 'Kris Cromwell',' (3, 'Susan Randor','{4, 'Jim Wilson',(5, 'Lynn Seckinger',(6, 'Richard Tatersall',[•?, 'Gabriella Sarintia',(8, 'Lisa Hartwig

1,

(652)462-0931'(652)462-0932'(652)462-0933'(652)482-0934-(652)482-0935'

•(652)482-0936'),'' (652)482-0937-),•

1 (652}482-0938

1

++)">;

stmt.executeC'INSERT INTO Orders VALUES

"(IF

"(1,"(1,"(8

f

"(1."(!•"(7,

1,3,5,7,9,U ,13,

29.39.24.29.39.

99),99),в"?),99),99),

24.87),"21.12},"

'(2,'(3,'(3,'(2," (3,

+ "(3,+ "(1,

2,4,6,8,10,12,14,

49.86) , " +99.13)," +112.221," +49.86)," +99.13)," +112.22),"27.49)");

catch(SQLException ex)ex.printStackTrace(

:•

{

private void loadJDBCDriver[)try {

Page 266: Java Server Pages

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-драйвер » создает соединение с базойданных. Затем, вызывая метод Connection . c r e a t e S t a t e m e n t , конструктор получа-ет объект Statement, инкапсулирующий 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-драйверами.

Page 267: Java Server Pages

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

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

На рис. 10.1 показаны два JSP-документа. Один из них выполняет запрос к базе

данных {он отображается в окне, расположенном слева на рисунке), а второй выво-

дит результаты запроса.

в 1 Ojutma Eiersk • Маж< М д и Е *

|j р, &,, V favwlie I»l

* * ^ | > в I ® . W'P " l > c i ^ n ! b . B c a I ' t J W i » v '

^ ] Dare

< >«. Б-.[|тр z i ^ ^

]

J

.

J

-MiCHKO'tln' Expkttr

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

Customers

CUSTOHER

1

г

3

4

Б

7

в

10 NAME PHONE_NUHBER

William Oupant (652)4В2ШЭ1

! Kris Cromwell [652^492-0932

SussnRandM (К2Н82-09ЭЭ

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

; Lisa Hartwig (652)482-0938

J

JT>: Local rtrawl

Рис. 10.1. Пользовательские дескрипторы, предназначенные для обращений к базе данных

JSP-код документа, который показан в окне на рис, 10.1 слева, представлен в лис-

тинге 10.2,а.

Листинг 10.2,a. /test.jsp

<htmlxhead><title>Database Example</title><%@ tagl ib uri='/WEB-INF/tlds/database.tld' prefix^'database'%>

</head><body>

<database:query id='customers' scopes'session'>SELECT * FROM Customers

</database:query>

<font size-' A'><a href='showCustomerQuery.jsp'>3how Customers</a><p>

</font>

</body>

</html>

Page 268: Java Server Pages

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

<htmlXheadxtitle>Customers</title><*@ taglib uri='/WEB-INF/tlds/database.tld' prefix='database'%>

</head><body>

<p><font size=' 4' >Custome ITS : </fontx/p>

<table border='2' cellpadding=' 5' ><database:columnNames query='customers' id-'name'>

<th><!= name %x/th></database:columnNames><database:rows query='customers'><tr>

<tr><database;columns query-'customers' id='value'>

<td><%= value 4x/td></database:columns>

</tr>

</database:rows></table>

<database:release query-'customers'/>

</body></html>

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

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

Page 269: Java Server Pages

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

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

Дескриптор queryДескриптор query, который был использован в документе, показанном в листинге

10.2,а, интерпретирует содержимое как SQL-выражение, поэтому в файле описаниябиблиотеки дескрипторов элемент bodycontent содержит значение tagdependent .

<tag><name>query</name><tagclass>tags.jdbc.QueryTag</tagclass><bodycontent>tagdependent</bodycontent>

</tag>

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;

Page 270: Java Server Pages

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) Irows = 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;

try (if('driverLoaded) {

Class.forName("COM.cloudscape.core.JDBCDriver");driverLoaded = true;

)con = DriverManager.getConnection(

"jdbc:cloudscape;" + dbName + ";create=false");}

c a t c h (Except ion, 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 . ge tConnect ion, откры-вая тем самым соединение с базой данных. Данное соединение используется для соз-дания выражения, для которого набор результатов поддерживает прокрутку, но неможет обновляться. С наборами результатов такого типа могут работать только драй-веры, реализующие JDBC 2.0, следовательно, это же ограничение накладывается и нарассматриваемые здесь пользовательские дескрипторы. Это ограничение вполнеприемлемо, поскольку большинство производителей баз данных предоставляют драй-веры, совместимые с JDBC 2.0.

Page 271: Java Server Pages

Пользовательские дескрипторы для работы с базами данных 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 {

Page 272: Java Server Pages

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

Query query «• findQuery(pageContext, name);return query.getUpdateCount();

Ipublic static Query findQuery(PageContext pageContext,

String name) throws JspException {Query query «• (Query)pageContext.findAttribute (

QUERY_PREFIX + name);

if(query == null) ( // сеанс отсутствует?throw new JspException("Query " + name + " not found." +

" Please retry the query");}return query;

|private static int getConstantForScope(String scope) {

int constant =» PageContext.PAGE_SCOPE;

if(scope.equalsIgnoreCase("page"}}constant •* PageContext.PAGE_SCOFE;

else if(scope.equalslgnoreCase("request"))constant * PageContext.REQUEST_SCOPE;

else if[scope.equalslgnoreCase("session"))constant = PageContext.SESSION_SCOPE;

else if(scope.equalslgnoreCase("application"))constant « PageContext.APPLICATION_SCOPE;

return constant;

Класс Query содержит выражение, а также, в зависимости от типа запроса, либонабор результатов, либо счетчик обновлений.

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

Дескриптор columnNamesДескриптор query, рассмотренный выше, устанавливает соединение с базой дан-

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

Дескриптор columnNames используется следующим образом:

<database:columnNames query='customers' id='name'><%- name %>

</database:columnNames>

Page 273: Java Server Pages

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

Дескриптор columnNames обращается к запросу, указанному с помощью атрибутаquery, и создает переменную сценария, представляющую имя текущего столбца. Этапеременная именуется с помощью атрибута id. В приведенном выше фрагменте кодапеременная экземпляра используется для вывода имени каждого столбца в запросеcustomers.

Два из рассматриваемых в данной главе дескрипторов — columnNames и columns —осуществляют перебор столбцов в наборе результатов. Их поведение инкапсулированов абстрактном базовом классе Colu iml tera torTag, код которого приведен в листинге10.4,а. Данный класс осуществляет перебор столбцов набора результатов, а после окон-чания набора выводит тело дескриптора. Несмотря на то что в классе Colunin-I 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;

1catch(Exception ex) (

throw new JspException(ex,getMessage());}if(columnCount > 0) return EVAL_BODY_TAG;

else return SKIP_BODY;}public int doAfterBody() throws JspException {

if(++currentColumn <= columnCount} {return EVAL_BODY_TAG;

Page 274: Java Server Pages

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

else {try {

bodyContent.writeOut[getFreviousGut() ) ;)catch(Java.io.lOException e) (

throw new JspException(e.getMessage());}

treturn SKIP_BODY;

Метод DoStartTag класса Columnl tera torTag получает ссылку на набор резуль-татов, связанных с указанным запросом; для этого используется статический методQuery. g e t R e s u l t . Этот метод рассматривался при обсуждении пользовательскогодеск ри п тора qu e r у.

Имея ссылку на набор результатов, метод C o l u m n l t e r a t o r T a g . d o S t a r t T a g по-лучает ссылку на метаданные, связанные с этим набором, посредством которой опре-деляет число столбцов в наборе результатов. Если число столбцов больше нуля, про-изводится обработка тела дескриптора.

Метод C o l u m n l t e r a t o r T a g . doAf terBody вызывается до тех пор, пока столбцынабора результатов не будут исчерпаны. После обработки последнего столбца методdoAf terBody выводит содержимое, используя для этого выходной поток предыдуще-го дескриптора, и возвращает значение SKIP_BODY, что приводит к прекращениюперебора. Подробно об использовании выходного потока предыдущего дескрипторав методе doAf terBody было сказано в главе 2.

Разрабатывая собственное приложение, вы можете создать подкласс классаColumnl tera torTag и переопределить методы BodyTag. В теле этих методов можнообращаться к переменным экземпляра Columnl tera torTag, объявленным какp r o t e c t e d . Именно это и делает класс поддержки дескриптора columnNames. Кодкласса поддержки показан в листинге 10.4,6.

Листинг 10.4,6. /WEB-INF/classea/ tags/ j dbc/ColumnNames Tag. Java

package tags.jdbc;

import java.sql.ResultSetMetaData;import java.sql.SQLException;

import javax.servlet.jsp.JspException;

public class ColumnNamesTag extends ColumnlteratorTag {public void doInitBodyO throws JspException {

setAttribute();}public int doAfterBody(J throws JspException {

int whatNext = super.doAfterBody(};

if(whatNext = EVAL_BODY_TAG)setAttribute{);

Page 275: Java Server Pages

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

return whatNext;>private void setflttribute() throws JspException \

try {pageContext.setAttribute(getldf), rsmd.getColumnNameI

currentColumn]);}catch(SQLException ex) {

throw new JspException(ex.getMessage());

Класс ColumnNamesTag является подклассом ColumnlteratorTag; в нем переоп-ределены методы doInitBody и doAfterBody. Эти методы сохраняют в области ви-димости документа имя текущего столбца, которое задается с помощью обязательногоатрибута id. В процессе работы класс ColumnNamesTag применяет метаданные, свя-занные с набором результатов (в методе se tAt t r ibute используется переменная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 ограничиваетобласть видимости переменной сценария телом дескриптора.

Page 276: Java Server Pages

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;

Page 277: Java Server Pages

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

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

<database:rows query='customers'><database:columns query=' customers' id='value'>

<%= value %></database:columns>

</database: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) {

Page 278: Java Server Pages

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

t h i s . q u e r y = query;)public int doStartTag[) throws JspException {

rs = Query.getResult(pageContext, query);

try jkeepGoing = rs.next(); // Указывает на первую строку

}catch(Exception ex) {

throw new JspException(ex.getMessage О) ;

)if(keepGoing) return EVAL_BODYJTAG;else return SKIP_BODY;

Ipublic 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 получает в теле метода doStar tTag ссылку на набор результатов. После получе-ния ссылки вызывается метод next набора результатов, в результате чего курсор набораперемещается на первую строку (первоначально курсор размещается перед первойстрокой). Метод R e s u l t S e t . next возвращает значение типа boolean, которое позво-ляет выяснить, указывает ли курсор на существующий столбец. Если в методе Rows-Tag. doStartTag метод R e s u l t S e t . n e x t возвращает значение f a l s e , это означает,что в наборе результатов пет пи одной строки; в результате метод doStar tTag возвра-щает значение SKIP_BODY. В противном случае тело дескриптора обрабатывается.

В методе RowsTag . doAf terBody проверяется положение курсора. Если он указы-вает на последнюю строку, он перемещается на первую строку и перебор прекращает-ся. В противном случае вызывается метод R e s u l t S e t .nex t и перебор продолжается.

Подобно ColumnNames . doAf terBody, RowsTag. doAf terBody выводит данные ввыходной поток предыдущего дескриптора.

Page 279: Java Server Pages

Пул соединений 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;

Ipublic int doEndTag() throws JspException (

Query q = Query.findQuery(pageContext, query);

try {

Connection con = q.getStatement(}.getConnection()

if(con != null &£ ! con. isClosedO ) {con.close ();

c a t c h ( j a v a . s q l . S Q L E x c e p t i o n s q l e x ) (t h row 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 ( ) ) ;

tpageContext.removeAttribute(query);

return EVAL_PAGE;)

\

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

Пул соединенийСоединение с базой данных относится к числу ресурсов, которые оказывают наи-

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

Page 280: Java Server Pages

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.0Optional Package API, но для их использования необходим источник данных, связан-ный с JNDI. (Источники данных упоминались в начале этой главы.)

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

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

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

Дескриптор r e l e a s e возвращает соединение в пул с помощью метода r e c y c l e -Resource. Фрагмент листинга модернизированного класса ReleaseTag приведенниже.

Page 281: Java Server Pages

Пул соединений 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 ServletExceptionlsuper.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();

Page 282: Java Server Pages

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

Сервлет, представленный в листинге 10.8,а, создает экземпляр класса DbCon-nectionPool. Конструктору класса DbConnectionPool передаются четыре инициа-лизационных параметра сервлета: имя JDBC-драйвера, JDBC URL, имя и пароль. По-сле создания пула соединений он сохраняется в области видимости приложения подименем db-connection-pool.

Метод destroy, который вызывается перед завершением сервлета, прекращаетработу пула соединений и закрывает все открытые соединения.

Для того чтобы гарантировать, что пул соединений будет присутствовать в облас-ти видимости приложения уже при первом обращении к нему, сервлст загружаетсяпри запуске Web-приложения. Загрузка сервлета производится посредством элементаload-on-startup дескриптора доставки. Соответствующий фрагмент дескрипторадоставки приведен ниже.

<web-app><servlet>

<servlet-name>setup</ser:vlet-name><servlet-class>SetupServlet</servlet-class><init-paj:am>

<param-name>jdbcDriver</param-name><param-value>C0M.cloudscape,core.JDBCDriver</param-value>

</init-param>

<init-param><param-name>jdbcURL</param-name><param-value>

jdbc:cloudscape:f:/databases/sunpress;create=false</param-value>

</init-param>

<init-param><param-name>jdbcUser </param-name><param-value>roymartin</param-value>

</init-param>

<init-param>

<param-name>jdboPasgword</param-name>

<param-value>royboy</param-value></init-param>

<load-on-starti>p/></servlet>

</web-app>

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

Теперь, когда вы умеете использовать пул соединений, рассмотрим процесс егосоздания.

Page 283: Java Server Pages

Пул соединений 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);

Page 284: Java Server Pages

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

closeResources(inUseResources);

availableResources.clear() ;inUseResources.clear(] ;

}private Object getFirstAvailabLeResource() (

Object resource = null;if(availableResources.size[) > 0) {

resource = availableResources.firgtElement();availableResources.removeElementAt(0);

}if(resource != null && !isResourceValid(resource))

return getFirstAvailableResource();

return resource;}private void closeResources(Vector resources) {

Iterator it = resources.iterator();while(it.hasNextО >

closeResource(it.next() ) ;}

I

В абстрактном классе ResourcePool объявлены три абстрактных метода: create-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 синхронизированы и объявле-ны как f inal . Итераторы устойчивы к ошибке; если при переборе элементов векторон изменяется, генерируется исключение.

Page 285: Java Server Pages

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

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

В-третьих, если при создании ресурсов возникнет ситуация, препятствующая нор-мальной работе, методы c r e a t e R e s o u r c e и getResource могут сгенерировать ис-ключение ResourceExcept ion. Данное исключение представляет собой расширениеException, показанное ниже.

package beans,util;

public class ResourceException extends Exception {public ResourceException(String s) 1

super(s) ;

Расширение возможностей пула ресурсов

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

1. Ограничение числа ресурсов. Большое количество открытых ресурсов может сни-зить производительность приложения.

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

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

Новая версия класса ResourcePool, реализующая перечисленные выше возмож-ности, приведена в листинге 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 реализовывать следующие три метода:

Page 286: Java Server Pages

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

public abstract Object createResource ()throws ResourceException;

public abstract boolean isResourceValid(Object resource);public abstract void сloseResource (Object resource);

public ResourcePool() (this(10, // По умолчанию максимальное

// число ресурсов в пуле равно 10false); // Не ожидать ресурс, если превышено

// максимальное значение}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 ResourceExceptionObject resource •= getFirstAvailableResource();

if(resource == null) ( // Доступных ресурсов нетif[countResources[) < maxResources) {

waitForAvailableResource();return getResource(};

}else { // Достигнуто максимальное значение

if{waitlfMaxedOut) {try (

wait(timeout) ;\catch(InterruptedException ex) {}return getResource();

)throw new ResourceException("Maximum number " +

"of resources reached. Try again later.");}

)inUseResources.addElement(resource) ;return resource;

}public synchronized void recycleResource(Object resource) {

inUseResources.removeElement(resource);availableResources.addElement(resource);notifyAllt); // Оповещение ожидающих потоков о

// наличии соединения.

1public void shutdown() {

closeResources(availableResources);closeResources(inUseResources) ;

availableResources.clear() ;inUseResources.clear() ;

Ipublic synchronized void run{) 1 // Исключение не может

Page 287: Java Server Pages

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

// быть сгенерированоObject resource;error = null;try f

resource =• createResource[); // Создание подклассов)catch(ResourceException ex) (

error = ex; // Сохранение исключенияnotifyAll0; // Исключение должен сгенерировать

// ожидающий потокreturn;

)availableResources.addElement(resource);notifyAllО; // Оповещение ожидающих потоков.

Jprivate Object getFirstAvailableResource() (

Object resource = null;

if(availableResources.size() > 0) {resource = availableResources.firstElement();availableResources.removeElementAt(0);

}if(resource !•= null Sfi !isResourceValid(resource))

resource = getFirstAvailableResource();return resource;

iprivate 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());Iprivate int countResources() {

return availableResources . size () -f-inUseResources.size [) ;

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

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

Page 288: Java Server Pages

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

Если лимит ресурсов исчерпан и доступных ресурсов нет, метод getResource ожи-дает освобождения ресурса либо генерирует исключение. В классе ResourcePool пре-дусмотрен вариант метода getResource, при вызове которого задается тайм-аут— мак-симальное время ожидания ресурса при заполненном пуле. Значение тайм-аута задаетсяв миллисекундах и хранится в целочисленной переменной.

Метод wai tForResource запускает поток, в котором создается новый ресурс.В новом потоке запускается экземпляр класса ResourcePool (этот класс реализуетинтерфейс Runnable), Таким образом, при обращении к t h r e a d , s t a r t вызываетсяметод run пула.

Метод run вызывает метод crea teResource , реализованный и подклассе. МетодcreateResource может генерировать исключение ResourceException, котороедолжно быть обработано в методе run. Делегировать обработку исключения невозмож-но, поскольку метод run определен в интерфейсе Runnable без ключевого словаthrows. Поэтому метод run сохраняет исключение в переменной экземпляра, опове-щает ждущие потоки о случившемся и завершает выполнение. Один из зкдущих потоковвпоследствии обработает исключение в теле метода wait For A v a i l ableRe sour 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 < initialConnections; ++i)

availableResources.addElement(createResource());

)catch(Exception ex) {}

)public Object createResource() throws ResourceException {

Page 289: Java Server Pages

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

Connection connection = null;try (

if [! driverLoaded) {Class.forWame(driver);driverLoaded = true;

}if(user == null II pwd == null)

connection = DriverManager.getConnection(url);else

connection = DriverManager.getConnection(url, user,pwd) ;

)catch(Exception ex) {

II ex содержит ClassNotFoundException// или SQLExceptionthrow new ResourceException(ex.getMessage());

}return connection;

Ipublic void closeResource(Object connection) {

try {( [Connection)connection),close();

}catch(SQLException ex) (

// Игнорировать исключение)

)public boolean isResourceValid(Object object) (

Connection connection = (Connection)object;boolean valid = false;

try {valid = ! connection.isClosed();

}

catch[SQLException ex) {valid = false;

}return valid;

При создании экземпляра класса DbConnectionPool конструктору передаетсяимя JDBC-драйвера, JDBC URL и необязательные имя пользователя и пароль. Конст-руктор DbConnectionPool заранее создает пять соединений с базой.

В классе DbConnectionPool реализованы три абстрактных метода, объявленныхв суперклассе: createResource, isResourceValid и closeResource.

Если JDBC-драйвер не был загружен ранее, метод createResource загружает егои создает соединение, используя один из двух вариантов метода DriverManager.getConnection. Выбор конкретного варианта метода зависит от того, были ли зада-ны имя пользователя и пароль.

Метод DbConnectionPool .closeResource закрывает соединение, а метод i s -ResourceValid проверяет, открыто ли соединение. Метод isResourceValid вы-полняет проверку, вызывая метод Connection. isClosed. Если при вызове методаisClosed не генерируется исключение, значит, соединение допустимо.

Page 290: Java Server Pages

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

Предварительно подготовленные выраженияПри каждом запросе к базе данных соответствующее SQL-выражение компилиру-

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

JDBC предоставляет класс PreparedSta tement , который является подклассомкласса Statement, Для создания экземпляра P r e p a r e d S t a t e m e n t надо вызвать ме-тод C o n n e c t i o n . p r e p a r e d S t a t e m e n t .

String query = "SELECT * FROM Orders WHERE Amount > ?";PreparedStatement pstate;

try (pstate = connection,prepareStatement{query);

)catch(SQLException sqlEx) {

// Обработка SQL-исключения

}

В предварительно подготовленном выражении переменные обозначаются знака-ми вопроса; в таком выражении может содержаться практически неограниченноечисло переменных.

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

p s t a t e . se tDoubleU, 100.00);

try (ResultSet result = pstate.executeQuery[);

1catch(SQLException sqlEx) (

// Обработка SQL-исключения

}

Значении переменных задаются посредством методов setXXX класса Prepared-Statement {XXXсоответствует типу переменной). При вызове такого метода ему пе-редаются дна параметра: один из них указывает позицию переменной (для первой пе-ременной позиция равна I), а второй параметр задает значение. Например, в преды-дущем фрагменте кода метод P r e p a r e d S t a t e m e n t . s e t Double используется для ус-тановки значения 100.00 первой (и единственной) переменной предварительноподготовленного выражения.

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

Page 291: Java Server Pages

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

Ниток* lifemrf Ё^ш

£t% bdi' ^«¥ ЛдоНЧ) ^ M * ДО ЕШ

Tom's Candy Store Sales Database

Find orders over this «noun f jaai i

•hoasaln I

J

i jJ?J «if /Л

CUSTOHEHJD

2

3

?3

3

OMXRJD

2

A

*

i

in

12

АН01АП

e «

Э9 13

112 22

19.B6

9913

; 112 22

НАЛЕ РНОЯЕJUJUBE D i

№*e™*« сиамки

.КП.С»™.!! («2^0532

Рис. Т0.2. Пользовательские дескрипторы, предназначенные для работы с предварительноподготовленными выражениями

Код JSP-локумента. показшиадго плевом окне на рис. 10.2, приведен и листинге 10.9,а.

Листинг 10.9,a. /test_preparedstatements. jsp

<html><head><title>Using Prepared Statements</title><%Э taglib uri='/WEB-INF/tlds/database.tld' prefix='database'%>

</head><font size='5'>Tom'5 Candy Store Sales Database</fontxhr>

<database:prepareStatement id='candyQuery' scope='application'>SELECT * FROM Orders JOIN Customers USING {Custoraer_ID)WHERE Amount > ?

</database:prepareStatement>

<font ='A'

<form action='showPreparedStatementQuery.jsp'>Find orders over this amount: $<input type^'text' name^' amount' size='6'xp><input type='submit' value='show sales'></p>

</form>

</font>

JSP-документ, представленный в листинге 10.9,а, использует для создания предва-рительно подготовленного выражения дескриптор preparestatement. В этом деск-рипторе предусмотрены два атрибута.

1. Идентификатор предварительно подготовленного выражения.

2. Область видимости, в которой хранится предварительно подготовленное вы-ражение.

В составе дескриптора prepareStatement содержится SQL-выражение.

Page 292: Java Server Pages

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

При активизации кнопки Submit формы управление передается документуshowPreparedStatementQuery. jsp, код которого приведен в листинге 10.9,6.

Листинг 10.9,6. /showPreparedStatementQuery.jsp

<html><head><title>Using Prepared Statements</title><fc@ taglib uri='/WEB-INF/tlds/database.tld' prefix='database'%>

</head><body>

<% Double amount = new Double[request.getParameter("amount")); %>

<database:executePireparedStatemenfc id='candyQuery' scope='session'

variables='<%= new Object[] (amount) %>'/>

<p><font size='4'>0rders Over $<%= amount %>:</fontx/p>

<table border='2f cellpadding='5'>

<database:columnHames query='candyQuery' id='name'>

<th><%= name %></th></database:columnNames><database:rows query='candyQuery'><tr>

<tr><database:columns query='candyQuery' id='value'><td><%= value 4><7td></database:columns>

</tr></database:rows>

</table>

<database:release query='candyQuery'/></body></html>

Для выполнения предварительно подготовленного выражения JSP-документ, пред-ставленный в листинге 10.9,6, использует дескриптор executePreparedStatement.В составе данного дескриптора указываются три атрибута.

1. Идентификатор предварительно подготовленного выражения.2. Область видимости, в которой сохраняются результаты запроса.

3. Массив объектов, каждый из которых соответствует знаку вопроса в предвари-тельно подготовленном выражении.

После выполнения предварительно подготовленного выражения JSP-документ,показанный в листинге 10.9,6, использует для отображения результатов запроса деск-рипторы columnNames, columns и rows (эти дескрипторы обсуждались ранее в дан-ной главе).

Класс поддержки дескриптора prepareStatement приведен в листинге 10.9,в.

Page 293: Java Server Pages

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

Листинг 10.9,в. /KEB-INF/clagses/fcags/jdbe/PrepareStatementTag. Java

package tags.jdbc;

import java.sql.Connection;import Java.sql.PreparedStatement;import Java.sql.ResuitSet;import Java.sql.SQLException;

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

import beans.jdbc.DbConnectionPool;import beans.util.ResourceException;

public class PrepareStatementTag extends BodyTagSupport {private String scope = "page";

public void setScope(String scope) {this.scope = scope;

}public int cioAfterBody () throws JspException {

DbConnectionPool pool = [DbConnectionPool)pageContext.getServletContext().getAttribute("db-connection-pool");

Connection con;try {

con = (Connection)pool.getResource();}catch(ResourceException ex) (

throw new JspException(ex.getMessage{));

}PreparedStatement ps = prepareStatement(con);pageContext.setAttribute(id,ps,getConstantForScope(scope));

return SKIP_BODY;}private PreparedStatement prepareStatement[Connection con)

throws JspException {PreparedStatement pstate = null;try (

pstate = con.prepareStatement(bodyContent.getstring(),ResultSet.TYFE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);

}catch(SQLException ex) {

throw new JspException(ex.getMessage());}return pstate;

}private int getConstantForScope(String scope) {

int constant = PageContext.PAGE_SCOPE;

if(scope.equalsIgnoreCase("page"))

Page 294: Java Server Pages

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

constant •= PageContext. PAGE__5COPE;else if(scope.equalsIgnoreCase("request"))

constant = PageContext.REQUEST_SCOPE;else if(scope.equalsIgnoreCase("session"))

constant = PageContext.SESSION_SCOPE;else if(scope.equalsIgnoreCase("application"))

constant = PageContext. APPLICATIONS COPE;

return constant;

)

!

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

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

ЛИСТИНГ 10.9, г. /WEB- IMF/classes /tags/ jdba/Execu tePreparedSta tement-Tag.Java

package tags.jdbc;

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

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

import beans.jdbc.Query;

public class ExecutePreparedStatementTag extends TagSupport (private Object[] variables = nul l ;private String scope = "page";

public void setScope(String scope) {this.scope = 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 {

Page 295: Java Server Pages

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

setVariables(ps, variables);ResultSet rs = ps.executeQuery0;

Query query = new Query(ps, r s ) ;Query.save(queryr pageContext, id, scope);

}catch(SQLException sqlex) {

throw new JspException(sqlex.getMessage[));1r e t u r n EVAL_FAGE;

}

private void setVariables(PreparedStateHient ps,Object[] variables)throws SQLException, JspException {

for(int i=0; i < variables.length; ++i) {Object object = variables[i];

if(object instanceof String) {ps.setString[i+1, (String)object);

}if(object instanceof Integer) [

int value = ( (Integer) object) . irttValue (} ;ps.setInt(i+1, value);

}if (object instanceof Double) (

double value = ((Double)object).doubleValue();ps.setDouble(i+1, value);

)// Для поддержки других типов переменных надо добавитьII дополнительный код. Допустимые типы переменных// описаны в Java.sql.PreparedStatement.else (

throw new JspException("ExecuteStatementTag:" +"setVariables(): unkown parameter type"};

Закрывающий дескриптор executePreparedStatement получает ссылку напредварительно подготовленное выражение с помощью метода PageContext. find-Attr ibute, которому при вызове передается атрибут id дескриптора. Переменныепредварительно подготовленного выражения определяются посредством значений,хранящихся в массиве Object. Этот массив задается с помощью атрибута objects.Впоследствии предварительно подготовленное выражение выполняется, и, подобнодескриптору query, экземпляр Query сохраняется в области видимости, определяе-мой атрибутом scope дескриптора. Затем результаты обрабатываются с помощью де-скрипторов columnNames, columns и rows.

Page 296: Java Server Pages

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

ТранзакцииТранзакция— это группа SQL-выражений, предназначенных для выполнения.

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

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

< d a t a b a s e : t r a n s a c t i o n >INSERT INTO Customers VALUES (100, ' B i l l ' , ' ( 8 8 2 ) 8 6 3 - 4 9 7 1 ' ) ;INSERT INTO Customers VALUES (101, ' R o y ' , ' ( 8 8 2 ) 8 6 3 - 4 9 7 2 ' } ;INSERT INTO Customers VALUES (102, ' R o n ' , ' ( 8 8 2 ) 8 6 3 - 4 9 7 3 ' }

< / d a t a b a s e : t r a n s a c t i o n >

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

<database:transaction file='addCustomers.tx'/>

Код JSP-документа, содержащего дескриптор transact ion, приведен в листинге10.10,а.

Листинг 10.10,a. / test_transact ion.

<html><title>Database Example</title><head>

<%@ taglib uri='/WEB-INF/tlds/database.tld' prefix^'database'%></head><body>

<database:transaction file='addCustomers.tx'/>

<database:query id='addCustomers'>SELECT * FROM Customers

</database:query>

<table border='2' cellpadding='5'><database:columnNames query='addCustomers' id='column_name'>

<th><%= column_name %></th></database:columnNames>

<database:rows query='addCustomers'><tr><trxdatabase: columns query=' addCustomers' id=' column_value' >

<td><%= column_value % / d</database:columns></tr></database:rows>

</table>

</body></html>

Page 297: Java Server Pages

Транзакции 301

JSP-документ, показанный в листинге 10.10,а, использует дескриптор transactionдля выполнения SQL-выражений, указанных в файле addCustomers.tx. Затем JSP-документ отображает данные, содержащиеся в таблице Customers, используя для этогодескрипторы query, columnNames, columns и rows.

Класс поддержки дескриптора transaction представлен в листинге 10.10,6.

ЛИСТИНГ 10.10,6. /WEB-INF/classas/tags/jdbc/TransactionTag.Java

package tags.jdbc;

import Java.sql.Connection;import Java.sql.Statement;import Java.sql.SQLException;

import Java.io.BufferedReader;import Java.io.InputStreamReader;

import java.util.StringTokenizer;import javax.servlet.jsp.JspException;import j avax.servlet.j sp.tagext.BodyTagSupport;

import beans.util.ResourceException;import beans.j dbc.DbConnectionPool;

public class TransactionTag extends BodyTagSupport {private String file;

public void setFile[String file) {this.file = file;

)public int doAfterBody() throws JspException {

Connection con « getConnection();String stmts = null;

if (file !<= null) stmts - getStatementsFromFile () ;else stmts - bodyContent.getString();

try {executeTransaction(con, stmts);

}catch(SQLException ex} {

throw new JspExeeption(ex.getMessage());

}return SKIP_BODY;

)

private Connection getConnection() throws JspException {DbConnectionPool pool = (DbConnectionPool)

pageContext.getServletContext().getAttribute("db-connection-pool");

Connection con;try {

con • (Connection)pool.getResource(};Icatch(ResourceException ex) {

Page 298: Java Server Pages

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

throw new JspException[ex.getMessage());1return con;

)private String getStatementsFromFile[) throws JspException {

Buf f eredReader reader - new Buf f eredlReader {new InputStreamReader[

pageContext.getServletContext()getResource&sStream(file)));

String stmts = "", line = null;

try {while((line = reader.readLine()) != null) {

stmts += line;

catch(Java.io.IOException ex) {throw new JspException(ex.getMessage[));

)return stmts;

}private void executeTransaction(Connection con, String stmts)

throws SQLException {boolean wasAutoCommit = con.getAutoCommit();

try (

StringTokenizer tok = new StringTokenizer(stmts, " ; " ) ;

con.setAutoCommit(false);

while(tok.hasMoreElements[)) (

Statement stmt = con.createStatement();String query = (String) tok.nextElement 0;stmt.execute(query);stmt.close();

)

catch [Exception ex) {con.rollback();

)finally {

con.commit() ;

con.setAutoCommit(wasAutoCommit);

Метод TransacionTag . doAf terBody получает из пула соединение с базой данных.Если атрибут f i l e указан, выражения, составляющие транзакцию, читаются из этогофайла, а если атрибут f i l e отсутствует, выражения извлекаются из тела дескриптора.

Метод executeTransacion выполняет следующие действия.

1. Сохраняет свойство autocommit соединения.

2. Устанавливаетзначениесвойстваа^осотпис равным false.

Page 299: Java Server Pages

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

3. Выполняет каждое выражение.

4. Отменяет изменения, если в процессе выполнения возникает исключение.

5. Если исключение не генерируется, метод вносит изменения.

6. Восстанавливает свойство autocommit соединения.

Прокрутка набора результатовВ некоторых случаях целесообразно вместо отображения всего набора результа-

тов дать возможность пользователю организовать прокрутку его строк.В данном разделе показано, как реализовать прокрутку, модифицировав дескрип-

тор rows, осуществляющий перебор строк набора результатов. Модификация пред-полагает добавление атрибутов, которые задают начальную и конечную строки, огра-ничивая возможности перебора. Модифицированный дескриптор используется со-вместно с простым компонентом bean, предназначенным для прокрутки наборарезультатов. Приложение, в составе которого присутствует модифицированный де-скриптор rows, показано на рис. 10.3.

£4* £де у*-*-

Customers.

a^>vt Iwi i вирift= »' ft^etL'jfo-KnlUi aoKJ

CUSTOMERJD КАМЕ

1

.3

•in™

WHiam Duponl

Kfi; Cromwell

| John Smith

ADDRESS

(в5!)482-0Ю1

(652)482-0932

(652H82-OB33

ЛсПГ|

- •

L-IJI.J.'.I..I'.HHJ9 * £4* №*#

Customer:

CUSTOMER.

*

.6

< >

.ID NAME

Jim Wtleon

Lynn Seckinger

Richard Tatersan

• • • •Ор>«™и», _^ .

ADDRESS

(852)482-0634

(652)482-0935

(652ИВ2-0936

" l » lО

1

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

Рассматриваемое приложение одновременно отображает три строки набора ре-зультатов и содержит ссылки на следующую и предыдущую страницы.

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

Листинг10.11,а,/test scro l l ,

<html><title>Database Example</title><head>

<%@ taglib uri='/WEB-INF/tlds/database.tld' prefix-'database'%></head><body>

<database:query id='customers' scope='session'>SELECT * FROM Customers

</database:query>

Page 300: Java Server Pages

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

<font size='4'>

<а href=' scrollQuery. jsp' >Show Customers</aXp></font>

</body></html>

Данный JSP-документ использует дескриптор query для выполнения запроса и со-

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

держится ссылка на файл scrollQuery. jsp, содержимое которого представлено в

листинге 10.11,6.

ЛИСТИНГ 10.11,6. /scrollQuery.jep

<htmlxtitle>Customers</title><head>

<*@ taglib uri='/WEB-INF/tlds/database.tld' prefix='database'4></head><body>

<jsp:useBean id=f scroller' class='beans.ScrollBean'

scope='session' ><jsp:setProperty name='scroller' property='position' value='1'/><jsp:setProperty name='scroller' property='pageSize' value='3'/>

</j sp:useBean>

<% scroller.scrolHrequest.getParameter ("scroll") ); %>

<pxfont size=M' >Customers:</fontx/p><table border-'2' cellpadding='5'>

<database:columnNames query='customers' id='name'><th><%= name %></th>

</database:columnNames>

<database:rows query='customers'startRow='<%= scroller.getPosition() %>'endRow='<%= scroller.getEndPositionO *>'>

<tr><database:columns query='customers' id='value'>

<tdx%= value %></td></database:columns>

</tr></database:rows>

</table>

<database:release query='customers'/>

<a href='scrollQuery.jsp?scroll=back'>slt;</a><a href='scrollQuery.jsp?scroll-fwd'>&gt;</a>

</body></html>

Page 301: Java Server Pages

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

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

Для контроля за строками в документе используется компонент ScrollBean, кото-рый отслеживает позицию прокрутки и размер страницы. В составе компонентаScrollBean содержится метод scrol l , которому при вызове передается строка "f wd"или "back". Метод scro l l вычисляет позицию, которая зависит от размеров страницыи направления прокрутки. Код класса ScrollBean приведен в листинге 10.11, в.

ЛИСТИНГ 10.11,В. /WEB-INF/classes/beans/util/ScrollBean . Java

package beans.util;

public class ScrollBean {private int 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 < 1} ? 1 : position;

)else

position = 1;

return position;}public synchronized void setPosition(int position) (

this.position = position;1public synchronized void setPageSize(int pageSize) (

this.pageSize = pageSize;}public synchronized int getPosition () {

return position;)public synchronized int getEndPositionO {

return position + pageSize;

Класс поддержки дескриптора rows представлен в листинге 10.11,г.

Page 302: Java Server Pages

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

Листинг 10.11,г. /WEB-INF/classes/taga/jdbc/RowsTag. Java

package tags.jdbc;

import java.sql.ResultSet;import java.sql.Statement;import Java.sql.SQLException;import javax.servlet.jsp.JspException;import javax.servlet.jsp.tagext.BodyTagSupport;import tags.jdbc.beans.Query;

public class RowsTag extends BodyTagSupport {private static int UNASSIGNED=-1;

private ResultSet rs;private boolean keepGoing;private String query;private int startRow = UNASSIGNED, endRow = UNASSIGNED;

public void setQuery(String query) {this.query =• query;

)public void setStartRow(int startRow) {

this.startRow = startRow;}public void setEndRow(int endRow) {

this.endRow = endRow;}public int doStartTagO throws JspException {

rs = Query.getResult(pageContext, query);

try {if(startRow = UNASSIGNED) {

keepGoing = rs.next(); // Указывает на первую строку}else {

iffstartRow < 1)startRow = 1;

keepGoing = rs.absolute(startRow);

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

)if(keepGoing) return EVAL_BODYJTAG;else return SKIP_BODY;

}public int doAfterBody() throws JspException 1

try {if(endRow == UNASSIGNED) {

if(rs.isLast {)) (rs.beforeFirst ();writeBodyContent();return SKIP BODY;

Page 303: Java Server Pages

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

else (if(rs.getRow() == endRow || rs.isLast()) {

rs.beforeFirst();writeBodyContent();return SKIP_BODY;

rs.riext () ;|catch(java.sql.SQLException ex) {

throw new JspException(ex.getMessage() ) ;}return EVAL_BODY_TAG;

}public void writeBodyContent() throws JspException

try ibodyContent.writeOut(pageContext.getOut0 ) ;

}catch(Exception e) {

throw new JspException(e.getMessage[));

Класс поддержки дескриптора rows выполняет перебор заданного подмножествастрок набора результатов. Для определения подмножества строк в дескрипторе ука-зываются начальная и конечная строки.

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

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

2. Организация пула ресурсов, в частности пула соединений с базой данных.

3. Дескрипторы, реализующие дополнительные возможности при работе с база-ми данных.

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

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

Page 304: Java Server Pages

XML

В этой главе...

• Генерация ХМL-документов.

• Создание компонентов bean на основе XML-данных.

• Обработка сгенерированных XML-данных.

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

• SimpleAPIforXML(SAX).

• SAX-разбор с применением пользовательскихдескрипторов.

• Document Object Model (DOM).

• Пользовательские дескрипторы для DOM-разбора.

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

• Совместное использование XSLT и JSP.

• Генерация HTML-кода путем XSLT-преобразованияс помощью пользовательского дескриптора.

• Применение XSLT для генерации JSP-кодана этапе компиляции.

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

Page 305: Java Server Pages

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

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

• Генерация XML-данных на основе одного или нескольких источников инфор-мации.

• Разбор XML-информации и создание объектов на стороне сервера или XML-документов.

• Преобразование XML-данных в документы на других языках, например HTMLили WML.

Генерировать XML-документы средствами JSP несложно, поскольку в качестве JSP-шаблона можно использовать любые данные, в том числе XML-код. (Здесь под шабло-ном понимается JSP-файл, в котором отсутствуют действия, директивы, выражения искриптлеты. Шаблоны, о которых здесь идет речь, не имеют никакого отношения кшаблонам, рассмотренным в главе 4.) В начале данной главы мы обсудим использова-ние JSP и компонентов bean для генерации XML-документов.

Существует много способов совместного использования JSP и XML; например, нарис. 11.1 показана архитектура Model 2, предназначенная для генерации XML-доку-ментов на основе информации, хранящейся в базе данных. Средства XSLT позволяютпреобразовывать XML в документ другого типа: HTML, JSP или один из "диалектов"XML, например WML (Wireless Metalanguage).

XML-разбор для создания объектов на стороне сервера обычно осуществляетсясредствами SAX (Simple API for XML) и DOM (Document Object Model). Подробно этиAPI будут рассмотрены далее в данной главе.

Page 306: Java Server Pages

310 Глава 11. XML

Реализуется разработчиками программ

Реализуется авторами Web-страниц

XML WML

JSP

~

HTMLОкончательные документы

Рис. 11.1. Архитектура M o d e l 2 с использованием XML и XSLT

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

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

• Java Development Kit (JDK) 1.3

• Tomcat 3.2.1 Final

• Apache Xeices-J a va version 1.2.2

• Apache Xalan-Java version 1.2.2

Пакет JDK можно скопировать, обратившись к узлу h t t p : //www. Java, aun.com, aсервер Tomcat находится по адресу h t t p : // jakar ta .apache.org/tomcat/ index. html,

Xerces п Xalan — это соответственно программа XML-разбора и XSLT-процессор,Дополнительную информацию о данных продуктах можно найти по адресу h t t p : //x m l . a p a c h e . o r g / x a l a n / i n d e x . h t m l .

Page 307: Java Server Pages

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

Генерация XML-документовВ листинге 11.1 представлен код чрезвычайно простого JSP-документа, содержа-

щего минимальный набор динамических данных. В процессе выполнения JSP-кода ге-нерируется XML-документ.

Листинг 11.1. /date.jsp

<%@ page contentType='text/xml' %>

version="1.0" encoding="ISO-8859-l"?><document>

<date><%= new Java,uti l .Date () %>

</date></document>

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

Внешний вид Web-страницы, которую генерирует код, содержащийся в листинге 11.1,показан на рис. 11.2.

Единственным видом динамического содержимого на данной Web-странице явля-ется информация о дате. Как видно из рис. 11.2, выражение вычисляется, и результатвключается в сгенерированный XML-документ.

х|

ЕЙ* »«* Favorite Took tfslp

?xml version-" 1.0" encoding-1150-8S59-1" ?>d'.-::i.inler-it><ti3tt>Thu Dec 14 10:51:59 MST 2000</dats>

' ; 4 j Local inhanrt

J

J

Рис. 11.2. Генерация XML-документа средствами JSP

Генерация XML-данных с помощьюкомпонентов bean

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

Page 308: Java Server Pages

312 Глава 1 1 . XML

Jjflt Ioo!s

Addnstjg] htlp MrKjItosI Wffil/vml/rwenlCTy.jip :у.

O«ml version-" 1.0" enU)ding-"(SCi-B359-r J>

>Anttque breo<ci*i.;iiiJti iF>fri»m the early 170Q's</ci^tcnudari>< t* i i e > 12 38.99 <Лтад >

«name > Gum by л nit Рокеу-г/иагти??<;йэ?=г;кЬвг!>р11лЫя ettion naurBSi/descri№an><pries>2.99 -С/РГЙ*?

. excellent s

<d9#cripl ^r->One of the first gds -engine

<!ргкв>499Д5</ог11:р>

Рис. 11.3. Генерация XML-кода с помощью JSP иJavaBeans

Код JSP-документа приведен в листинге 11.2,а.

Листинг 1.2,a. /inventory.jap

<%@ page contentType='text/xml' %><%@ page import='beana.Inventory' %><%@ page import='beans.Item' %>

<jgp:useBean id='inventory' class='beans.Inventory' scope='page'/>

<% java.util.Iterator it = inventory.getItems();Item item - null;

version="1.0" encoding-"ISO-8B59-l"?><inventory>

<% while(it.hasNextO) { %><% item = (Item)it.next(); l><item>

<name><%= item.getName() %></name><description><%= item.getDescription()<price><%= item.getPrice() %></price>

</item><% ) %>

</inventory>

%></description>

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

Page 309: Java Server Pages

Генерация 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 java.ut i l .I terator;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 Iterator 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-документами,но и выполнять его обработку.

Page 310: Java Server Pages

314 Глава 11. XML

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

<xslt:apply xsl='date.xsl'>

<%@ include file='genXML.jsp' %>

</xslt:apply>

В данном случае xsl t :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.Serial izable (

public void prirttXml (PrintStream s) (s.println["<inventory>") ;

Iterator it = getltemsf);while(it.hasNext[)) {

Item item = [Item]it.next();

Page 311: Java Server Pages

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

item.printXml[s, 1) ;

s.println("</inventory>") ;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("<item>");

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

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

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

s.println("<name>");s.println(getName О ) ;s.println("</name>");

s.printIn("<description>");s.printIn(getDescription()) ,s.printIn("</description^'}

s.println("<price>");s.printIn(getPrice()) ;s.printIn("</price>");

dolndent(s,indent); s.printIn("</item>");Iprivate void dolndent(PrintStream s, int indent, int cnt) {

fortint i=0; i < cnt;dolndent(s,indent)j

private void dolndent(PrintStream s, int indent) {fortint i=0; i < indent;

s.print{" " ) ;

I}

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

без труда отобразить XML-данные в компоненты bean, а также реализовать обратноеотображение. На момент написания данной книги приближался к завершению про-ект Adelard компании Sun, предназначенный для генерации компонентов bean на ба-

Page 312: Java Server Pages

316 Глава 11. XML

зе XML. Дополнительную информацию о проекте Adelard и XML Productivity Kitможно найти по следующим UR.L:

• http://Java.sun.cora/xml/• http://ww.alphaworks.ibm.com/tech/xmlproductivity

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

Обработка сгенерированных XML-данныхНезависимо от того, создает ли JSP-документ XML-код или нет, вы можете обраба-

тывать данные, сгенерированные этим документом, одним из двух способов.

• Создавать пользовательские дескрипторы, предназначенные для перехвата иобработки выходных данных.

• Использовать для обработки выходных данных фильтр сервлета.

На момент написания данной книги спецификация Servlet 2.3 еще не была завер-шена, поэтому мы будем следовать подходу, подразумевающему применение пользо-вательских дескрипторов. В листинге 11.4, а показан JSP-документ, который сохраняетв файле XML-данные, сгенерированные примером, показанным в листинге 11.2,а. Длясохранения данных применяется пользовательский дескриптор.

Листинг 11.4,a. /test.jsp

<%@ taglib uri='/WEB-INF/tlds/util.tld' prefix='util' %>

<jsp:useBean id='inventory' class='beans.Inventory' scope='page'/>

<% Java.util.Iterator it = inventory.getltems();beans.Item item - null;

<util:streantToFile file='f:/books/jsp/sre/xral/inventory.xml'>version="1.0" encoding="ISO-8859-l"?>

<inventory><l while(it.hasNext()) { %>

<% item = (beans.Item)it.next 0; %><itejn>

<name><%= item.getName(j %></name><description>

<%= item.getDescriptionO %></description> /"<price><%- item.getPriceO %></price>

</item>

Page 313: Java Server Pages

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

</inventory>:streamToFile>

Класс поддержки дескриптора u t i l : s t reamToFile представлен в листинге 11.4,6.

Листинг 11.4,6. WEB-INF/claeses/tags/util/StreamToFileTag. Java

package tags.util;

Import java.io.File;import java.io.FileWriter;import j avax.servlet.j sp.JspException;import javax.servlet.jsp.tagext.BodyTagSupport;

public class StreamToFileTag extends BodyTagSupport (private String filename;

public void setFile(String filename) {this.filename = filename;

)public int doAfterBody[) throws JspException (

try {FileWriter writer «• new FileWriter(new File(filename)writer.write(bodyContent.getString[).trim());writer.closet)t

}catch(java.io.IOException e) {

throw new JspException[e.getMessage());>return SKIP_BODY;

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

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

Разбор XML-кодаТеперь, когда вы знаете, как создаются XML-данные, рассмотрим вопросы их

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

Page 314: Java Server Pages

318 Глава 11.XML

Simple API for XML (SAX)SAX является стандартом de facto для разбора XML-кода. Средства SAX были раз-

работаны участниками списка рассылка XML-DEV; первая версия SAX, известная какSAX1, была реализована в мае 1998 г.

Позже SAX1 уступил место SAX2, который был реализован в мае 2000 г. SAX2 имеет су-щественные отличия от SAX1, что делает два этих продукта несовместимыми. Примерыиспользования SAX, приведенные в данной главе, ориентированы на SAX2. Дополнитель-ную информацию о SAX можно найти по адресу h t t p : //www .megginson. com/SAX.

Средства разбора SAX работают быстро, поскольку в отличие от Document ObjectModel (DOM), они не строят в памяти дерево, представляющее структуру XML-документа.

При работе SAX используется обратный вызов; средства разбора обращаются кобъектам, которые называются обработчиками (handler), оповещая их о нахождениикомпонентов — элементов, атрибутов и текста. Подход, используемый при разборесредствами SAX, напоминает модель делегирования событий, описанную в главе 7.

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

• o r g . x m l . s a x . C o n t e n t H a n d l e r

• org.xml.sax.DTDHandler

• o r g . x m l . s a x . E r r o r H a n d l e r

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

1. Реализация одного или нескольких обработчиков.

2. Создание объекта чтения XML {org. xml. sax . XMLReader).

3. Связывание обработчиков, построенных на первом этапе, с объектом чтения,созданном на втором этапе.

4. Создание источника входных данных (org. xml . s a x . I n p u t Sour se).

5. Вызов метода XMLReader . p a r s e ; при вызове этому методу передается источ-ник входных данных, созданный на этапе 4.

В процессе разбора объект чтения XML вызывает методы обработчиков; напри-мер, в интерфейсе E r r o r H a n d l e r объявлен метод warning, который вызывается то-гда, когда SAX генерирует предупреждение.

Часто SAX-приложен и я реализуют все три интерфейса обработчиков, а это озна-чает необходимость написания кода 19 методов. В качестве альтернативного подходаможно использовать класс Defaul tHandler , содержащийся в пакете org .xml.sax. h e l p e r s . В этом классе реализованы все три указанных выше интерфейса; объ-явленные в этих интерфейсах методы не выполняют никаких действий. Создаваяприложение, разработчик переопределяет требуемые методы. В листинге 11.5,а при-веден код компонента bean, в котором инкапсулированы средства SAX-разбора с ис-пользованием Apache Xerces.

1 В SAX2 также определен дополнительный интерфейс Entity-Reference, который в данной книгеin-liatt-vrimpitutiemm. - Прим. авт.

Page 315: Java Server Pages

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

ЛИСТИНГ 11.5,a. /WEB-INF/classBs/beans/annl/sax/SAXParserBean. Java

package beans.xml.sax;

import java.io.FileReader;import org.xml.sax.Attributes;import java.io.IOException;import java.util.Vector;import org.xml.sax.Attributes;import org.xml.sax.InputSource;import org.xml.sax.SAXException;import org.xml.sax.XMLReader;import org.xml.sax.helpers.DefaultHandler;import org.apache.xerces.parsers.SAXParser;

public class SAXParserBean extends DefaultHandler {private Vector vector = new Vector[);private SAXElement currentElement = null;private String elementText;

public Vector parse(String filename) throws SAXException,IOException {

XMLReader xmlReader = new SAXParser();FileReader fileReader = new FileReader{filename);

xmlReader.setContentHandler(this);jcmlReader.parse(new InputSource(fileReader)};return vector;

!public void startElement[String uri, String localName,

String qName, Attributes attrs) {cu^rentElement = new SAXElement(uri,localName,qName,attrs);vector.addElement(currentElement);elementText = new StringO;

}public void characters(char[] chars, int start, int length) {

if(currentElement != null £& elementText != null) {String value = new String[chars, start, length);elementText += value;

1}public void endElement (String uri, String localName,

String qName) {if(currentElement != null && elementText != null)

currentElement.setValue(elementText.trim 0 ) ;

currentElement = null;

Класс SAXParserBean является подклассом DefaultKandler и реализует все триинтерфейса SAX-обработчиков, поэтому первое из перечисленных выше действийвыполняется автоматически.

Page 316: Java Server Pages

320 Глава 11. XML

Подобно всем средствам разбора SAX, Xerces, используемый SAXParserBean,реализует интерфейс XMLReader, поэтому второе из указанных выше действий вы-полняется путем установки средств разбора.

Реализация третьего, четвертого и пятого этапов, как видно из листинга 11.5,а,сводится к установке обработчика и выполнению разбора с использованием источни-ка данных.

В классе SAXParserBean переопределены методы startElement, endElement иcharacters, объявленные в интерфейсе Content Handler. В листинге 11.5,а эти триметода вызываются при встрече каждого элемента и создают вектор компонентов,представляющих XML-элементы. Данные компоненты являются экземплярами классаSAXElement, код которого представлен в листинге 11,5,6.

Листинг 11.5,6. /WEB-INF/ classes /bean s/xml/sax/SAXElement. Java

package beans.xml.sax;

import org.xml.sax.Attributes;

public class SAXElement (String namespace, localName, qualifiedName, value=null;Attributes attributes;

public SAXElement(String namespace, String localName,String qualifiedName, Attributes attributes){

this.namespace = namespace;this.localName = localName;this.qualifiedName - qualifiedName;this.attributes = attributes;

}public void setValuefString value) (

this.value = value;

)public String getNamespace() { return namespace; }public String getLocalName() ( return localName; )public String getQualifiedName() ( return qualifiedName; Ipublic String getValueO ( return value; }public Attributes getAttributes() ( return attributes; }

Экземпляры класса SAXElement содержат информацию об XML-элементах, кото-рая доступна другим объектам только для чтения. Экземпляр класса SAXElement соз-дается и заполняется данными в теле метода ContentHandler. startElement(см. листинг 11.5,а).

НА рис. 11.4 показан JSP-документ, который использует компонент, приведенный влистинге 11.5,а, для разбора XML-файла, содержащего перечень книг.

Page 317: Java Server Pages

в 'SAX E m * • МпмоЛ Мят* Ечкщ

fill ysr xin

Book Inventory:

Tldi : Pomls Unknownprict: ИЗ .96

Title; A New Kind of Sciencepries: 110.75

TWi: 111» Sciential in Ih» CribP'ict. (9.95

Tllle. Ttrs Ajiceleisling IJniieisephce: (19 55

Tl«e: Saundan Pharmsceuncjl Wind Bookprice: S i : 25

i*; Dn

Раэбор XML-кода 321

- ,Г-Loral Him*

Рис. 11.4. Использование SAX

Содержимое XML-файла, разбор которого осуществляет приложение, показано влистинге II,5,в.

Листинг 11.5,в. /booklnventory.xml

version="1.0" encoding="I30-e859-l"?>

<inventory><book>

<ISBN>0393040009</ISBH><title>Points Unknown</title><price>$23.96</price>

</book><book>

<ISBN>1579550086</ISBN><title>A New Kind of Science</title><price>$10.75</price>

</book><book>

<ISBN>0416399760</ISBN><title>The Accelerating Universe</title><price>$19,55</price>

</book></inventory>

Page 318: Java Server Pages

322 Глава 11. XML

Код JSP-документа, представленного на рис. 11.4, содержится в листинге 11.5,г.

Листинг 11.5,г. /test^sax. jsp

<htmlxheadxtitle>SAX Example</title>page import='Java, lit il.Collection' %>page import='Java.util.Iterator' %>

<£@ page import='beans.xml.sax.SAXParserBean' %><%@ page import='beans.xml-sax.SAXElement' %>

</head><body>

<jsp:useBean id='parser' class='beans.xml.sax.SAXParserBean'scope='page'/>

<% Collection elements = parser.parse("f:/books/jsp/src/xml/booklnventory.xml");

Iterator it = elements.iterator();%>

<font size='5'>Book Inventory:</font><p>

<% while(it.hasNext() ) (SAXElement element = (SAXElement)it.next();String tagName = element.getLocalName{);String showThis = null;

if(tagName.equals("title")) fshowThis = "<b>Title:</b> " +

element.getValue() + "<br/>";Ielse if(tagName.equals("price")) {

showThis = "price: " + element.getValue() + "<p>";

<%=> showThis =- null ? "" : showThis %>

</body></html>

JSP-flOK)pMCHT, представленный в листинге, осуществляет перебор элементов(SAXElement}, которые были созданы в процессе разбора компонентом SAXParser-Bean. Имя дескриптора возвращает метод SAXElement .getLocalName, а значениедескриптора позволяет определить метод SAXElement. getValue.

Page 319: Java Server Pages

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

SAX-разбор с применением пользовательскихдескрипторов

Пользовательские дескрипторы, которые содержатся в документе, представлен-ном в листинге 11.6, полностью исключают Java-код из^Р-документа. Документ, при-веденный в листинге 11.6, выполняет те же функции, что и рассмотренный ранее JSP-документ. код которого был показан в листинге 11.5,г.

Листинг 11.6. /test_sax.jsp — выполняет те же функции, что и документ,показанный в листинге 11.5,г

<html><head><title>SAX Example</title><%@ taglib uri='/WEB-INF/tlds/sax.tld' prefix^'sax' %><l@ page import='beans. xml.sax.SAXElement' %>

</head><body>

<font size=' 5' >Book Inventory: </fontxp>

<зах:iterateElements id='element'xmlFile='f:/books/jsp/sre/xml/booklnventory.xml'>

<sax:ifElementNameEquals element='<%= element %>'names='title'>

<b>Title:</b> <%= element.getValue() %><br/></sax:ifElementNameEquals><sax:ifElementNameEquals element^'<%= element %>'

names=fprice'>

price: <%= element.getValue() %><p></sax:ifElementNameEquals>

</sax:iterateElements></p></body></html>

Дескриптор iterateEleraents использует класс SAXParserBean, приведенный влистинге 11.5,а, для разбора XML-документа и перебора элементов, a ifElement-NameEquals включает тело дескриптора в зависимости от имени текущего элемента.

Дескриптор i terateElements создает для текущего элемента переменную сцена-рия; имя этой переменной определяется атрибутом id данного дескриптора. Такимобразом, каждый из XML-элементов, содержащихся в файле inventory.xml, стано-вится доступным посредством переменной сценария element.

Переменная сценария, сгенерированная дескриптором iterateEleraents, пере-дается дескриптору ifElementNameEquals. Этот дескриптор включает содержащие-ся в нем данные в том случае, если имя элемента совпадает с именем, указанным по-средством атрибута names.

На рис. 11.5 показано более сложное приложение, которое использует рассмот-ренные выше дескрипторы для просмотра файла описания библиотеки дескрипторов(TLD — Tag Library Descriptor).

Page 320: Java Server Pages

324 Глава 11. 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. Thislibrary also includes tags for prepared statements and transactions.

TsgNama

transaction

Tag Class

19 g s. jtJbc. TransactionTag

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

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

query lags.jdbc. OueryTag

raws tags.jdbc. Rows Tag

с о 1 umnNames jtags.jdbc. ColumnNamasTag

columns lags jdbc. Column sTag

release tags. jdbc. RalaaseTa g

Body Content

tagdependent

tagdependent

None

tagdependent

JSP

JSP

JSP

JSP

Attributes

file

scope id

id scope variables

id scope update

query startRow endRow

query id

query id

query

J;*fc Local i t t m l

Puc. 11.5. Разбор TLD-файла с применением пользовательских дескрипторов JSP

В листинге П,7,а представлено содержимое TLD-файла, разбор которого осущест-вляется с помощью JSP-документа, показанного на рис. 11.5.

Листинг 11.7,a. database. t l d

version="1.0" encoding-"!SO-a859-l" ?><!DOCTYPE taglib PUBLIC

"-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN""http://Java.sun.com/j 2ee/dtds/web-j sptaglibrary_l_l.dtd">

<taglib><tlibversion>l.0</tlibversion><jspversion>l.l</jspversion><shortnarae>

Sun Microsystems Press Database Tag Library</shortname><info>

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

</info><tag>

<name>transaction</name><tagclass>tags.jdbc.TransactionTag</tagclass>

<bodycontent>tagdependent</bodycontent><attribute>

<name>file</name><required>false</required><rtexprvalue>true</rtexprvalue>

Page 321: Java Server Pages

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

</attribute></tag>

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

</taglib>

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

Листинг 11.7,6. /test

<htmlXheadxtitle>SAX Example</title><%@ taglib uri='/WEB-INF/tlds/sax.tld' prefix='sax'%><%@ page import='tags. util.IteratorTag' %><4 boolean inAttributes = false, bodyContentSpecified = false;

String attributeData = null; %></head><body>

<%-- Отображение имени библиотеки дескрипторов --%><sax:iterateElements id='element'

xmlFile='f:/books/jsp/src/xmi/database.tld'><sax:ifElementNameEquals element^'<%=element%>'

names='shortname'><font size='5'x%= element.getValue () %></fontxp>

</sax:ifElementNameEquals>

<sax:ifElementNameEquals element='<%=element%>'names='info'>

<p><font size='4'X%= element.getValueO %></fontx/p></sax:ifElementHameEquals>

</sax:iterateElements>

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

<table border»'1' cellpadding='2'><th>TagName</th><th>Tag Class</th><th>Body Content</th><th>Attributes</th>

<sax:iterateElements id='element'xmlFile='f:/books/jsp/src/xml/database.tld'>

<% if( ! inAttributes) { %><sax:ifElementNameEquals element^'<%=element%>'

names='name'><trxtd><%= element.getValueO %></td>

</sax:ifElementNameEquals>

<saxrifElementKameEquals element-'<%=element%>'names='tagclass'>

<td><%= element.getValueO %x/td></sax:i fElementNameEquals>

Page 322: Java Server Pages

326 Глава 11. XML

<sax:ifElementNameEquals element='<%=element%>'names='bodycontent'>

<td><%= element.getValue() %></td><% bodyContentSpecified = true; %>

</sax:ifElementNameEquals>

<sax:ifElementNameEquals element^'<%=element%>'names='attribute'>

<% inAttributes = true;attributeData = new StringO;

if[ I bodyContentSpecified) [ !>

<td>JSP</td>

<% }bodyContentSpecified = false; %>

</sax:ifElementNameEquals><% }else { %>

<sax:ifElementNameEquals element^'<%=element%>'names='name'>

<% attributeData += element.getValue[) + " "; %></sax:ifElementNameEquals>

<sax:ifElemenfNameNotEquals element-'<%=element%>'names='attribute name required rtexprvalue'>

<td><%= attributeData %></td><% inAttributes = false; %>

</sax:ifElementNameNotEquals><% } %></sax:iterateElements><td><%- attributeData %></td></tr>

</table></p>

</body></html>

JSP-документ, показанный в листинге 11.7,6, дважды перебирает элементы файлаd a t a b a s e . t l d ; один раз — для обработки элементов shortname и in fo, а второй —для построения HTML-таблицы. Однако разбор XML-файла (database . t l d ) выпол-няется только один раз.

Тело i t e r a t e E l e m e n t s можно сравнить с оператором switch, реализованнымсредствами пользовательских дескрипторов; особенности обработки элемента зави-сят от его имени. Например, для отображения значения элемента 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;

Page 323: Java Server Pages

Разбор 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; перебор организуется методами суперкласса.

Page 324: Java Server Pages

328 Глава 11. XML

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

Для определения переменных сценария используется подкласс класса TagExtra-Info, который рассматривался в главе 2. Код класса SAXParserTaglnfo приведен влистинге 11.7,г.

Листинг 11.7,г. /WEB-INF/classes/tage/aaru/saji/SAXParserTagXnf о. Java

package tags.xml.sax;

Import javax.servlet.jsp.tagext.TagData;import javax.servlet.jsp.tagext.TagExtraInfo;import javax.servlet.jsp.tagext.VariableInfo;import beans.xml.sax.SAXElement;

public class SAXParserTaglnfo extends TagExtralnfo {public Variablelnfol] getVariableInfo(TagData data} (

return new Variablelnfо[] {new Variablelnfo(data.getld(),

"beans.xml.sax.SAXElement",true, Variablelnfo.NESTED),

new Variablelnfo("next", // Имя переменной сценария"beans.xml.sax.SAXElement", II Тип переменнойtrue, // Должна ли переменная создаватьсяVariablelnfо.NESTED) П Область видимости

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

Класс поддержки дескриптора sax: ifEleraentNameEquals приведен в листинге11.7л

ЛИСТИНГ 11,7,Д. /WEB-INF/classes/tags/xml/aax/

IfElementNameEqualsTag.Java

package tags.xml.sax;

import java.util.StringTokenizer;import java.util.NoSuchElementException;import javax.servlet.jsp.JspException;import javax.servlet.jsp.tagext.TagSupport;import beans.xml.sax.SAXElement;

public class IfElementNameEqualsTag extends TagSupport {private SAXElement element = null;private String names = null;

Page 325: Java Server Pages

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

public void setElement(SAXElement element) (this.element = element;

}public void setNames(String names) [

this.names = names;}public int doStartTag() throws JspException {

StringTokenizer tok « new StringTokenizer(names);String nextName - null,

elementName = element.getLocalName();boolean nameFound = false;

try 1while(!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 extendsIfElementNameEqualsTag {

public int doStartTag() throws JspException (int rvalue = super.doStartTagO;return rvalue == EVAL_BODY_INCLUDE ? SKIP^BODY :

EVAL_BODY_INCLUDE;

Page 326: Java Server Pages

330 Глава 1 1 . XML

Класс IfElementNameNotEqualsTag является подклассом IfElementName-EqualsTag. Метод doStartTag возвращает значение, обратное по сравнению сосвоим суперклассом.

Итак, рассмотренные выше классы взаимодействуют следующим образом. Деск-риптор i terateElements использует экземпляр класса SAXParserBean для разборауказанного XML-файла. Этот дескриптор создает переменную сценария, которая яв-ляется экземпляром SAXElement. Переменная сценария затем используется дескрип-торами ifElementNameEquals и ifElementNameNotEquals.

Document Object Model (DOM)Document Object Model — это интерфейс, обеспечивающий доступ к документам и

их обновление. Программа разбора, созданная на базе этого интерфейса, строит Дре-вовидную структуру, к узлам которой можно обращаться и изменять их содержимое.Средство просмотра XML, построенное с использованием Swing, показано нарис. 11.6; в окне отображается визуальное представление построенного дерева.

- inlx1 inventoryО book9 • ISBN

D 0393040009

|j Points Unknown9 • price

9 • book9 • ISBN

• 15795500889 • title

Q A New Kind of Science9 C3 price

D Л 0.75

©-•tonok«-•book

l ~ l Узлы, представляющие элементы

[_| Узлы, представляющие текст

Рис, T f . e , Дерево, построенное средствами DOM

Дерево, изображенное на рис. 11.6, соответствует следующему XML-коду,

version="1.0" encoding="ISO-6859-l"?>// Полностью код представлен в листинге 11.5,в<inventorу>

<book><ISBN>0393040009</ISBN>

Page 327: Java Server Pages

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

<title>Points Unknown</title><price>$23.96</price>

</book><book>

<ISBN>157 9550088</ISBN><title>A New Kind of Science</title><price>$10.75</price>

</book>

</inventory>

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 Java . io .IQExcept ion;import J a v a . i o . F i l e I n p u t S t r e a m ;import org.apache.xerces .parsers .DOMParser;import org .xml . sax . InputSource;import org.xml.sax.SAXException;import org.w3c.dom.Document;

p u b l i c c l a s s DOMParserBean {p r i v a t e DOMParserBean() U // Экземпляр класса

// создать невозможно.

p u b l i c s t a t i c Document getDocument(String f i l e )throws SAXException, IOException {

DOMParser p a r s e r = new DOMParser();par ser .par se(new 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 parser ,getDocument() ;

Код, ориентированный на конкретное средство разбора, сосредоточен в статиче-ском методе 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. - Прим. авт.

Page 328: Java Server Pages

332 Глава 11. XML

Приложение на рис. 11.7 состоит из двух JSF-документов и иллюстрирует три ал-горитма, реализуемых большинством DOM-прилжоений.

• Разбор XML-кода и создание DOM-документа.

• Модификация существующего документа.

• Создание XML-файла на базе существующего документа.

$>JDQM ExHrffe-MEmoft Intend Еиркая

Book Inventory:

ТШв: Points Unknownprice i*23 96

TW«: A New Kind of Scienceрпсе: |*ю 7Б

ТШ*: The Accelerating Universeprice: 1*19-55

Update DOM Dscument

PrimXML 1

nJ

< ;

21•nel

pie £dlr

<i inventory»

<book>

0393040009</ISBN>«titlesPoints Unknown

J23.S6

1579550088</ISB№

Рис. У 7.7. Обращение к XML-документам и их модификация

JSP-документ, показанный в левом окне на рис. 11.7, выполняет разбор XML-файлаи сохраняет лолучившийся в результате документ в области видимости документа. Ес-ли пользователь активизирует кнопку Update DOM Document, цены, введенные в по-лях редактирования, копируются в соответствующие узлы документа. Если пользова-тель щелкает на кнопке Print XML, управление передается JSP-документу, показанномув правом окне. Этот документ читает данные из области видимости приложения иотображает их в XML-формате.

Код JSP-документа, отображаемого в левом окне на рис. 11.7, представлен в лис-тинге 11.8,6,

Листинг 11.8,6. /test_dom_booka . jsp

<htmlxheadxtitle>DOM Example</title>tagl ib uri='/WEB-INF/tlds/dom.tld' prefix='dom'%>page import='org.w3c.dom.Document' %>page import^'org.w3c.dom.Node' %>page import=

rorg.w3c.dom.ModeList

f %>

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

</head><body>

Page 329: Java Server Pages

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

<% Document document = (Document)application.getAttribute("document");

if(document == null) {document = DOMParserBean.getDocument(

"f:/books/jsp/src/xml/booklnventory.xml");

application.setAttribute("document", document);

}updatePrices(document, request);showDocument(document, out};addButtons[out];

<%! private void updatePrices(Node node,HttpServletRequest request)throws JspException (

NodeList list - node.getChildNodest);int childCnt = liat.getLengthO;

if(childCnt > 0) {String lastTitle - null;

for(int i=0; i < childCnt; ++i) (Node next = list.item(i);String value = next.getNodeValue();

if(next.getNodeTypeO — Node.ELEMENT_NODE) {String nodeName - next.getKodeKame();String text • getElementText(next);

if(text != null) {if(nodeName.equals("title")) {

lastTitle - text;}else if(nodeName.equals("price")) (

String pval = getParameterValue(request,lastTitle);

if (pval != null Si Ipval.equals(text))setElementText(next, pval);

updatePrices(next, request);

)private void showDocument(Node node, JspWriter out)

throws JspException [NodeList list = node.getChildNodes();int childCnt = list.getLengthO;

if{childCnt > 0) 1String lastTitle = null;

for(int i=0; i < childCnt;

Page 330: Java Server Pages

334 Глава 11. XML

Node next = list.item(i);try {

String value = next.getNodeValue ();

if(next.getNodeTypet) == Node.ELEMENT_NODE) {String nodeName = next.getNodeName();String text = getElementText(next);

if(text != null) {if(nodeName.equals("inventory")) {

out.print("<font size='5'>Book " +"Inventory:</fontxf orm>") ;

Ielse if(nodeName.equals("title")) I

LastTitle = text;out.print ("</pxb>Title: </b>" + text +

else if(nodeName.equals("price")) {out.print("price: " +"<input type=' text' size='6' name='" +lastTitle + •' value='" + text+ "

catchfJava.io.IOException ex) (throw new JspExceptioniex.getMessage()

IshowDocument(next, out);

private void addButtons(JspWriter out) throws JspException {try (

out.print ("<hrxinput type=' submit' " +"value='Update DOM Document'/></form><p>");

out.print("<form action='printXML.jsp'>");out.print("<input type-'submit' value='Print XML'" +

/

catchfJava.io.IOException ex) {throw new JspException(ex.getMessage());

private String getParameterValue(HttpServletRequest request,String param) {

Enumeration parameterNames = request.getParameterNames();

while(parameterNames.hasMoreElements()) {String next = (String)parameterNames.nextElement();

if(next.equals(param))

return request.getParameterValues(next)[0];

1

Page 331: Java Server Pages

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

return null;}private String getElementText(Node element) (Node child = element.getFirstChildf);String text = null;

if(child != null)text = child.getNodeValue();

return text»}private void setElementText(Node element, String text) {

Node child = element.getFirstChildO;

if(child [= null)child.setNodeValue(text);

\

</body></html>

В JSP-документе, представленном в листинге 11.8,6, находится небольшой скрипт-лет, за которым следует декларация, содержащая пять методов; три из них вызывают-ся из скриптлета. Для удобства рассмотрения код скриптлета приведен ниже.

<% Document document - (Document)application.getAttribute("document");

if(document == null) {document - DOMParserBean.getDocument(

"f:/books/jsp/src/xml/booklnventory.xml"];

application.setAttribute("document", document);

}updatePrices(document, request);showDocument(document, out);addButtons(out) ;

l>

Как легко заметить, скриптлет проверяет, существует ли документ в области види-мости приложения; если документ отсутствует, производится разбор XML-файла исозданный в результате разбора документ сохраняется в области видимости прило-жения. Затем последовательно вызываются методы u p d a t e P r i c e s , showDocument иaddButtons.

Методы u p d a t e P r i c e s и showDocument предназначены для обхода древовиднойструктуры DOM и допускают рекурсивные вызовы. Метод u p d a t e P r i c e s копируетпараметры запроса, представляющие цепы на книги, в соответствующие узлы доку-мента, а метод showDocument отображает XML-код в окне броузера. При вызове обо-их методов им передается ссылка на DOM-узел, и в обоих методах содержится управ-ляющая конструкция, представленная в листинге 11.8,в.

Page 332: Java Server Pages

336 Глава 1 1 . XML

Листинг 11.8,в. Обход DOM-дерева

// Этот алгоритм обхода дерева DOM используют методы// showDocument() и updatePriceaО , представленные// а листинге 11.8,6.

NodeList list = node.getChildNodes();int childCnt = list.getLengtht);

if[childCnt > 0) {

for[int i=0; i < childCnt; ++i) {Node next = list,item(i);

// Действия с next.

// Здесь рекурсивно вызывается updatePrices(node)// или showDocument(node, out)

И updatePrices, и showDocument получают из узла, переданного им, список до-черних узлов, а затем для каждого дочернего узла осуществляют рекурсивный вызов.Таким образом эти методы обходят все дерево DOM.

Последним в скриптлете вызывается метод addButtons. Этот простой метод ге-нерирует HTML-код для двух кнопок, показанных на рис. 11.7.

Если пользователь активизирует кнопку Print XML, приложение, показанное нарис. 11.7, передает управление документу printXML.jsp, код которого представлен влистинге 11.8,г.

Листинг 11.8,г. /printXMX,- jep

<htmlxheadxtitle>DOM Example</title><%@ taglib uri='/WEB-INF/tlds/dom.tld' prefix='dom'%><%@ page import='org.w3c.dom.Document' %><%@ page import^'org.w3c.dom.Node' %><%@ page import='org.w3c.dom,NodeList' %><l@ page import='javax.servlet.jap.JspWriter' %><%@ page import='javax.servlet.http.KttpServletRequest' l><%@ page import='Java.util.Enumeration' %>

</head><body>

<4 printXML( (Document)application.getAttribute("document") ,out);%>

<% ! private void printXMMNode node, JspWriter out)throws JspException {

NodeList list = node.getChildNodes();int childCnt = list.getLengthO;

for(int i=0; i < childCnt;

Page 333: Java Server Pages

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

Node next = list.item(i);

if (next.getNodeType[) — Node.ELEMENT_NODE) (String name = next.getNodeNamef);try {

out.print ("Sit,- " + name + "figt; <br/>"};if(name.equals("inventory"))

out.print("<br/>");

Icatch(Exception ex) {}

>String value = getElementText(next);

if (value != null is value.charAt(0) != l\n') (

try {out.print(value + "<br/>");

}catch(Exception ex) (}

printXML(next, out);

if(next.getNodeTypef) — Node.ELEMENT_NODE) {String name - next.getNodeNamef);try (

out.print("filt;" + "/" + name + "Sgt;<br/>")

if(name.equals("book"))

out.print ("<br/>");

}

catch{Exception ex) О

}

Iprivate 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-страниц.

Page 334: Java Server Pages

338 Глава 11. XML

Пользовательские дескрипторы для DOM-разбораПростой JSP-файл, представленный в листинге 11.9,а, функционально идентичен

громоздкому документу, который был показан в листинге 11.8,6. Данные, выводимые

обоими документами, показаны в левом окне на рис. 11.7.

Листинг 11.9,a. test_dom_books_with_tags. jap

<htmlxheadxtitle>DOM Custom Tags Example</title>taglib uri=

f/WEB-INF/tlds/dom.tld' prefix='dom'%>

taglib uri='/WEB-INF/tlds/app.tld' prefix='app'l></head><body>

<dom:parse id='document'xmlFile='f:/books/jsp/src/xml/booklnventory.xml'/>

<app:updateFrices/>

<font size='5'>Book Inventory:</font><form><dom:iterate node='<%= document %>' id='book'>

<dom:ifKodeKameEquals node='<%= book %>' names='book'><% String lastTitle = null; %>

<dom:iterate node='<*= book %>' id=fbookElement'>

<dom:ifNodeIsElement node='<%= bookEleraent %>'><dom:elementValue id='value'

element='<%= bookElement %>'/>

<dom:ifNodeNameEquals node='<%= bookElement %>'names^'title'>

<% lastTitle = value; %></pxb>Title:</b>finbsp;<%= value %><br/>

</dom:ifNodeNameEquals>

<dom:ifWodeNameEquals node='<%= bookElement %>'names='price' >

price:Snbsp;<input type='text' size=

f 5' value='<%= value %>'

name='<%= lastTitle %>'/></dom:ifNodeNameEquals>

</dom:ifKodeIsElement></dom:iterate>

</dora:ifNodeKameEquals></dom;iterate><p><hrxinput type='submit' value='Update DOM Document'/></p>

</form>

<form action='printXML.jspf>

<input type='submit' value='Print XML'/></form>

</body></html>

Page 335: Java Server Pages

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

Данный JSP-документ отличается от документа, представленного в листинге 11.8,6,шестью пользовательскими дескрипторами. Эти дескрипторы перечислены в табл. 11.1.

Таблица 11.1. Пользовательские дескрипторы, которые содержатсяв документе, представленном в листинге 11.9,а

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

<dom:parse>

<dom:iterate>

<dom:ifNodeName-

Equals>

<dom:ifNodels-

Element>

<dom:elementValue>

<app:updatePrices>

Выполняет разбор кода, заданногопосредством атрибута xmlFile, исохраняет результирующий доку-мент в переменной сценария

Выполняет перебор узлов, дочер-них по отношению к node, и обес-печивает доступ к ним посредст-вом переменных сценария

Включает тело дескриптора в томслучае, если имя node совпадает содним из значений names

Включает тело дескриптора, еслиnode представляет элемент

Сохраняет значение element впеременной сценария

Копирует параметры запроса, пред-ставляющие цены, в документ, соз-данный посредством <dom: parse>

id xmlFile force

id

node names

node

id element

В данном разделе мы рассмотрим классы поддержки для дескрипторов, приведен-ных в табл. 11,1. В листинге 11.9,6 содержится код класса поддержки для дескриптораparse.

Листинг 11.9,6. /WEB-IKP/clasaes/tags/ionl/dom/DOMParserTag. java

package tags.xml.dom;

import javax.servlet.jsp.PageContext;import javax.servlet.jsp-JspException;import javax.servlet.jsp.tagext.TagSupport;import org.w3c.dom.Document;import beans.xml.dom.DOMParserBean;public class DOMParserTag extends TagSupport [

private String xmlFile;private boolean force = false;

public void setXmlFile[String xmlFile) {

Page 336: Java Server Pages

340 Глава 11. XML

this.xmlFile = xmlFile;)public void setld(String id) (

this.id • id;)public void setForce(boolean force) {

this.force • force;}public int doStartTag[) throws JspException {

Document document - (Document)pageContext.getServletContext{).getAttribute(id);

if(document == null II force)parsed ;

return SKIP_BODY;

)private void parse() throws JspException {

try {pageContext.setAttribute(id,

DOMParserBean. get Document (xmlFile) •,PageContext. AFPLICATION_SCCiPE);

)catch(Exception ex) {

throw new JspException(ex.getMessage());

public void released (xmlFile = null;force • false;

Дескриптор parse выполняет разбор указанного XML-файла; по умолчанию раэ-бор производится один раз. Полученный в результате DOM-документ сохраняется вобласти видимости приложения и доступен другим дескрипторам посредством пере-менной сценария. Вы можете указать дескриптору parse повторно выполнить раз-бор, для этого надо задать значение true атрибута force {по умолчанию значениеэтого атрибута равно true).

Для выполнения разбора дескриптор parse вызывает статический метод DOMParser-Bean. getDocument. Этот метод, инкапсулирующий непереносимые фрагменты кода,рассматривался ранее в этой главе.

Переменная сценария для DOM-документа определяется с помощью классаDOMParserTaglnfo, являющегося подклассом класса TagExtralnfo. Код классаDOMParserTaglnfo приведен в листинге 11.9,в.

Листинг 11.9,в. /WEB-XNF/classes/taga/xml/dom/DOMParaerTaglnfо. Java

package tags.xml.dom;

import javax.servlet. jsp.tagext.TagData;import javax.servlet. jsp.tagext.TagExtralnfo;

Page 337: Java Server Pages

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

import javax.servlet.jsp.tagext.Variableln.fo;

public class DOMParserTaglnfo extends TagExtralnfo {public VariableInfo[] getVariableInfo(TagData data) {

return new Variablelnfo[] {new Variablelnfo(data.getld(),

"org.w3c.dom.Document",true, Variablelnfo.ATTEND)

Имя переменной сценария, используемой для доступа к документу, определяется спомощью атрибута id дескриптора parse.

Изменения вносятся в документ, созданный с помощью дескриптора parse, по-средством пользовательского дескриптора updatePrices. Дескриптор update-Prices устанавливает значения цен в соответствии с параметрами запроса. Классподдержки дескриптора updatePrices приведен в листинге 11.9,г.

Листинг 11.9,г. /WEB-INF/elassee/fcags/app/OpdatePricesTag. Java

package tags.app;

import java.util.Enumeration;import javax.servlet.ServletRequest;import javax.servlet.jsp.JspException;import javax.servlet•jsp.PageContext;import javax.servlet.jsp.tagext.TagSupport;import org.w3c.dom.Document;import org.w3c.dom.Node;import org.w3c.dom.NodeList;

public class UpdatePricesTag extends TagSupport {public int doEndTag() throws JspException {

Document document = (Document)pageContext.getAttribute("document",PageContext.APFLICATION_SCOPE);

if(document != null)updatePrices(document);

return EVAL_PAGE;}private void updatePrices(Node node) throws JspException [

ServletRequest request = pageContext.getRequest();NodeList list - node.getChildNodesf);int childCnt - list.getLength();

if(childCnt > 0) {

String lastTitle = null;

for(int t=0( i < childCnt; ++i) {Node next = list.item(i);String value = next.getNodeValue();

Page 338: Java Server Pages

342 Глава 11. XML

if(next.getNodeTypeO == Node.ELEMENT_NODE) fString nodeName = next.getNodeName();String text = getElementText(next);

if(text != null) [if(nodeName.equals("title"}) {

lastTitle = text;)else if(nodeName.equals("price")) {

String pval = getParameterValue(request,lastTitle);

if (pval !» null &£ !pval.equals(text))setElementText(next, pval);

updatePrices(next);

private String getParameterValue(ServletRequest request,String param) {

Enumeration parameterNames = request.getParameterNames();

while(parameterNames.hasMoreElements()} {String next = (String)parameterNames.nextElement();if (next.equals(param))

return request.getParameterValues(next)[0];}return null;

)private String getElementText(Node element) {

Node child = element.getFirstChild();String text = null;

if(child != null)text = child.getNodeValue();

return text;)private void setElementText(Node element, String text) {

Node child = element.getFirstChild() ;

if[child != null)child.setNodeValue(text) ;

Метод doEndTag приведенного выше класса ищет документ в области видимостиприложения. Если документ найден, он передается в качестве параметра методуupdatePrices, который выполняет обход дерева документа. Принцип обхода былпредставлен в листинге 11.8,в.

Page 339: Java Server Pages

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

Для каждого элемента price, найденного в документе, метод updatePrices ищетпараметр запроса с именем, совпадающим с названием книги. Если параметр найден,соответствующий элемент заменяется значением параметра.

После разбора XML-файла и обновления цен JSP-документ, показанный в листинге11.8,в, перебирает дочерние узлы документа, используя для этого дескрипторi t e r a t e . Класс поддержки данного дескриптора приведен в листинге 11.9,д.

Листинг 11 -9,Д. /WHB-INF/dasses/tags/xaLl/dom/HodelteratorTag. Java

package tags.xml.dom;

import Java.util.Collection;import java.util.Vector;import j avax.servlet.jsp.JspException;import org.w3c. clom. Document;import org.w3c.dom.Node;import org.w3c.dom.NodeList;

public class NodelteratorTag extends tags.util.iteratorTag (private Node node;

public void setNode(Mode node) [this.node = node;

)public int doStartTagO throws JspException (

Node parent = node;

if(node instanceof Document)parent = ((Document)node).getDocumentElement();

setCollection(collection(parent.getChildNodes{))) ireturn super.doStartTag();

Ipublic void released f

node = null;}private Collection collection(NodeList list) {

Vector vector = new Vector();int length = list.getLength () ;

for(int i=0; i < length;vector.addElement(list.item(i));

}return vector;

Представленный выше класс поддержки является подклассом класса t a g s . u t i l .IteratorTag, который рассматривался в главе 2. Данный дескриптор осуществляетперебор набора и создает переменную сценария для текущего элемента набора.

Прочие классы поддержки, такие как NodelteratorTag, приведенный в листинге11.9,д, являются расширением IteratorTag; при этом в них переопределяется метод

Page 340: Java Server Pages

344 Глава 11. XML

cioStartTag. Перед тем как вернуть super .doStartTag, вызывается метод I terator-Tag .setCollection.

Класс NodelteratorTag перебирает узлы DOM-дерева. Эти узлы являются дочер-ними по отношению к уэлу, указанному посредством атрибута node. Если родитель-ский узел является экземпляром класса Document, в переменную parent записывает-ся значение, полученное при вызове метода Document .getDocumentElement. Этотметод возвращает корневой элемент документа.

Переменная сценария, созданная с помощью класса NodelteratorTag, определяет-ся посредством класса NodelteratorTaglnfo, код которого показан Б листинге 11.9,е.Имя переменной сценария задается с помощью атрибута id дескриптора i t e r a t e .

Листинг 11.9,е. /WEB-INF/claases/tags/janl/dam/NodelteratorTaglnfо. Java

package tags.xml.dom;

import javax.servlet.jsp.tagext.TagData;import javax.servlet.jsp.tagext.TagExtralnfo;import javax.servlet.jsp.tagext.VariableInfo;

public class NodelteratorTaglnfo extends TagExtralnfo {public VariableInfo[] getVariableInfo(TagData data) (

return new Variablelnfo[] (new Variablelnfo(data.getld() ,

"org.w3c.dom.Node",true, Variablelnfo.NESTED)

U

В JSP-документе, показанном в листинге 11.9,а, содержатся три вспомогательныхдескриптора: ifNodeNameEquals, ifNodelsElement и elementValue. Первые двадескриптора включают содержимое в зависимости от отмени узла или от того, явля-ется ли узел элементом. Дескриптор elementValue создает переменную сценариядля указанного элемента.

Класс поддержки дескриптора ifNodeNameEquals приведен в листинге П.9,ж.

ЛИСТИНГ 11.9,Ж./WEB-INF/classes/tags/xml/dom/IfNodeNameEqualsTag.Java

package tags.xml.dom;

import java.util.StringTokenizer;import java.util.NoSuchElementException;import javax.servlet.jsp.JspException;import javax.servlet.jsp.tagext.TagSupport;import org.w3c.dom.Node;

public class ifWodeNameEqualsTag extends TagSupport (private Node node = null;private String names - null;

public void setNode(Node node) ( this.node « node; }

Page 341: Java Server Pages

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

public void setNames(String names) { this.names = names; }

public int doStartTag() throws jspException (StringTokenizer tok = new StringTokenizer(names);String nextName - null, nodeName = node.getNodeName();boolean nameFound = false;try f

while(!nameFound)nameFound - nodeName.equals(tok.nextTokenU);

}catch(NoSuchElementException ex) {

// Больше лексем нетireturn nameFound ? EVAL_BODY_INCLUDE : SKIP_BODY;

)public void re lease ! ) {

names = n u l l ;node = n u l l ;

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

Класс поддержки дескриптора ifNodelsElement включает тело дескриптора, ес-ли узел представляет элемент. Код данного класса приведен в листинге 11.9,з.

ЛИСТИНГ 11.9,3. /WEB-INF/olasses/tags/ionl/dom/IfNoclelsElemaiitTag, Java

package tags.xml.dom;

import javax.servlet.jsp.JspException;import javax.servlet.jsp.tagext.TagSupport;import org.w3c.dom.Node;

public class ifNodelsElementTag extends TagSupport (private Node node = null;

public void setNode(Node node) (this.node = node;

}public int doStartTag() throws JspException {

return node.getNodeTypeO == Node.ELEMENT_NODE ?EVAL_BODY_1NCLODE : SKIP_BODY;

Ipublic void release!) {

node - null;

Page 342: Java Server Pages

346 Глава 11. XML

В листинге 11.9,и представлен класс поддержки дескриптора elementValue, ко-

торый сохраняет значение элемента в переменной сценария.

Листинг 11.9,и. /WEB-INF/classes/togs/xml/dom/GetElementValueTag. Java

package tags.xml.dom;

import javax.servlet.jsp.JspException;import javax,servlet.jsp.tagext.TagSupport;import org.w3c.dom.Element;import org.w3c.dom.Node;

public class GetElementValueTag extends TagSupport (private Node element = null;

public void setElement(Node element) {this.element = element;

}public void setldfstring id) (

this.id = id;)public int doEndTag() throws JspException {

if(element.getNodeType() =» Node.ELEMENT_NODE) fString value - getElementText(element);pageContext.setAttribute(id, value);

}else

throw new JspException("Node must be an element");

return EVAL_PAGE;)public void released I

element - null,-

)private String getElementText(Node element) {

Node child = element.getFirstChild ();String text = null;

if(child I- null)text - child.getNodeValue();

return text;

Подкласс класса TagExtralnf о для дескриптора elementValue — класс GetElement-ValueTaglnfo - показан в листинге 11.9,к.

Листинг 11.9,к. /WEB-lHF/classea/taga/jonl/dom/GetEXeaeiitValueTag. Java

package tags.xml.dom;

import j a v a x . s e r v l e t . j s p . t a g e x t . TagData,-

Page 343: Java Server Pages

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

import javax.servlet.jsp.tagext.TagExtralnfo;import javax.servlet.jsp.tagext.VariableInfo;

public class GetElementValueTaglnfo extends TagExtralnfo {public Variablelnfo[] getvariablelnfo(TagData data} f

return new Variablelnfo[] {new Variablelnfo(data.getldO ,

"Java.lang.String", true,variablelnfo.AT_END)

Имя переменной сценария, созданной с помощью класса GetElementValue-Taglnfo, определяется посредством обязательного атрибута id дескриптора e lement-Value.

Как можно видеть, сравнивая листинги 11.8,6 и 11.9,а, пользовательские дескрип-торы, предназначенные для работы с DOM, существенно упрощают работу авторовWeb-страниц, предоставляя им привычные дескрипторы и исключая из JSP-докумен-тов Java-код.

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

собность Web-приложений преобразовывать данные в различные форматы (напри-мер, XML, HTML или WML) приобретает все большее значение.

Для преобразования XML-документов в любой из требуемых форматов можно ис-пользовать XSLT {Extensible Stylesheet Language: Transformations— расширяемыйязык листов стилей: преобразования).

Основы XSLT изучить несложно; в это можно убедиться, рассматривая простойпример преобразования XML-кода, представленного в листинге П. 10,а {этот код сге-нерирован JSP-документом, код которого показан в листинге 11.2,а),

Листинг 11.10,a. date.ami

<?xml version="1.0" encoding="ISO-8859-l"?><document>

<date>Fri Dec 15 11:46:45 MST 2000

</date></document>

В листинге 11.10,6 показан лист стилей XSLT, применяемый к XML-документу,представленному в листинге 11.10,а. В результате генерируется HTML-документ, по-казанный на рис. 11.8.

Page 344: Java Server Pages

348 Глава 1 1 . XML

р ' Frocesang XMl Gwxsitod «Wi JSP • МвокЙ Intem* E «plus

| £lte Edit Jlmi forefltes look tht>

i Addretl |a hBnVyiocAosLSOTOAmWert^^deta №

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

,,|Q1 x|

о

iРис. 11.8. Использование XSLT для преобразованияXML-документа в HTML-формат

Листинг 11.10,6. /date.xsl

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"version="l.0">

<stsl:template match="/"><htmlxhead>

<title>Processing XML Generated with JSP</title></head><body><fant size='4'>

Today's Date is: Otsl:apply-templatas/> </font></body></html>

</xsl:template>

<acsl: template match="date"><xsl:apply-templates/>

</xsl:template>

</xsl:stylesheet>

XSLT — это декларативный язык, основанный на правилах-шаблонах. Каждое пра-вило-шаблон состоит из обртца (pattern) и действия (action), которые задаются в со-ставе дескриптора x s l : t e m p l a t e .

Например, влистинге 11.10,а используются два правила-шаблона:

<xsl:template match-"/">...</xsl:template><xsl:template match="date">...</xsl:template>

В данных правилах-шаблонах используются образцы "/" (корневой элемент) и" d a t e " (элемент d a t e ) . Действия задаются в теле дескриптора.

Для правила, распознающего корневой элемент ("/")> создается следующийHTML-код.

<html><head><title>Processing XML Generated with JSP</title>

</head><body><font size=M'>

Page 345: Java Server Pages

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

Today's Date is: </font></body></html>

Правило для корневого элемента не включает дату; вместо этого в нем содержитсядескриптор xs l : apply-templates, который рекурсивно применяет правила дляэлементов, дочерних по отношению к корневому. В данном случае правилу "date" со-ответствует элемент date.

В состав правила "date" входит дескриптор x s l : apply-templates. Если элементне содержит дочерних элементов, x s l : apply-templates включает тело элемента всостав выходных данных. В нашем примере генерируется дата "Fri Dec 15 11:46:45MST2000".

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

Совместное использование XSLTnJSPНа практике JSP и XSLT часто используются совместно. Два способа такого при-

менения условно показаны на рис. 11.9.Как показано на рис. 11.9, XSLT-преобразования XML-кода можно выполнять в

процессе выполнения программы, применяя для этого пользовательский дескриптор.Кроме того, вы можете применять XSLT к XML на этапе компиляции при генерацииJSP-документа. Рассмотрим подробнее каждый из этих подходов.

/Выполнен

\

XML * - XSLT

ДескрипторJSP — * ???

XML XSLT

Командная строка

Компиляция Выполнение

JSP JSP

Рис. 11.9. Два способа преобразования XML-данныхс использованием JSP и XSLT

Генерация HTML-кода путем XSLT-преобразованияс помощью пользовательского дескриптора

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

<xslt:apply xsl='inventory.xsl' ><%@ include file='inventory,xml' %>

</xslt:apply>

Page 346: Java Server Pages

350 Глава 11. XML

В приведенном выше фрагменте кода лист стилей, содержащийся в файле с име-нем i n v e n t o r y . xs l , используется для преобразования XML-кода, который находит-ся в файле i n v e n t o r y .xml. При необходимости XML-данные можно помещать непо-средственно в тело дескриптора; соответствующий пример приведен ниже.

<xslt:apply xsl='inventory.xsl'><?xml version='1.0' encoding^'ISO-8859-1'?>

</xslt:apply>

На рис. 11.10 показано Web-приложение, в котором дескриптор x s l t : a p p l y ис-пользуется для выполнения XSLT-преобразования XML-файла. Содержимое XML-файла представлено в листинге 11.11,а.

в ]DDM Emmpte • МсяпоК Msngt Еч*тс

lilt Edit B«w Fj/or/kj loot ф^

Inventory as of Sat Dec 16 16:24:16

Item

Лш LjU'j broach

Gumhy and Pokey

Underwood lypewnler

LawnnKMnt

Superman comic book

Escher original drawing

Roman helmet

School desk

У Done

Description

from Ihe early 1700's

pliable atlion figures

19Л2. excellent shape

1

MST 2000

Price

123Э.99

:.M

299 95

On? of Ihe firs! gas-engine mowers 499.39

Series 1A7, quite rare

signed in blue ink

estimated 200 В С

made in1949. with inkwell

2 99

3459.95

ЭЕ.ЭЭ

995.99

! Local intranet

itsl

9Go_|

Рис. 11.10. Применение пользовательского дескрипторадля выполнения XSLT-преобразоаания

Листинг 11.11,a. /inventory.itml

<?xml version»"1.0" encoding="iso-a859-l"?><inventory>

<item><name>Antique broach</name><description>from the early 1700's</description<price>1238.99</price>

</item><item>

<name>Gumby and Pokey</name><description>pliable action figures</description><price>2.99</price>

Page 347: Java Server Pages

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

</item>

. . . Остальные пункты не указаны ....

</inventory>

Код^Р-документа, показанного на рис. 11.10, представлен в листинге 11.11,6.

Листинг 11.11,6. /test xslt.jsp

<html><head><title>XSLT Example*:/1itle><%@ tagl ib uri='/WEB-INF/tlds/xslt.tld' pref ix= f xs l t '

</head><body>

<font size=' 5'>Inventory as of <%= new java.util.Date() %>

</font>

<xsIt:apply xsl='inventory.xsl '><%6 include file='inventory.xml' %>

</xslt:apply>

</bodyx/html>

JSP-документ, показанный в листинге 11.11,6, применяет лист стилей inventory , x s lк данным в файле inventory, xml, используя для этого дескриптор xs I t : apply. Содер-жимое файла inventory, xs l приведено в листинге 11.11,в.

Листинг 11.11,в. /inventory.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"version="1.0">

:template match="/"><html>

<head><title>lnventory</title>

</head><body>

<table cellspacing^'15'><th><u>Item</u></th><th><u>Bescription</u></th><thxu>Price</u></th><xsl:apply-templates/>

</table></body>

</html>:template>

•cxsl:template match="item">

Page 348: Java Server Pages

352 Глава 11. XML

<trxxsl:apply-templates/x/tr></xsl:template><xsl;template match="item/*">

<tdxxsl: apply-templates/x/td></xsl:template>

: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;

Jpublic 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;

Page 349: Java Server Pages

Преобразование 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-процессоров, метод process получает XSLT-процессор, использует его длявыполнения XSLT-n ре образования и записывает результаты в поток JspWri ts r .

Page 350: Java Server Pages

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-файла

<xsl:stylesheet xmlns:xsl="http://www,w3.org/1999/XSL/Transform"version="1.0">

<xsl:template match="/"><jsp:root xmlns : jsp="http: //Java. sun.com/jsp_l_2">

<head><title>Inventory</title>

</head><body>

<font size='4'>Inventory as of <jsp:expression>

new java.util.Date()</jsp:expression>

</£ont><table cellspacing='15'>

<thxu>ltem</ux/th><th><u>Desctiption</ux/th><th><u>Price</ux/th><xsl:apply-templates/>

</table></body>

</jsp:root></xsl :terciplate>

:template match="item"><tr><xsl:apply-templates/x/tr>

</xsl:template>

<xsl:template fflatch="itero/*"><tdxxsl:apply-templates/x/td>

</xgl:template></xsl:stylesheet>

Файл inventory, xsl, представленный в листинге 11.12,а, задается в команднойстроке следующим образом;

> java org.apache.xalan.xslt.Process -in inventory.xml -xslinventory.xsl -out inventory.jsp

Page 351: Java Server Pages

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

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

Листинг 11.12,6. JSP-документ, полученный в результатеJSP-преобразования

<?xml version="l. О" encoding="UTF-8"?><jsp:root xmlns:jsp="http://java.sun.com/jsp_l_2"xhead><title>Inventory</title></headxbody><font size="4">Inventory as of

<jsp:expression>new java.util.Date()

:expression>

</font><table cellspacing="15"><thxu>item</ux/th><thxu>Description</uX/th><thxu>Price</ux/th>

<tr><td>Antique broach</td><td>from the early 17OO's</td><td>1238.99</td>

</tr><tr>

<td>Gumby and Pokey</td><td>pliable action figures</td><td>2.99</td>

</tr>

... Остальные пункты пропущены ...

</tablex/body></jsp:root>

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

Page 352: Java Server Pages

356 Глава 11. XML

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

Два подхода к выполнению XSLT-преобразованияВыше рассматривалось выполнение XSLT-преобразования на этапе компиляции и

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

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

Использование XPathXPatli — это язык, используемый в процессе XSLT-преобразования для проверки

соответствия XML-элементов образцам. Ранее в данной главе встречалось правило-шаблон, в котором образец " / " обозначал корневой элемент:

<xsl:template match="/">...</xsl:template>

Приведенное выше правило применяется к элементу, который соответствуетXPath-выражению "/", т.е. к корневому элементу. Данное XPath-выражение чрезвы-чайно простое, однако другие выражения могут быть достаточно сложными.

Средства XPath удобно применять для поиска XML-элементов, но их использова-ние снижает производительность при XSLT-преобразовании,

XPath — независимый язык, поэтому большинство XSLT-процессоров позволяетиспользовать XPath без XSLT. В данном разделе вы узнаете об использовании XPathпри работе с XSLT-процессором Apache Xalan.

JSP-документ, показанный на рис. 11.11, содержит пользовательский дескриптор,предназначенный для выбора элементов из XML-файла. Этот дескриптор используеткомпонент bean, который, в свою очередь, применяет XPath для поиска элементов вXML-файле.

Page 353: Java Server Pages

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

File Edit XKW FjjvorjiB Took Jjc|)

Conference Attendees

Cslesline. HoraceGraves. SamuelLopez.JoseMartin. RoyRoyat, StanleyWoodard, Daniel

2] Oa» ' I • ;']Sla«IHMnir

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

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

Листинг 11.13,a. test_xpath.jsp

<htmlxheadxtitle>XPath Example</title><%S taglib uri='xpath' prefix='xpath' l><%@ page import=f org. w3c.dorrL.Node' %>

</head><body>

<font size=' 4' >Conference Attendees</fontxp>

<xpath:selectNodes id='node'

xmlFile='f:/books/jsp/sre/xml/names.xml'

expr='//name'>

<% Node f irst = node.getAttributes().getNamedltemf"first");Node iast = noda.getAttributesО.getNamedltemC'last"); %>

<lix%=last. getNodeValue () %>, <%=first. getNodeValue () %x/li>

</xpath:selectNodes></p>

</bodyx/html>

Пользовательский дескриптор selectNodes, содержащийся в листинге 11.13,а,перебирает узлы указанного XML-файла. Выбор )^ia осуществляется в соответствии сXPath-выражением. Кроме того, пользовательский дескриптор обеспечивает доступ кпеременной сценария, которая представляет текущий узел. Как видно из листингаИ.13,а, переменная сценария имеет имя node.

Page 354: Java Server Pages

358 Глава 11. XML

XML-файл names.xml, обрабатываемый JSP-документом, приведен в листинге 11.13,6.

Листинг 11.13,6. names,xml

<?xml version="1.0"?><doc>

<name first="Horace" last="Celestine"/><name first="Samuel" last="Graves'7><name first="Jose" last="Lopez"/><name first="Roy" last="Martin"/><name first="Stanley" last="Royal'7><name first="Daniel" last="Woodard"/>

</doc>

В листинге 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);

Page 355: Java Server Pages

Использование 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 < length;vector.addElement(list.item(i));

}return vector;

Подобно классу поддержки, приведенному в листинге 11.9,д, представленный вышекласс является подклассом I t e r a t o r T a g (класс I t e r a t o r T a g был рассмотрен в главе 2).

Метод XPathTag .doStar tTag использует компонент bean XPathBean, которыйинкапсулирует вызовы XPath. Этот компонент обрабатывает XPath-выражение и воз-вращает объект NodeList, содержащий список узлов, которые соответствуют выра-жению. Метод XPathTag. d o S t a r t T a g преобразует список в набор, который переда-ется методу I t e r a t o r T a g . s e t C o l l e c t i o n . Затем XPathTag . d o S t a r t T a g вызываетметод super .doStar tTag, в результате чего вступает в действие класс I t e r a t o r T a g ,который перебирает набор, установленный посредством вызова s e t C o l l e c t i o n .

Код класса XPathBean показан в листинге 11.13,г.

Листинг 11.13,г. /WEB-INF/elasses/beans/Kml/acpatli/XFathBaan. Java

package beans,xml.xpath;

import Java.io.InputStream;import Java.io.FileInputStream;import Java,io.FileNotFoundException;import javax.servlet.jsp.JspException;

import org,apache.xerces.parsers.DOMParser;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import org.xml.sax.InputSource;import org.xml.sax.SAXException;

import org.apache.xalan.xpath.XPathSupport;import org.apache.xalan.xpath,XPath;

Page 356: Java Server Pages

360 Глава 11. XML

import org.apache.xalan.xpath.XPathFrocessorlmpl;import org.apache.xalan.xpath.xml.XMLParserLiaisonDefault;import org.apache.xalan.xpath.xml.PrefixResolverDefault;import org.apache.xalan.xpath.XObject;

public class XPathBean (private XPathBean() ( } // Создать экземпляр класса невозможно

public static NodeList process(Node node, String expr)throws SAXException {

XPathSupport s = new XMLParserLiaisonDefault();PrefixResolverDefault pr - new PrefixResolverDefault(node);XPathProcessorlmpl processor = new XPathProcessorlmpl(s);XPath xpath = new XPath();processor.initXPath[xpath, expr, pr);XObject xo = xpath.execute(s, node, pr);return xo.nodeset();

Подобно классам SAXParserBean, DOMParserBean и XSLT P r o c e s s o r Bean, кото-рые были рассмотрены ранее в этой главе, класс XPathBean инкапсулирует код, ори-ентированный на конкретный процессор. Этот код, сосредоточенный в статическомметоде XPathBean.process, обрабатывает XPath-выражение. сравнивая его с узломDOM (предполагается, что этот узел является корневым узлом документа или егофрагмента). Этот метод возвращает объект NodeList, содержащий все узлы, соот-ветствующие XPath-выражению.

Как и DOMParserBean, класс XPathBean реализует единственный статическийметод. В связи с этим, чтобы предотвратить попытки создания экземпляра класса,конструктор XPathBean объявлен как p r i v a t e .

РезюмеВ этой главе рассказано о некоторых способах совместного использования JSP и

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

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

Для разбора XML-данных используются два стандартных API: SAX и DOM. Этисредства рассматривались на протяжении данной главы; здесь также обсуждалисьпользовательские дескрипторы для выполнения SAX- и DOM-разбора.

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

Page 357: Java Server Pages

ПРИЛОЖЕНИЕНА БАЗЕ JSP

В этой главе...

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

- Исходная страница.- "Витрина".- Проверка выбранных товаров.

- Приобретение товара.

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

- Модель.- Просмотры: JSP-документы и шаблоны.

- Контроллеры: сервлеты и действия.

Интернационализация.

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

HTML-формы.

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

SSL.

ХМ1_и00М.

Page 358: Java Server Pages

Читая данную книгу, вы познакомились с самыми различными технологиями,предназначенными для создания Web-приложений: пользовательскими деск-рипторами архитектуры Model 2, средствами интернационализации и аутен-

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

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

• Приложение соответствует архитектуре MVC Model 2.

• Приложение поддерживает три языка: английский, немецкий и китайский.

• Приложение осуществляет аутентификацию.

• В приложении используются|5Р-шаблоны.

• Приложение работает с базой данных,

• В приложении используются средства XML и DOM.

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

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

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

Page 359: Java Server Pages

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

Описанное в данной главе приложение содержит большой объем кода, поэтому мыбудем рассматривать его в три этапа.

• Основной сценарий развития событий: исходная страница—> "витрина"—>выбор продукта—> покупка.

• Архитектура 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.

Page 360: Java Server Pages

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

•J—h - , — .

шва°"—• i

Welcome to FmitStand.oom

Л MMil 2 JSP Л и й Ш К *

T-I т.

:--.

И ^ :, i

ш •

«•Mb

Й Г

It |HI*I

« M M

' - —

Ш

-Weteome to FruitStand.cam

•an

FruilStancl.com

^.««r i ,• ..•• •.. h - B D I 4 вв 2 Л r J 0 '

tfit-

" " " ^

рп-ч i'. JJ

link F"3

F B I

FrurtSlane.com

IBM РП UP

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

Page 361: Java Server Pages

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

ЁНь

Б

case_studv -'

; graphics:

: О flags£j fruit

£j Web-inf ' 'B O classes

Й- О action:--Ц] actions

fti i_J beans'

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

+

Н - Ш З tags ^ , ^ ' _| s p '

util

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

Защищеноотнепосредственногодостуга(данноеправило поддерживается не всеми контейнерами)

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

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

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, причем для каждого документа выделяется отдельный подкаталог.Например, средства, реализующие исходную страницу, содержатся в каталоге /WEB-INF/ jsp/homepage.

Приложение было проверено при работе с серверами Resin 1.2.1 и Tomcat 3.2 final.Организовать выполнение приложения можно одним из двух способов. Самый простойспособ— создать JAR-файл приложения и разместить его в каталоге ?ТОМСАТ_НОМЕ/webapps (для сервера Tomcat) или $RESIN_HOME/webapps (для сервера Resin). Здесь$ТОМСАТ_НОМЕ и $RESIN_HOME — каталоги, в которых были инсталлированы соответ-ственно контейнеры Tomcat и Resin. Если же вы собираетесь модифицировать прило-

Page 362: Java Server Pages

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

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

<Context path="/case-study"docBase="f:/books/jsp/src/case_study"/>

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

<web-app id='case-study'app-dir=' f:/books/jsp/src/case_study/final'/>

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

Исходная страницаНабор файлов, предназначенных для формирования приветственного сообщения,

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

<?xml version="l.0" encoding="ISO-8859-l"?>

<!DOCTYPE web-appPUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN""http:Z/java.sun.com/j2ee/dtds/web-app_2.2.dtd">

<welcome-fi<welcome-file>index.jsp</welcome-

</welcome-fi

</web-app>

При работе с Tomcat или Resin файл /index, j sp вызывается при обращении кURL h t t p : / / l o c a l h o s t : 8080/case-study (для других контейнеров номер портаможет отличаться). Содержимое файла /index, j sp представлено в листинге 12.1,а.

Листинг 12.1,a. /index.jsp

page contentType='text/html; charset=UTF-8' %><%@ taglib uri='regions' prefix='region' %><%@ include file='/WEB-lNF/jsp/regionDefinitions.jsp' %>

<region:render region='HOMEPAGE_REGIOK'/>

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

В файле / i n d e x . j s p применяется библиотека пользовательских дескрипторов,предназначенная для поддержки областей. Эта библиотека рассматривалась в главе 4.

Page 363: Java Server Pages

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

Определение областей содержится в файле /WEB-INF/jsp/regionDefinitions.jsp, который включается в состав /index* jsp. Внешний вид исходной Web-стра-ницы показан на рис, 12.3*

tltw F£/orlt« ТмЬ цф

Welcome to FruitStand.com

— — _ _ sA Model 2 JSP ApplicationТЫ? mockup of я foil stand use? many of Ihe techniques discussed in Advanced JarvsEarver Pages,published by Sun Microsystems Press and Frenike-Hall in. May 2001. That book dia?u&se$ a- simple-Model 2 Web application framework Ihefs Ihe basis for Ihis application. The purpose of this aplicallon islo illustrate using Ihe bunk's techniques lo develop э noniriwal wsb application

This, web anpiicaiion is iniernanonaliTed in three languages. English, Gem-tan, and Chinese. By default.this application utes The browser's language preferences te decide which language to use, but you canalso ejplicitly se" the language by clicking on one otiha flags in the sidebar Try n now, if you like.Note, vow browser mvsi support Chinese farts to see the Chinese iranstst&rta. S« yow browser'swйlt^sffe for mow information oonnemnq rfai support.

JSP templates are used extensively LhrdughQui tn-G web applicaimn ED consiruct web pages. Every webpage m th г арplieaiшп was creaied with в JSP template, which lets those радея share components;for tjxafia с. the text "Welcome tc Fruiiztand с о т " and (he honzania] rule underneath ii are creaied bya JSP file thai n reused by every wab page m Ihis application JSP template's, encapsulate page layoutand encourage the uee of reusable components.

This wab application uses programmatic authenticatjorv-meaning authintfcation implemanled fromscratch-to ensure poiabiiily. The auiheniicaiian farces usere to login aefora ttiay can check uut theirpurchaae. Va user doesni have an account. Ihey are given Ihe opportunnу (о create one andsubsequently login. Programmatic aulheniicalien, as Oflpcsed to BASIC or DIGEST atfhenlicalicn.gives applications complete control over authentication because it takes the s-e-rvie] container uu| of theauthentication loop.

Many oiher techniques from Advanced JavsServer Pages are used m Ihis WBD application; for example,з^огт framework is usefl ID handle HTML forms and custom tage are used lo delect Bens Hive formresubimssinns. This qppliciiiun also uses- rrary of the book's other JSP custom tags

To g« atlrtad. click l h i Go Shopping bulion in the sidebar

* Thanks for flapping by Todiy it Wtttnext*/, FeBtv*r/2i. 2001.

ej Otr»

Рис, 12.3. Исходная страница приложения

Как и другие JSP-документы, входящие в состав приложения, исходная страницасостоит из одной области, включающей четыре раздела: заголовок, полосу в левойчасти страницы, содержимое и нижний колонтитул. В левой части страницы разме-щены изображения трех флагов, которые используются для выбора языка, а такжекнопка Go Shopping, В разделе заголовка выводится строка "Welcome to Fru i t -stand.com" и горизонтальная линия. Основной текст размещен в разделе содержи-мого, а в нижнем колонтитуле отображается строка, включающая дату.

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

Page 364: Java Server Pages

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

пользование шаблонов делает возможным замену компонентов. Преимущества при-менения шаблонов были подробно рассмотрены в главе 4.

Файл regionDef i n i t i o n s . j sp определяет области, используемые в составе при-ложения. Частично содержимое этого файла приведено в листинге 12.1,6 (полностьюфайл regionDef i n i t i o n s . j sp будет представлен далее в этой главе).

Листинг 12.1,6. /WEB-IMF/jsp/regionDefinitions . jsp (фрагмент)

<%@ taglib uri='regions' prefix='region' %>

<region:define id='STOREFRONT_REGION'template-'/WEB-INF/jsp/templates/hscf.jsp'>

<region:put sec t ion=' t i t l e 'content^'FruitStand.com'

direct='true'/>

<region:put section='background'content='graphics/blueAndWhiteBackground.gif'

direct^'true'/>

<region:put section='header'content^'/WEB-INF/jsp/storefront/header.jsp'/>

<region:put section-'sidebar'content=' /WEB-INF/jsp/storefront/sidebar.jsp'/>

<region:put section='content'content='/WEB-INF/jsp/storefront/content.jsp'/>

<region:put section='footer'content»'/WEB-iNF/jsp/storefront/footer.jsp'/>

</region:define>

<region:define id='HOMEPAGE_REGION' region='STOREFRONT_REGION'><region:put section='sidebar'

content='/WEB-INF/jsp/homepage/sidebar.jsp'/>

<region:put section='content'contents/WEB-INF/j зр/homepage/content.j sp'/>

</region:define>

В файле regionDef i n i t i o n s . j sp определена область STOREFRONT_REGION, пред-назначенная для реализации "витрины" приложения. Область HOMEPAGE_REGION рас-ширяет STOREFRONT_REGION и переопределяет разделы sidebar и content. Разделыt i t l e , background, header и footer используются без изменений.

JSP-файлы, соответствующие разделам header и footer, приведены соответст-венно в листингах 12.1,в и 12.1,г.

Page 365: Java Server Pages

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

Листинг 12.1,В. /WEB-INF/jsp/storefront/header. jap

<%9 page contentType=' text/html,- charset=UTF-8' %><%@ taglib uri='il8n' prefix='il8n' %>

<font size='6' color='blue'><il8n:message key="storefront. t i t le"/>

</font><hr/><br/>

Листинг 12.1,г. /WEB-INF/jsp/storefront/footer. jsp

<%@ page contentType='text/html; charset=UTF-8' %><%S taglib uri='il8n

f prefix='il8n

f %>

<hrxp>

<table><tr>

<tdximg src-' graphics/duke, gif' /></td><td>

<il8n:message key='login.footer.message'/><i><il8n:format date='<%=new Java.util.Date ()%>'

dateStyle='<%=java.text.DateFormat.FULL%>'</td>

</tr></table>

Текст, отображаемый в приложении, воспроизводится посредством дескрипторовil8n:message и i l8n : format, которые обеспечивают интернационализацию прило-жения. Интернационализация предполагает изменение текста, а также формата чисел,даты и обозначения денежных единиц. Дескрипторы il8n:message и i l8n : formatиспользуются для отображения заголовка и нижнего колонтитула "витрины".

Дескриптор il8n:message выводит текст, заданный в файле свойств. Например,ниже приведена часть файла /WEB-INF/classes/app_en.properties, используе-мого для поддержки английского языка,

storefront.title=Welcome to FruitStand.com

storefront.form.title=Please select from our fresh fruts.

storefront.table.header.picture=Picturestorefront.table.header.item=Itemstorefront.table.header.description=Descriptionstorefront.table.header.price=Pricestorefront.table.header.addToCart=Add To Cart

В состав приложения также входят файлы свойств для немецкого и китайскогоязыков; подробно вопросы поддержки языков будут рассмотрены далее в этой главе.

Page 366: Java Server Pages

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

JSP-файл, соответствующий содержимому исходной страницы, приведен в листин-ге 12.1, д.

Листинг 12.1,д. /W£B-INF/jep/homepa.ge/content.jsp

<%@ page contentType='text/html; charset=UTF-8' %>taglib uri='il8n' prefix='il8n' %>

<font size='5'<il8n:message key='homepage.title'/>

<7font>

<11Эп:message key='homepage.text'/>

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

Выбор товаровИсходная страница позволяет пользователю предпринять одно из двух действий:

задать язык взаимодействия, щелкнув на одном из флагов, или перейти к выбору то-варов, для чего надо активизировать кнопку Go Shopping. Как флаги, так и кнопкарасположены в левой части Wcb-страницы. Код, предназначенный для воспроизве-дения флагов и кнопки, представлен в листинге 12.2,а.

Листинг 12.2,a. /WEB-IHF/jap/homepage/sidebar. jsp

<%@ page contentType='text/html; charset=UTF-8' %><jsp:include page^'../shared/flags.jsp' flush='true'/>

<form action='go-shopping-action.do'><input type='submit' value='Go Shopping'/>

</form>

Файл sidebar, jap включает другой JSP-файл, посредством которого отобража-ются флаги. Этот файл (/WEB-INF/jsp/shared/f lags, jsp) используется всеми JSP-документами, входящими в состав данного приложения; при этом пользователь в лю-бой момент времени может выбрать подходящий для него язык. Подробно файлflags . j sp будет обсуждаться далее в этой главе при рассмотрении вопросов, связан-ных с поддержкой языков.

Полоса в левой части страницы также содержит простую форму с кнопкой. Атри-бут action формы имеет значение go-shopping-action . do. URI, оканчивающиесясимволами ".do", обрабатываются сервлетом действий данного приложения. Этотсервлет, входящий в состав базового набора классов Model 2, перенаправляет запросgo-shopping-action. do действию, представленному в листинге 12.2,6,

Page 367: Java Server Pages

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

Листинг 12.2,6. /WEB-INF/cla3ses/actions/GoShoppingAction . Java

package actions;

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

import beans.app.ShoppingCart;

import action.ActionBase;import action.ActionRouter;

import beans.app.Item;

public class GoShoppingAction extends ActionBaseimplements beans.app.Constants i

public ActionRouter perform[HttpServlet servlet,HttpServletRequest req,HttpServletResponse res)throws ServletException {

HttpSession session = req.getSession();ShoppingCart cart = (ShoppingCart)session,getAttribute(

SHOPPING_CART_KEY)if(cart == null) (

cart - new ShoppingCartО ;

synchronized(this) {session,setAttribute(SHOPPING_CART_KEY, cart);

))return new ActionRouter("storefront-page");

Сервлет действий вызывает метод GoShoppingAction . perform, проверяет, соз-дана ли для данного сеанса "корзинка" покупателя (если "корзинка" отсутствует, соз-дается новый экземпляр класса ShoppingCart), и возвращает маршрутизатор дейст-вий, который сервлет действия использует для перенаправления запроса документуstorefront-page. Соответствие имени реальному документу задано в файлеact ions, proper t i e s приложения, представленном в листинге 12.2,в.

Листинг 12.2,в. /WEB-lNF/ciasses/actions. properties

# Отображение имен в действия, используемое сервлетом действий

go-shopping-action =actions.GoShoppingActionquery-account-action -actions.QueryAccountActionnew-account-action =actions.NewAccountActionshow-hint-action =actions.ShowHintActionupdate-locale-action =actions.UpdateLocaleAction

Page 368: Java Server Pages

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

add-selection-to-cart-action=actions.AddToCartActioncheckout-action =actions.CheckoutActionvalidate-account-action =actions.ValidateAccount Actionpurchase-action ^actions.PurchaseAction

# Отображение имен в JSP-документы, используемое маршрутизаторами

storefront-page =/WEB-INF/jsp/storefront/page.jsplogin-failed-page =/WEB-INF/jsp/loginFailed/page.jspquery-account-page =/WEB-INF/jsp/createAccount/page.jspaccount-created-page=/WEB-INF/jsp/accountCreated/page.jspshow-hint-page =/WEB-INF/jsp/showHint/page.jspcheckout-page =/WEB-lNF/jsp/checkout/page.jsppurchase-page =/WEB-INF/jsp/purchase/page.jsp

В файле actions .proper t ies определены два набора логических имен. Именапервого набора отображаются в действия; например имя go-shopping-action, doсоответствует действию act ions . GoShoppingAction, код которого приведен в лис-тинге 12.2,в. Имена второго набора отображаются в документы; например, в этом на-боре присутствует имя storefront-page, используемое действием go-shopping-action.do.

"Витрина"Рассмотрим кратко, как пользователь получает доступ к "витрине" интерактивного

магазина. Сначала он обращается к исходной странице, задавая URL h t t p : / / l o c a l -host: 8080/case_study. В результате такого обращения отображается приветст-венное сообщение, за вывод которого отвечает документ / index. j sp. Затем пользо-ватель активизирует кнопку Go Shopping, в результате чего формируется запрос к ре-сурсу go-shopping-action.do. Управление передается действию GoShopping-Action, которое создает "корзинку" покупателя и перенаправляет запр *с документу,отображающему "витрину" магазина. Внешний вид "витрины" показан на рис. 12.4.

Код JSP-документа, ответственного за отображение "витрины", представлен в лис-тинге 12.3,а.

Листинг 12.3,a. /WEB-INF/jsp/storefront/page.jsp

<%Э taglib uri='regions' prefix^'region' %>

<region:render region-'STOREFRONT_REGION'/>

Данный JSP-документ воспроизводит область STQREFRONT_REGION, которая опре-делена в файле /WEB-INF/jsp/regionDefinitions.jsp. Частично содержимоеэтого файла представлено в листинге 12.1,6.

Page 369: Java Server Pages

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

(

flit

Welcome to FruitStand.com

Please select from our fresh fruts and vegetables.

Plctun i Ham

» »appla

Description

Crunctiy and sweet

banana there are more than ]QQQ kinds of bananas

S 0 39 ЛЬ.

ю.еэль.

( О 19 ЛЬ.

: grapefruit • bit with sugar

kiwi

peach

: grapes i purple grapes are b&sl

Roys (агат в

come ( ю т a can

(СИЭЛЬ.

JO 79ЛЬ.

i П.Э9 ЛЬ.

$ D.39 /It.

Рис. /2.4. "Витрина" интерактивного магазина

Область STORE FRONT_REGION включает четыре файла: header , j sp , s i d e b a r , jsp,c o n t e n t . j sp и f o o t e r . j sp. Как вы уже знаете, заголовок (header . j sp) и нижний ко-лонтитул {footer. j sp) используются также исходной страницей, и коды этих файловбыли представлены ранее в листингах 12.1,в и 12.1,г. В листинге 12.3,6 приведенфайла, ответственного за отображение полосы в левой части Web-страницы.

Листинг 12.3,6. /WEB-INF/jsp/storefront/sidebajr. j s p

<%@ page contentType=' text/html; charset=rjTF-8' %>

<jsp:include page='../shared/flags.jsp' flush='true'/><p><jsp:include page='../shared/cart.jsp' flush='true'/></p>

Page 370: Java Server Pages

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

Подобно области HOMEPAGE_REGION, полоса в левой части области STOREFRONTREGION включает файл f l a g s . j s p , поэтому пользователь имеет возможность вы-брать требуемый язык. Кроме того, полоса в области STOREFRONT_REGION включаетеще один разделяемый компонент, посредством которого отображается текущее со-держимое "корзинки" покупателя. Этот компонент реализуется посредством файла/WEB-INF/j sp/ s h a r e d / c a r t , jsp, содержимое которого будет представлено далее влистинге 12.4,6.

Отображение основных сведений в области STOREFRONT_REGION реализуется спомощью файла /WEB-INF/ j s p / s t o r e f r o n t / c o n t e n t . jsp, который приведен влистинге 12.3,в.

Листинг 12,3,в. /WEB-XSF/jsp/storefront/content. j sp

<%@ page contentType='text/html; charset=UTF-8' !>

<%@ t a g l i b ur i= 'database ' pref ix='database ' %><%@ t a g l i b u r i = ' h t m l ' pref ix='html ' %><$@ t a g l i b u r i - ' i l 8 n ' prefix^'il8n' %><%@ tagl ib ur i= ' log ic ' pref ix^ ' logic ' %>

<font s ize='4 ' color='blue'><il8n:message key='storefront.form.title'f>

</font><p>

<database:query id='inventory' scopes'session'>SELECT * FROM Inventory

</database:query>

<% String currentltem = null, currentSku = null; %>

<table border='l' cellpadding='5'><tr><th><il8n:message key='storefront.table.header.picture'/>

</th>

<database:colmnnNames query^'inventory' id='name'><logic:stringsNotEqual compare^'SKU' to='<%= name %>'>

<% String hdrKey - "storefront.table.header." +name.toLowerCase[); %>

<thxil8n:message key-'<%= hdrKey %>' /></th>

<logic:stringsEqual compare='NAME' to=r<%= name %>'>

<thxil an: messagekey='storefront.table.header.description'/>

</th></logic:stringsEqual>

</logic:stringsNotEqual></database:

<thxi l8n:message key=' s tore f ront . table.header.addToCart ' /></th>

</tr>

Page 371: Java Server Pages

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

<tr><database:rows query='inventory'>

<database:columns query='inventory' columnKame='name'columnvalue='value'>

<logic:stringsEqual compare='SKU' to='<%= name %>'><% currentsku = value; l><tdximg src='<%= "graphics/fruit/" + currentSku +

"</logic:stringsEqual>

<logic:stringsEqual compare='NAME' to='<%= name %>'><% cucrentltem • value; %><td><%= value %></td><tdxil8n:message key=' <%=value + ".description"%>' /></td>

</logic:stringsEqual>

<logic:stringsEqual compare='PRICE' to='<%= name %>'><td><%= value %></td><td>

<form action-<html:link:

<option•coption<option<option<option<option<option<option<option<option<option

add-selection-to-cart-action.do >s name='

valuer'value='valuer'value='value='value='value='value-'value='valuer'value='

</html:links></form>

</td></trxtr>

</logic:stringsEqual>

</database:columns></database:rows>

</table>

<%=

0.001.001.502.002.503.003.504.004.505.005.50

<database:release query='inventory'/>

currentSku + "-'currentltem + "-'value %>'>'>0.00</option>'>1.00</option>'>1.50</option>'>2.00</option>'>2.50</option>'>3.00</option>'>3.50</option>'>4.00</option>'>4.50</option>'>5.00</option>'>5.50</option>

1 +

1 +

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

В файле c o n t e n t . j sp содержится таю^е пользовательский дескриптор html : l i n k s ,который рассматривался в главе 2, С помощью этого дескриптора оформляются пунктыраскрывающегося списка; при активизации пункта списка генерируется запрос, который

Page 372: Java Server Pages

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

передается ресурсу add-selection-to-cart-action.do. Логическое имя add-selec-t ion-to-cart-action.do отображается в класс actions.AddToCartAction; это ото-бражение задается в файле свойств actions.properties, содержимое которого былопоказано в листинге 12.2,в. Класс AddToCartAction будет подробно рассмотрен ниже.

"Корзинка" покупателяПри активизации одного из пунктов, показанных на рис. 12.4,а, генерируется за-

прос, который направляется действию actions.AddToCartAction. Код классаAddToCartAction показан в листинге 12.4,а.

ЛИСТИНГ 12.4,a. /VreB-INF/classes/AddToCartAction.Java

package actions;

import Java.util.Enumeration;import java.util.Iterator;import java.util.StringTokenizer;

import javax.servlet.*;import javax.servlet.http.*;

import beans.app.Item;import beans.app.ShoppingCart;

import action.ActionBase;import action.ActionRouter;

// В состав запроса входит параметр, представленный в формате:// код-наименование-цена=количество; например,II 1002-banana-0.69=0.75.

public class AddToCartAction extends ActionBaseimplements beans.app.Constants {

public ActionRouter perform(HttpServlet servlet,HttpServletRequest req,HttpServletResponse res)throws ServletException {

Enumeration e • req.getParameterNames();String skuAndFruit « (String)e.nextElement();String amount = req.getParameterValues(skuAndFruit)[0],ShoppingCart cart = (ShoppingCart)req.getSession().

getAttribute(SHOPPING_CART_KEY);

if(cart — null) {

throw new ServletException("No cart found.");

>

StringTokenizer tok = new StringTokenizer[skuAndFruit, " - " ) ,String sku = (String)tok.nextElement[),

fruit = (String)tok.nextElement{),price » (String)tok.nextElement0;

Page 373: Java Server Pages

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

Iterator it = cart.getItems().iterator();boolean fruitWasInCart = false;

while(it.hasNext t)) (Item item = (Item)it.next();

if(item.getName().equals(fruit)} {fruitWasInCart = true;item.setAmount(item.getAmount(} +

Float.parseFloat(amount))

if (!fruitWasInCart) {cart.acidltern(new Item(Integer.parselnt(sku), fruit,

Float.parseFloat(price),Float.parseFloat(amount))) ;

1return new ActionRouter("storefront-page");

При вызове действия, приведенного выше, ему передается единственный пара-метр в формате код_товара-11аименобание-цена=количество. Например, если пользова-тель выбрал два фунта грейпфрутов по цене $0,49, то параметр запроса будет иметьвид 1004-grapefruit-0.49=2.0. Действие AddToCartAction выполняет разборпараметра и использует полученную информацию для изменения содержимого"корзинки" покупателя.

Метод perform рассматриваемого действия возвращает маршрутизатор действий,который указывает на Web-страницу "витрины". В результате "витрина" обновляется,и содержимое "корзинки" отображается в полосе а левой части страЕшцы. JSP-доку-мент, соответствующий "корзинке" покупателя, представлен в листинге 12.4,6.

ЛИСТИНГ 12.4,6. /WEB-INF/jsp/sharea/cart. jsp

<%@ taglib uri='application' prefix='app' %>

<img src='graphics/cart.gif/>

<table cellpadding='3'><app:iterateCart id='cartItem'>

<tr><td><%- cartltem.getName() %></td><td><%= cartltem.getAmount() %></td>

</tr></app:iterateCart>

</table>

<form action^'checkout-action.do'><input type=' submit'value='checkout'/>

</form>

Page 374: Java Server Pages

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

JSP-документ, код которого показан в листинге 12.4,6, применяет для перебора со-держимого "корзинки" пользовательский дескриптор, специфический для данногоприложения. Класс поддержки этого дескриптора показан в листинге 12.4,в.

Листинг 12.4,в. /WEB-lNF/classes/tags/app/CartltaratorTag. Java

package tags.app;

import javax.servlet.jsp.PageContext;import javax.servlet.jsp,JspException;import javax.servlet.jsp.tagext.TagSupport;

import beans.app.User;import beans,app.Users;import beans.app.ShoppingCart;

public class CartlteratorTag extends tags.util.IteratorTagimplements beans.app.Constants {

public int doStartTag() throws JspException {ShoppingCart cart = (ShoppingCart)pageContext.getAttribute(

SHOPPING_CART_KEY,PageContext.SESSION_SCOPE) ;

if(cart —• null) {throw new JspException("CartlteratorTag can't find " +

"cart");}setCollection(cart.getItems());return super.doStartTag {);

I

Рассматриваемый пользовательский дескриптор организует перебор товаров, по-мещенных в "корзинку" покупателя; информация о текущем товаре доступна черезпеременную сценария, имя которой задается посредством атрибута id дескриптора.Код в листинге 12.4,в не дает представления о действиях, выполняемых дескрипто-рами, поскольку основные функциональные возможности реализованы в родитель-ском классе IteratorTag, который рассматривался в главе 2. Метод C a r t l t e r a t o r -Tag, doStartTag иызывает метод setCollect ion, определенный в суперклассе, азатем, перед окончанием выполнения, обращается к методу super .doStartTag.

Помимо содержимого "корзинки" в левой части Web-страницы отображается так-же кнопка Checkout, при активизации которой генерируется запрос к ресурсу chec-kout-action.do. В файле act ions .propert ies , код которого представлен в лис-тинге 12.2,в, логическое имя checkout-action.do отображается в класс a c t i o n s .Checkout Act ion. Код класса Checkout Act ion приведен в листинге 12.5,а.

ЛИСТИНГ 12.5,3. /WEB-INF/classes/CheckoutAction. Java

package actions;

import javax,servlet,ServletException;import javax,servlet.http.HttpServlet;

Page 375: Java Server Pages

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

import javax.servlet.http.HttpServletRequest;import j avax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;

import beans.app.ShoppingCart;

import action.ActionBase;import action.ActionRouter;

public class CheckoutAction extends ActionBaseimplements beans.app.Constants (

public ActionRouter perform(HttpServlet servlet,HttpServletRequest req,HttpServletResponse res)throws ServletException {

HttpSession session = req.getSessionO;ShoppingCart cart = (ShoppingCart)session.getAttribute(

SHOPPING_CART_KEY);if(cart == null) (

throw new ServletException("Cart not found");}return new ActionRouter("checkout-page");

}J

Рассматриваемое действие проверяет, создан ли объект, представляющий "корзинку"пользователя, если данный объект отсутствует, в методе CheckoutAct ion.per formгенерируется исключение. Если "корзинка" присутствует, метод perform возвращаетмаршрутизатор действий, в результате чего запрос перенаправляется к документу,который позволяет покупателю проверить свой выбор.

Проверка выбранных товаровДокумент, внешний вид которого показан на рис. 12.5, отображает счет, вклю-

чающий перечень товаров, находящихся в "корзинке". Для завершения транзакциипользователь должен активизировать кнопку Purchase The Items Listed Above.

Код документа, позволяющего покупателю проверить свой выбор, приведен в лис-тинге 12.5,6.

Листинг 12.5,6. /WEB-INF/jap/checkout/page, j s p

<%@ taglib uri='security' prefix='security'%>taglib uri='regions' prefix='region' %>

<security:enforceLoginloginPage='/WEB-iNF/jsp/login/page.jsp'errorPage='/WEB-INF/j sp/loginFailed/page.j sp'/>

<region:render region='CHECKOUT_REGION'/>

Page 376: Java Server Pages

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

• MWPSCH Inters Itti&a '

FruitStand.com

Here are the items you selected.

liuffl AmcunE Prlca/Ib. Tolal

apple 20 lbs И 39 tDSB

Ьэпапз T 3 11)0 10.63 (1.77

canla loupe ID lbs. №.19 10.19

ors;ie1rj:r 25b3 I04S 11 23

.tilts 3 0 lbs. (079 К.Э7

Tlwitems haeij abo** will tie biitedlo;

Jaoies Wikon2J973 Roquert LaneKhica.NewYoikUSAVisa

Purehnse The hems listed Above

Thanks (or stopping bf Today is Wsdwarfsy, Feb>iwy21, 2001-

*|

Рис. /2.5. Web-страница, предназначенная для проверки выбора

Как и документ, реализующий "витрину" {листинг 12.3,а), рассматриваемый доку-мент воспроизводит область, В данном случае используется область CHECKOUT_REGION,определенная следующим образом:

<region:define id='CHECKOUT_REGIONf region-'LOGIH_REGION'><region:put s e c t i o n = ' c o n t e n t '

content=' /WEB-INF/jsp/checkout/content.jsp'/></region:define>

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

Page 377: Java Server Pages

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

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

<%@ page contentType='text/html; charset=UTF-8' %><%@ page import='beans.app.User' %>

<%@ taglib uri='application' prefix='app' %><%@ taglib uri='il8n' prefix='il8n' %>

<font si2e='4' color='blue'><il8n:message base='app' key='checkout.title'/>

</fontxp>

<img src='graphics/cart.gif'/>

<table cellpadding=' 10'><thxil8n:message base='app'

key=' checkout.table.header.item'/></th><thxil8n:message base^'app'

key='checkout.table.header.amount'/></th><thxil8n: mess age base=' app'

key='checkout.table.header.pricePerLb'/></th><thxil8n:message base='app'

key=' checkout, table, header .price' /x/th>

<% double total = 0.0; %>

<app:iterateCart id='<% String name = item.getName();

float amt = item.getAraount(),price = item.getPrice(} ;

%><tr>

<td><%= name %></td><td><%= amt %> lbs.</td>< t d x i l 8 n : format currency='<%=new Double (price) %>'/></td>< t d x i l 8 n : format currency='<%=new Double (price*amt) %>'/></td>

</tr><% total += price * amt; %>

</app:iterateCart>

<tr><td colspan='4'xhr/x/td>

</tr><td><bxilBn;message base=' app' key=' checkout, table.total' /></bx/td><tdx/tdxtdx/td><tdxilSn: format currency=' <%= new Double f total) $>'/x/td>

</tr></table><p>

<% User user = (User)session.getAttribute(

tags.security.Constants,USER_KEY); %>

<font size=M' color='blue'>

Page 378: Java Server Pages

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

<ИЭп:message base='app' key=' checkout .billTo' /><p></font>

<%= user.getFirstName () %> <%= user .getLastName () lxbr/><%= user.getAddress() %><br/><%= user.getCity() %>, <%= user.getState() %><br/><%= user.getCountryO %><br/><%= user.getCreditCardTypeU %><br/>

<form action^'purchase-action.do'><input type='submit'

value='<il8n:message key="checkout.purchase.button"/>'/></form>

Подобно полосе в левой части Web-страницы (листинг 12.4,6), документ, опреде-ляющий содержимое области проверки, применяет для перебора товаров, находя-щихся в "корзинке", пользовательский дескриптор Cart l terator, На основанииданных, извлекаемых посредством данного дескриптора, формируется счет.

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

В состав страницы, предназначенной для проверки выбора пользователя, такжевходит форма с кнопкой. При активизации этой кнопки формируется запрос к ресур-су purchase-action, do, который перенаправляется действию actions . Purchase-Action. Код класса PurchaseAction приведен влистинге 12.5,г.

ЛИСТИНГ 12.5,Г. /WEB-INF/classes/actions/Purchas&Acfcion . Java

package actions;

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

import beans.app.ShoppingCart;import beans.app.User;

import action.ActionBase;import action.ActionRouter;

public class Purchaseftction extends ActionBaseimplements beans.app.Constants,

tags.security,Constants (public ActionRouter perform(HttpServlet servlet,

HttpServletRequest req,HttpServletResponse res}throws ServletException I

HttpSession session = req.getSession();ShoppingCart cart = [ShoppingCart)session.getAttribute(

5H0PPING_CART_KEY)if(cart == null) {

Page 379: Java Server Pages

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

<%@ taglib uri='regions ' prefix='region' %>

<region:render region='PURCHASE_REGION'/>

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

<region:define id='PURCHASE_REGION' region='LOGIN_REGION'><region:put section= f content'

content='/WEB-INF/jsp/purchase/content.jsp'/></region:define>

Page 380: Java Server Pages

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

Подобно CHECKOUT_REGION, область PURCHASE_REGION расширяет LOGIN_REGIONи переопределяет только раздел содержимого. Этот раздел генерируется посредствомдокумента /WEB-INF/j sp/purchase/content. j sp, приведенного в листинге 12.5,е.

Листинг 12.5,е. /WEB-iNF/jsp/purchase/content. j sp

<%@ page contentType='text/html; charset=UTF-8' %>

taglib uri='il8:nr prefix=' il8n' l>

<font size-'4' color='blue' ><il8n:message base='app' 5cey='purchase.title'/><p><il8n:massage base='app' key='purchase.willBeShippedOn'/><il8n:format date='<%= new java.util.Date() %>'

dateStyle='<%= Java.text.DateFormat.SHORT %>'/></p></font>

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

Page 381: Java Server Pages

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

Кроме перечисленных выше компонентов bean в приложении также определен

ряд констант.•*"*"! case_study

i+ J graphics

Б Gj Web-infВ1 СЛ classes

"СЗ action•Cl actions

В-Gill beansГ-1-1ЙД app

—•{аЯ f«ms

_J email

Ej html: - £ j iiBn:-i:a |dbc

Sj templates

Й-СЗ tags

ShoppingCart

Item

[nventoiy

Constants

util - ( f 5 CreateDB

Java

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

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

База данныхНа рис. 12.8 показана таблица I n v e n t o r y базы данных. Таб-

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

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

Код приложения CreateDB, которое создает базу данных,приведен в листинге 12.6.

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

JSKU

11001

1002

J1003

-I004

1005

1006

1007

100В

1003

1010

1011

NAME

apple

banana

cantaloupe

grapefruit

grapes

fcnri

peach

pear

pineapple

strawberry

watermelon

PRICE j

0.29

0 69

0.19

049

a79

0.99

039

0.89

0.29

0.B8

0.29

Page 382: Java Server Pages

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

Листинг 12.6. /WEB-INF/util/CreateDB.Java

import java.sql.Connection;import Java.sql.DriverManager;import Java.sql.SQLException;import Java.sql.Statement;

public class CreateDB {private Connection conn;private Statement stmt;

public static void main(String args []) {

new CreateDB();

}public CreateDB() {

try {loadJDBCDriverО;conn = getConnection{"F:/databases/sunpress");stmt • conn.createStatement();

createTables(stmt);populateTables(stmt) ;

stmt.close() ;conn.close() ;DriverManager.getConnection(

"jdbc:cloudscape:;shutdown=true")}catch(SQLException ex) I

ex.printStackTrace();)

Iprivate 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 VARCHARU5), " +"STATE VARCHAR<15), " +"COUNTRY VARCHAR(25), " +"CREDIT_CARD_TYPE VARCHAR(IO), " +"CREDIT_CARD_NUMBER VARCHAR(2D), " +•• CREDITJ:ARD_EXPIRATION VARCHARUO) , " +

"USER_ID VARCHAR[15)f " +

"PASSWORD VARCHAR(15), " +"PASSWORD_HINT VARCHAR(15), " +"ROLES VARCHAR(99))");

stmt.execute("CREATE TABLE Inventory (" +"SKU INTEGER, " +"NAME VARCHAR[30), " +"PRICE FLOAT)");

}catch(SQLException ex) (

Page 383: Java Server Pages

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

ex.printStackTrace(};

I

private void populateTables(Statement stmt) {try {

stmt.execute("INSERT INTO Users VALUES " +"('James

1, 'Wilson

1, '24978 Roquert Lane', 'Ithica'," +

" 'New York', 'USA', 'Visa', '124-3393-62975', '01/05',"+" 'jwilson',

Ts2pdpl8', 'license', 'customer')"),-

stmt.execute("INSERT INTO Inventory VALUES " +'apple', '0.29')," +'banana', '0.69'),"

" ( 4 0 0 1" ( '1002"('1003" (4004"('1005" ('1006"('1007"('1008"('1009"('1010"('1011

'cantaloupe', '0.19'J," +'grapefruit', '0.49')," +'grapes', '0.79')," +'kiwi', '0.99')," +'peach', '0.39')," +•pear

1, '0.69')," +

'pineapple', '0.29')," +'strawberry', '0.89')," +'watermelon', '0.29')");

catch(SQLException ex) {ex.printStackTrace() ;

private void loadJDBCDriver() {try {

Class.forName("COM.cloudscape.core.JDBCDriver");

catch(ClassNotFoundException e) {e.printStackTraceO ;

private Connection getConnection(String dbName) {Connection con = null;try (

con - DriverManager.getConnection(11 jdbc: cloudscape: " + dbName + ";create=true") ;

1catch(SQLException sqe) {

System.err.println("Couldn't access " + dbName);)return con;

Приложение, код которого показан в листинге 12.6, устанавливает соединение сСУБД Cloudscape и создает базу в каталоге f: /databases/sunpress. Затем прило-жение заполняет таблицы базы данных, а после разрывает соединение с базой и пре-кращает работу.

Page 384: Java Server Pages

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

При необходимости можно без труда адаптировать данное приложение для рабо-ты с базой другого производителя. Для этого достаточно изменить имя драйвера вметоде loadJDBCDriver и URL базы в методе getConnection.

Компоненты beanКомпоненты bean, используемые в данном приложении, — это упрощенные версии

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

Листинг 12.7,a. /WEB-INF/classes/baans/app/User . Java

package beans.app;

// Информация о пользователе сохраняется неизменной, поэтому// к объекту может осуществляться одновременное обращение из// нескольких потоков.

public class User implements Java.io.Serializable {private final String firstName, lastName, address, city, state;private final String country, creditCardType, creditCardNumber;private final String creditCardExpiration;private final String userName, password, pwdHint, roles;

public User(String firstName, String lastName, String address,String city, String state, String country,String creditCardType, String creditCardNumber,String creditCardExpiration, String userName,String password, String pwdHint, String roles) [

firstName;lastName;address;city;state;country;creditCardType;creditCardNumber;creditCardExpiration;

this.firstNamethis.lastNamethis.address

citystatecountrycreditCardTypecreditCardKumbercreditCardExpirationuserNamepassword

thisthisthisthisthisthisthisthis

this.pwdHintthis.roles

}public String getFirstName()public String getLastNameOpublic String getAddress()public String getCityOpublic String getStateOpublic String getCountryO

= userName;= password;= pwdHint;= roles;

{ return firstName;{ return lastName;{ return address;{ return city;{ return state;{ return country;

public String getCreditCardType() { return creditCardType; )public String getCreditCardNumber() { return creditCardNumber;}public String getCreditCardExpirationO {

return creditCardExpiration;

Page 385: Java Server Pages

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

Ipublic String getUserName() { return userName; }public String getPassword() { return password; }public String getPwdHint() { return pwdHint; }public String getRoles{) { return roles; }

public boolean equals {String uname, String pwd)return getUserName().equals(uname) ss

getPassword[).equals(pwd);

)

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

Код класса Users, описывающего набор пользователей, приведен в листинге 12.7,6.

ЛИСТИНГ 12.7,6. /WEB-INF/clasaes/beans/app/Uaers . Java

package beans.app;

import Java.sql.ResultSet;import Java.sql.ResultSetMetaData;import Java.sql.SQLException;

import java.util.Enumeration;import java.util.Hashtable;

public class Users {private final static int FIRST_NAME=1, LAST_NAME=2, ADDRESS=3,

CITY=4, STATE=5, COUNTRY-6,CREDIT_TYPE=7, CREDIT_NUMBER=8,CREDIT_EXPIRE=9, USER_NAME=10,PASSWORDS1, PASSWORD_HINT=12,ROLES=13;

private final Hashtable users = new Hashtable П;

public Users(ResultSet rs) 1try (

ResultSetMetaData rsmd = rs.getMetaData();

if(rsmd.getColumnCount() > 0) {boolean moreRows = rSitiextO; // Указывает на

// первую строкуwhile(moreRows) (

addUser(new User(t(String)rs.getObject(FIRST_NAME)),trim(),{(String)rs.getObject(LAST_NAME)),trim(),

Page 386: Java Server Pages

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

( (String)rs.getObject(ADDRESS)). trim(),([String)rs.getObject(CITY)),trim(),((String)rs.getObject(STATE)).trim(),((String)rs.getObject(COUNTRY)).trim(),([String)rs.getObject(CREDIT_TYPE)),trim(),((String)rs.getObject(CREDIT_NUMBER)}.trimО,( (String)rs.getObject(CREDIT_EXPIRE)) ,trim(),((String)rs.getObject(USER_NAME)).trim(),((String)rs.getObject(PASSWORD)).trira(),((String)rs.getObject(PASSWORD_HINT)).trim(),((String)rs.getObject(ROLES)).trim()));

moreRows » rs.next(); // Переход к следующей строке

catch(SQLException ex) (// В конструкторе исключение не может быть сгенерировано

public int getNumberOfUsers() {return users.size();

)public User addUser(User user) {

users.put(user.getUserName(), user);return user;

)public User getUser(String username, String password) {

User user = getUser(username);boolean found = false;

iftuser != null) {found = user.equals(username, password);

)return found ? user : null;

}public User getUser(String username) {

return (User)users.get(username);)public Hashtable getUsersf) (

return users;}public String getPasswordHint(String username) (

User user •> getUser (username) ;return user != null ? user.getPwdHint() : null;

Поскольку информация о пользователях содержится в базе данных, конструкторуUsers передается объект ResultSet, используемый при создании объектов User.Кроме конструктора, в состав класса Users входят другие методы, предоставляющиедоступ к информации о пользователях.

Page 387: Java Server Pages

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

Как и User, класс Users устойчив к обращению из нескольких потоков, посколькучлены класса не могут изменяться.

В листинге 12.7,в представлен код класса Item, представляющего продукт, предна-значенный для продажи.

ЛИСТИНГ 12.7,в. /WEB-INF/classes/beans/app/Item. J

package beans.app;

// Данный класс представляет продукт, находящийся в магазине// или в "корзинке". Класс содержит четыре свойства: sku, (код// продукта), name (имя), price (цена) и amount (количество).// Большинство свойств доступно только для чтения, что упрощает// поддержку работы в многопотоковом режиме.

public class Item implements Java.io.Serializable {private final int sku;private final float price;private final String name;

private float amount;

public Item tint sku, String name, float price, float amount) {this.sku • sku;

this.name = name;this.amount - amount;this.price - price;

)public int getSkuf) { return sku; }public String getNamef) { return name; )public float getPriceO { return price; )

public synchronized float getAmount() {return amount;

}public synchronized void setAmount(float amount) {

this.amount = amount;

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

Товары, представленные объектами Item, принадлежат набору. Код классаInventory, представляющего набор, приведен в листинге 12.7,г.

Page 388: Java Server Pages

Базовый набор классов Mode! 2 393

Листинг 12.7,г. /WSB-INF/classes/beans/app/Inventorv. Java

package beans.app;

import java.util.Iterator;import java.util.Vector;

public class Inventory implements Java.io.Serializable (final protected Vector items;

public Inventory() {items = new Vector();

}

public void addltem(ltem item) {items.add(item);

}public void removeltem(ltem item) {

items.remove(item);}public Vector getltems () {

return items;

Класс Inventory соответствует образу разработки fa9ade и представляет собойупрощенный интерфейс для вектора, элементами которого являются объекты това-ров (образ разработки fa?ade был рассмотрен в главе 3). Кроме того, класс Inventoryпредоставляет метод getltems, который возвращает вектор товаров. Класс, вызы-вающий этот метод, не может модифицировать вектор.

Код класса ShoppingCart приведен в листинге 12,7,д.

Листинг 12.7,д. /WEв-INF/classes/beans/app/ShoppingCart.java

package beans.app;

public class ShoppingCart extends Inventory {

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

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

Page 389: Java Server Pages

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

Листинг 12.7,е. /WEB-INF/clasзаз/beans/app/Constants.Java

package beans.app;

// Эти константы в основном используются в действиях данного/7 приложения (см. /WEB-lNF/classes/actions).

public interface Constants (// Префикс позволяет избежать случайного повторения// имен констант.static final String prefix = "beans.app";

static final String// Ключи атрибутовLOCALE_KEY = prefix + ".locale",SHOPPING_CART_KEY - prefix + ".cart",USERS_KEY - prefix + ".users",USERNAME_KEY -> prefix + " . username",PASSWORD__KEY • prefix + ".password",CONFIRM_PA5SWORD_KEY - prefix + ".cnfrmpwd",FASSWORD_HINT_KEY = prefix + ".pwdhint",

// Значения по умолчаниюDEFAULT_I18N_BftSE = "app";

Константы, определенные в C o n s t a n t s , доступны классам, реализующим данныйинтерфейс. Например, класс PurchaseAct ion реализует интерфейс C o n s t a n t s ииспользует переменную SHOPPING_CART_KEY.

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

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

На рис. 12.9 показаны файлы, с помощью которых создаются девять JSP-документов,входящих в состав приложения. Каждый из подкаталогов каталога /WEB-INF/ j sp, за ис-ключением shared и templates, содержит все JSP-файлы, соответствующие одномуJSP-документу. Например, в каталоге /WEB-INF/jsp/accountCreated находятся файлыpage. j sp и c o n t e n t . j sp JSP-документа, предназначенного для создания учетной записи.

В каталоге shared расположены JSP-файлы, которые используются в несколькихJSP-документах, а каталог t e m p l a t e s содержит шаблон, единый для всего приложения.

Каждый из JSP-документов определяется посредством области, использующей шаб-лон, и содержимого, которое включается в шаблон. Все области приложения использу-ют один и тот же шаблон, который находится в файле /WEB-INF/jsp/templates/hscf. j sp. Этот шаблон организует отображение заголовка, полосы в левой части Web-страницы, содержимого и нижнего колонтитула.

Page 390: Java Server Pages

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

case_studygraphicsWeb-inf

Щ classes

isP , 'Ш) accountCreated

checkout - 'createAccount -homepage

j login -( 2 j loginFailed - -•Г"Л purchase -.,

shared » ^ ~" "* - ^showHint - "~ ~* ^storefront. *" ^ "* - ^templates \ "" >. ~,

tlds

sidebarpageheaderFormcontent

•'• inventorysidebaipageinventory-to-xmlheaderfootercontent

®^ JSP Щ] XSLT

ftic. 1Г.9. Просмотры: JSP-документы

Все области приложения определены в одном файле /WEB-INF/jsp/region-Def i n i t i o n s . j sp, код которого представлен в листинге 12.7,ж.

Листинг 12.7,ж./WEB-INF/jsp/regionDefinitions.jsp

taglib uri='regions' prefix='region' %>

<region:de fine id-"3T0REFR0NT_REGI0N'template='/WEB-lKF/jsp/templates/hscf.jsp'>

<region:put section='title'content-'FruitStand.com'direct-'true'/>

<region:put section="background'content-'graphics/blueAndWhiteBackground.gif'

Page 391: Java Server Pages

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

direct='true'/>

<region:put section-'header'content='/WEB-IMF/jsp/storefront/header.jsp'/>

<region:put section^'sidebar'content^'/WEB-INF/jsp/storefront/sidebar.jsp

1/>

<region:put section='content'content='/WEB-INF/jsp/storefront/content.jsp'/>

<region:put section='footer1

content='/WEB-INF/jsp/storefront/footer.jsp'/></region:define>

<region:define id='LOGIN_REGION' region='STOREFRONT_REGION'><region:put section='header'

content='/WEB-INF/jsp/login/header.jsp'/>

<region:put section=1sidebar

1

content='/WEB-INF/j sp/login/sidebar.j sp'/>

<region:put section='content'content^

1/WEB-INF/jsp/login/content.jsp'/>

</region:define>

<region:define id='HOMEPAGE_REGION' region='STOREFRONT_REGION'><region:put section='sidebar'

corvtent='/WEB-INF/jsp/homepage/sidebar.jsp'/>

<region:put section='content'content='/WEB-INF/jsp/homepage/content.jsp*/>

</region:define>

<region:define id=•CREATEJVCCOUNT_REGION' region='LOGIN_REGIOM'><region:put section^'content'

content-'/WEB-INF/j sp/createAccount/content.j sp'/></region:define>

<region:define id='L0GlN_FfllLED_REGION' region^'LOGIN_REGION'><region:put section='content'

content='/WEB-INF/jsp/loginFailed/content.jsp'/></region:define>

<region:define id='ACCOUNT_CREATED_REGIOM' region-'LOGIN_REGION'><region:put section='content'

content='/WEB-INF/j sp/accountCreated/content.jsp'/></region:define>

<region:define id='CHECKO0T_REGI0N' region-'LOGIN_REGIOH'><region:put section='content'

content='/WEB-INF/jsp/checkout/content.jsp'/></region:define>

<region:define id='PURCHASE_REGION' region='LOGIN_REGIQN'><region:put section^'content'

Page 392: Java Server Pages

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

content='/WEB-iNF/jsp/purchase/content.jsp'/></region:define>

<region:define id='SHOW_HINT_REGION' region='LOGIN_REGION'><region:put s e c t i o n ^ ' c o n t e n t '

content='/WEB-INF/j sp/showHint/content.jsp'/></region:define>

Большинство областей, определенных в листинге 12.7,ж, являются расширениямидругих областей и используют характеристики своих предков. Так, например, областьHOMEPAGE_REGION использует заголовок, фон и нижний колонтитул областиSTOREFRONT_REGION. Все остальные области используют содержимое LOGIN_REGIONили STOREFRONT_REGION.

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

Единый шаблон для рассматриваемого в данной главе приложения приведен влистинге 12.7,з.

ЛИСТИНГ 12.7,3. /WEB-INF/;)ep/teniplat«/h*cf . jep

<htmlxhead><%@ taglib uri-'regions' prefix-'region' %><titlexregion: render section='titleV></title>

</head>

<body baclcground-'<region:render section=tbackground1/>1>

<table><tr valign-'top'>

•CtdXregion: render section»1 sidebar'/x/td><td>

<table><tr><tdxregion: render section»1 header'/x/tdx/tr><trxtdxregion: render section» 'content' /x/tdx/tr><trxtdxregion: render section» ' footer' /x/td></tr>

</table></td>

</tr></table>

</bodyx/html>

Шаблон, приведенный в листинге 12.7,з, использует для отображения содержимо-го HTML-таблицу. Содержимое предоставляют JSP-файлы, на которые ссылаются об-ласти. Например, при воспроизведении области LOGIN_REGION шаблон используетсодержимое, заданное в определении этой области, а именно заголовок, фон, нижнийколонтитул и содержимое страницы регистрации.

Заметьте, что в каждом каталоге приложения, соответствующем JSP-документу,находится JSP-файл с именем page. j sp. Этот файл предназначен для воспроизведе-

Page 393: Java Server Pages

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

ния соответствующей области. Например, в листинге 12.7,и приведено содержимоефайла page. j sp для страницы регистрации. Этот JSP-файл воспроизводит областьLOGIN REGION.

Листинг 12.7,и. /WEB-lNF/jsp/login/page. jsp

<%@ tagl ib uri='regions ' prefix='region' %>

<region:render region='LOGIN REGION'/>

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

Листинг 12.7, к. /WSB-INF/j эр/ checkout /page. jsp

<%@ taglib uri='security' prefix^'security'$><%@ taglib uri='regions' prefix='region' %>

<security:enforceLoginloginPage='/WEB-INF/jsp/login/page.jsp'errorPage='/WEB-INF/jsp/loginFailed/page.jsp'/>

<region:render region^'CHECKOUT_REGION'/>

В файле, показанном в листинге 12.7,к, присутствует пользовательский дескрип-тор s e c u r i t y : e n f o r c e L o g i n . Этот дескриптор обрабатывает часть документа, сле-дующую за ним, только в том случае, если пользователь зарегистрирован, в против-ном случае запрос перенаправляется странице регистрации. Подробно дескрипторs e c u r i t y : e n f o r c e L o g i n описан в главе 9. Средства поддержки аутентификации вприложении, реализующем интерактивный магазин, обсуждаются далее в этой главе.

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

Области определяют содержимое, отображаемое с помощью шаблонов. Возмож-ность отдельно определять области позволяет создавать единое описание несколькихJSP-документов. Такой подход упрощает создание и сопровождение JSP-документов.

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

Page 394: Java Server Pages

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

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

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

Контроллеры: сервлеты и действияВ приложениях, соответствующих архитектуре "модель-просмотр-контроллер"

(MVC), данные хранятся в модели и отображаются с помощью просмотров. Контрол-леры объединяют модель и просмотры. Контроллер реагирует на события и можетобращаться к модели перед тем, как передать управление просмотру.

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

СервлетыВ рассматриваемом приложении используются четыре сервлета, расположенные в

каталоге /WEB-INF/classes (рис. 12.10).

Й-jjjp case_study / IВ Q graphics13 CJ Web-inf-

ActionServlet

AppAuthenticateS ervlel

Authenticates etvlet

SetupS ervlel

Java Файлы XML

Рис. 12.10. Сервлеты и файл web.xml

Сервлет ActionServlet представляет собой часть базового набора Model 2 и об-рабатывает все HTTP-запросы, оканчивающиеся символами " . do". Поскольку дан-ный сервлет был подробно описан в главе 6, здесь он обсуждаться не будет.

Класс AuthenticateServlet представляет собой абстрактный базовый класс,подклассом которого является AppAuthenticateServlet. Эти сервлеты, используе-мые для поддержки аутентификации, будут рассмотрены далее в этой главе.

Сервлет SetupServlet загружается при запуске приложения и инициализируетбазу данных. Код этого сервлета приведен в листинге 12.8,6.

Конфигурация всех сервлетов, используемых в данном приложении, задается вфайле web.xml, код которого представлен в листинге 12.8,а.

Page 395: Java Server Pages

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

Листинг 12.6,а.

<?xml version-"].. О" encoding="ISO-8859-l"?>

<!DOCTYPE web-appPUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN""http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">

<web-app><servlet>

<servlet-name>authenticate</servlet-name><servlet-clasa>AppAuthenticateServlet</servlet-class>

</servlet>

<servlet><servlet-naine>action</servlet-riaine><3ervlet-class>ActionServlet</3ervlet-cla3S><init-param>

<param-name>action-mappir>gs</param-name><param-value>actions</param-value>

</init-param></servlet>

<servlet><aervlet-name>setup</servlet-name><servlet-class>SetupServlet</servlet-claas><init-param>

<param-naine>jcibcDriver</param-naine><param-value>COM.cloudscape.core.JDBCDriver</param-value>

</init-param>

<init-param>

<param-name>jdbcURL</param-name><param-value>

jdbc:cloudscape:f:/databases/sunpresa;create=falae</param-value>

</init-param>

<init-param><param-name>jdbcU3er</paraiti-name><param-value>roymartin</param-value>

</init-param>

<init-param>

<param-name>jdbcPassword</param-name>

<param-value>royboy</param-value></init-param>

<load-on-startup/></servlet>

<servlet-mapping><servlet-name>action</servlet-name><url-pattern>*.do</url-pattern>

</aervlet-mapping>

Page 396: Java Server Pages

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

<servlet-mapping><servlet-name>authenticate</servlet-name><url-pattern>/authenticate</url-patte]:n>

</servlet-mapping>

<welcome-file-list><welcome-file>index.jsp</welcome-file>

</welcome-

<taglib><taglib-uri>application</taglib-uri><taglib-location>/WEB-INF/tlds/utilities.tld</taglib-location>

</taglib>

. . . Информация об остальных библиотеках здесь не приводится •..

</web-app>

В файле web. xml определяются имена и классы сервлетов и при необходимостиприводятся инициализационные параметры. Так, например, для сервлета Setup-Servlet указаны параметры, определяющие JDBC-драйвер, JDBC URL, регистраци-онное имя и пароль.

Кроме того, в файле web. xml задаются библиотеки пользовательских дескрипто-ров, применяемые в приложении. Поскольку количество библиотек достаточно вели-ко, файл web.xml в листинге 12.8,а приведен не полностью. Помимо библиотекиappl icat ion в приложении используются следующие библиотеки:

• database

• dorn

• html

• il8n

• template

• tokens

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

Код сервлета SetupServlet, предназначенного для инициализации базы данных,приведен в листинге 12.8,6.

ЛИСТИНГ 12.8,6. /WEB-INF/classes/SetupServlet.Java

import Java.sql,Connection;import Java.sql.ResultSet;import Java.sql.ResultSetMetaData;import Java.sql.Statement;import Java.sql.SQLException;

import j avax.servlet.ServletConfig;

import javax.servlet.ServletContext;

Page 397: Java Server Pages

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

import j avax.servlet.ServletException;import j avax.servlet.http.HttpServlet;

import beans.app.User;import beans.app.Users;import beans.jdbc.DbConnectionPool;

public class SetupServlet extends HttpServletimplements beans.app.Constants,

tags.jdbc.Constants {private DbConnectionPool pool;

public void init(ServletConfig config) throws ServletException)super.init(config);

ServletContext ctx = config.getServletContextf);createDbConnectionPool(config, ctx);

try (ctx.setAttribute(USERS_KEY, loadUsers(ctx)};

Icatch(SQLException ex) {

throw new ServletException(ex);

public void destroy() (

ServletContext ctx = getServletConfig().getServletContext();ctx.removeAttribute(DBPOOL_KEY);ctx.removeAttribute(USERS_KEY);

pool.shutdown();pool = null;super.destroy();

}private void createDbConnectionPool(ServletConfig config,

ServletContext ctx) (pool = new DbConnectionPool(

config.getInitParameter("jdbcDriver"),config.getInitParameter("jdbcORL") ,config.getInitParameter("jdbcUser"),config.getInitParameter("jdbcPwd"});

ctx.setAttribute(DBPOOL_KEY, pool);

}private Users loadUsers(ServletContext ctx)

throws SQLException {Connection conn = null;

if(pool != null) {try (

// Если пул заполнен, ожидание соединения до// 10 секунд.

conn = (Connection)pool.getConnection(10000);

>catch(Exception ex) {

throw new SQLExceptionlex.getMessageO);

Page 398: Java Server Pages

Интернационализация 403

Statement stmt = conn.createStatement(};ResultSet rs - Stmt.executeQuery("SELECT * FROM USERS")Users users - new Users(rs};

pool.recycleConnection(conn);return users;

1return null;

Сервлет, код которого приведен в листинге 12.8,6, создает пул соединений с базой,используя инициализационные параметры, заданные в файле web. xml. Вопросы реали-зации и использования пула соединений с базой данных рассматривались в главе 10.

После построения пула соединений SetupServlet выполняет запрос к базе дан-ных и на основе результатов запроса создает набор пользователей.

Контейнер сервлетов вызывает SetupServlet при первом обращении к прило-жению; на это указывает элемент load-on-startup в файле web .xml.

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

ДействияHTTP-запрос, оканчивающийся символами ".do", в конечном итоге передается

действию. Подробно этот вопрос обсуждался в главе 6, поэтому здесь мы не будемуделять ему внимание. Действия являются частью любого Web-приложения, соответ-ствующего архитектуре Model 2; поэтому они используются и в приложении, реали-зующем интерактивный магазин (рис. 12.11).

В каталогах /WEB-INF/classes/action и /WEB-INF/classes/action/eventsсодержатся классы, составляющие базовый набор Model 2. Эти классы обсуждались вглавах б и 7.

В каталоге /WEB-INF/classes/actions расположены действия, специфическиедля рассматриваемого приложения. Каждое из этих действий, как видно из их имен,представляет отдельный сценарий развития событий. В этом разделе мы не будемподробно рассматривать данные действия, однако они обсуждаются на протяжениивсей главы. Так, например, при описании "корзинки" покупателя рассматривалосьдействие AddToCartAction, а рассмотрению страницы проверки сопутствовало об-суждение действия CheckoutAction.

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

ские дескрипторы, предназначенные для интернационализации текста, числовыхданных, даты и денежных единиц. В данном разделе рассматривается выбор пользо-вателем языка в процессе работы приложения.

Page 399: Java Server Pages

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

Action

ActionBase

ActionFactotii

ActionR outer

S en sitiveActionLis tenet

Классы из базового набора Model 2

rtJLJ graphicsВЩ Web-inf

classesaction ' '

events---5Г1 actions- _

Ш-EJ beanstags

ш-CJtlds

SJAddToCartAction

CheckoutAction

GoShoppingAction

NewAccountAction

PuichasaAction

Q ueryAccountAction

ShowHintAction

UpdateLocaleAction

ValidateAc

^Действия, специфические дня приложения

Рис. 12.11. Классы действий а приложении Mode! 2

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

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

1. Пользователь щелкает мышью на изображении флага в левой части Web-страницы.

2. Приложение изменяет регион в соответствии с выбранным флагом.

3. Приложение повторно отображает текущую Web-страницу.

На рис. 12.13 показаны файлы, участвующие в реализации описанного выше сце-нария развития.

Page 400: Java Server Pages

Интернационализация 405

SO™ 1*иМ« l a *

Bitle Ausstellung(Bnrichtung) derVertiindung

Рис. 12. ?2. Web-страница приложения на английском, немецком и китайском языках

Page 401: Java Server Pages

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

case_studygraphicsWeb-inf

classesaction

Qg eventsactions - „beans

S O tagsB-C3

••-Si accountCreatedCJ checkout-Iv-Ч createAcctKint

•!"~1 homepage

•["~1 login

CJ loginF ailed-""1 purchase-23 sharedШ showHint•ffJl storefrontО templates

C j tlds• _J util

UpdateLocateAction

Java JSP Файлы свойств

fttc. 12.13. Файлы, участвующие в реализации сценария развития

"выбор пользователем языка взаимодействия"

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

Листинг 12.9,a. /WEB-INF/classes/app en.properties

click=clickhere=here

messages.login-header-title=FruitStand.commessages.today=Todaу is (0, date}

homepage.title=A Model 2 JSP Applicationhomepage.text=<p>This mockup of a fruit stand uses many of thetechniques discussed in Advanced JavaServer Pages

( published by Sun

Microsystems Press and Prentice-Hall in May 2001.

... Остальная часть сообщения homepage.title здесь не приводится

Page 402: Java Server Pages

Интернационализация 407

login.title=FruitStand.com

login,form.title=Please Loginlogin.button.submit^loginlogin.textfield.name=Namelogin.textfield.pwd=Passwordlogin.footer.message=Thanks for stopping by. Today is

login.failed.title=Login Failedlogin.failed.message=Please enter a valid username and password, orcreate a new account

... Остальная часть файла пропущена ...

В листинге 12.9,6 приведен фрагмент файла свойств, поддерживающего немецкийязык.

Листинг 12.9,6. /WEB-INF/clae3ea/app_de. properties

click=Klickhere=Hier

messages.login-header-title^FruitStand.commessages.today=Huite i s t (0, date}

homepage.title=Eine Model 2 JSP Anwendung

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

••!•• -;:;•:• . ; • : : . ' . s ; , . a s : > . , . . . ; , - , . . . . . . - . -

Листинг 12.9,В. /WEB-IKF/classea/app^zh .properties

click=\u70b9\u51fbhere=\u8fd9\u91cc

messages.Iogin-header-title=\u6b22\u8fce\u514 9\u4e34messages.today=\u<leca\u5929\u662f fO, date}

homepage.title=\u4e00\u4e2a\u6a21\u5fOf\u4e8cJSP\u8303\u4f8b

В листинге 12.9,г приведено содержимое файла flags . j sp

Page 403: Java Server Pages

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

Листинг 12.9,г. /WEB-INF/jsp/shared/flaga. J3p

<% String thisPage • request.getServletPath() ;String updateLocaleAction • "update-locale-aetion..do?page=" +

thisPage + "Scountry="; %><table widths'160'>

<trxtd><a href='<%= updateLocaleAction + "EN" %>'>

<img src=' graphics/flags/britain_flag.gif/></a>

<a href='<%= updateLocaleAction + "DE" %>'><img src='graphics/flags/german_flag.gif'/></a>

<a href='<%= updateLocaleAction + "ZH" %>'><img src=' graphics/f lags/ chine se__f lag .g i f /></ax/tr>

</tdxtd height='25'></td></table>

JSP-файл, показанный в листинге 12.9,г, задает действие для каждого из флагов всоответствии с текущей Web-страницей и страной, которую представляет этот флаг.Например, файл f lags, j sp включается в документ /WEB-INF/ j sp/storefront/sidebar, поэтому, если пользователь щелкает на изображении китайского флага, вы-зывается следующее действие:

http://localhost:80BQ/case-study/update-locale-action.do?page=/WEB-INF/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");

Page 404: Java Server Pages

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

req.getSession(true).setAttribute(LOCALE_KEY, locale);res.setLocale(locale);

return new ActionRouter(forwardTo, true, false);1

1

Действие UpdateLocaleAct ion получает посредством параметра запроса ин-формацию о стране и, соответственно, о требуемом языке (запрос генерирует JSP-файл, представленный в листинге 12.9,д). Имея сведения о языке, действие устанав-ливает атрибут сеанса, который определяет текущий регион, задает регион для отве-та и перенаправляет запрос исходному документу.

После того как исходный документ отображается повторно, пользовательские де-скрипторы i l 8 n отображают текст на выбранном языке. Эти дескрипторы использу-ют информацию о регионе, связанную с сеансом конкретного пользователя.

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

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

1. Если пользователь пытается выполнить проверку, дескриптор определяет, за-регистрирован ли пользователь.

2. Если пользователь не зарегистрирован, дескриптор передает управление стра-нице регистрации.

3. На странице регистрации пользователь активизирует ссылку, соответствую-щую созданию учетной записи.

4. Приложение отображает форму, предназначенную для создания новой учетнойзаписи.

5. Пользователь заполняетформуи активизирует кнопку создания учетной записи.

6. Приложение повторно выводит форму регистрации, и пользователь регистри-

руется.

Файлы, участвующие в реализации сценария развития "аутентификация пользова-теля", показаны на рис. 12.14.

После активизации кнопки Checkout на странице "витрины" пользователь пере-направляется к документу проверки. Код этого документа показан в листинге 12.10,а.

Page 405: Java Server Pages

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

H l l case_studyЩ- Cj graphicsВ CJ Web-inf

3 i" 1 classes3 - £ 3 action

•_J eventsj" I actions •

H Cj beansВ CJ tags

•••£] app

-ГП emailCj html

ш CJ idbc: - -i:"'"} logic

1 regionsI secuiity

' 1 tokens• LJ util

Ш-CJ «mlj jsp

• - Ш accountCreated- - "Ш checkout

• Ci createAccount - - -• - Ш homepage- C J login•-ГИ loginFailed••••{?Щ purchase

-Q shared- ^ " 1 showHint

C3 storefrontC3 templates

J lidsjutii

^ validate-account

NewAccountActionValidateAccountAction

QueryAccountAction

Java $ JSP

Рис. 72. /4. Файлы, участвующие в реализации сценария развития "аутентификацияпользователя"

Листинг 12.10,a. /WEB-INF/jsp/checkout/page. jэр

<%@ taglib uri='security' prefix='security'%><%@ taglib uri='regions' prefix='region' %>

<security:enforceLogin

Page 406: Java Server Pages

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

loginPage=' /WEB-INF/jsp/login/page.jsp'

errorPage='/WEB-INF/jsp/loginFailed/page.jsp'/>

<region:render region='CHECKOUT_REGION'/>

Класс поддержки дескриптора secur i ty : 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);

try {

pageContext.forward(loginPage);

return SKIP_PAGE;

)catch(Exception ex) (

throw new JspException(ex.getMessage());

}Ireturn EVAL_PAGE;

Дескриптор secur i ty : enforceLogin проверяет, присутствует ли объект User вобласти видимости сеанса; при наличии объекта обрабатывается остальная часть до-кумента, в противном случае управление передается документу регистрации. Содер-жимое документа регистрации приведено в листинге 12.10,в.

ЛИСТИНГ 12.10.В. /WEB-INF/jsp/login/content.jsp

<%@ page contentType-'text/html; charset=UTF-8' %>

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

Page 407: Java Server Pages

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

<font size='5' colоr='blue' ><il8n:message key='login.form.title'/>

</fontxhr>

<jsp:include page=' form.jsp' flush=f true'/>

<jsp:include page='../shared/createAccountLink.jsp' flush-'true'/>

Представленный выше JSP-доку мент включает два JSP-файла. Первый из этих фай-лов, form, jsp, генерирует регистрационную формуй используется лишь в сочетаниис другими JSP-файлами. Код файла form, jsp приведем в листинге 12.10,л.

Кроме form, jsp в документ content. j sp включается JSP-файл createAccoun-tLink. j sp, код которого приведен в листинге 12.10,г.

Листинг 12.10,г, /WEB-INF/ jep/shared/createAccountLmk. jsp

taglib uri='il8n' prefix='U8n' %><%@ taglib u r i = ' u t i l i t i e s ' prefix='util ' %>

<i!8n:message base^'app' key='click'/><a href='<util:encodeURL url="query-account-action.do"/>' ><il8nгmessage base='app' key='here'/></a><il8n:message base='app' key=' password.hint.toOpenAccount'/>

Файл createAccountLink. j sp формирует ссылку на ресурс query-account-action . do. Внешний вид страницы регистрации приведен на рис. 12.15.

] FnjtSlmd.com - МшомЛ Internet E xpltmt

file £dit ilgw

hllp.//loealhml:8080/case-study/ched(Oi«-*c<en*7

FruitStand.com

Piease Login

Name

Password |~

login |

click del i Ю open a new account.

Thanks for slopping by Today is Monday, Match 12, 2001.

И DoneJ

Рис. 12.15. Web-страница регистрации

Page 408: Java Server Pages

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

Ссылка, формируемая в файле createAccountLink. j зр, указывает на ресурс

query-account-action.do, который отображается в QueryAccountAction. Дей-

ствие Que r yAccount Act ion приведено в листинге 12.10.Д.

Листинг 12.10,д. AJEB-INF/claaees/QueryAccountAction.java

package actions;

import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax. servlet .http.HttpServletRequ.est;import javax.servlet.http.HttpServletResponse;

import action.ActionBase;import action.ActionRouter;

public class QueryAccountAction extends ActionBase ^public QueryAccountAction() {

// Данное действие обращается к J5P-документу// с чувствительной формойhasSensitiveForms = true;

}public ActionRouter perform (HttpServlet servlet,

HttpServletRequest req,HttpServletRe spoils e res)throws ServletException (

return new ActionRouter("query-account-page");)

\

Метод QueryAccountAction.perform не выполняет никаких действий кромеперенаправления запроса. При перенаправлении запроса используется логическоеимя query-account-page. Класс создан потому, что необходимо выполнить перена-правление JSP-докумен ту, включающему чувствительную форму. Как видно из листин-га, в конструкторе класса задается значение true переменной hasSensitiveForms{эта переменная унаследована от суперкласса). Подробно о перехвате повторной ак-тивизации чувствительных форм будет рассказано далее в этой главе.

Логическое имя query-account-page отображается в документ /WEB-INF/ j sp/createAccount/page.jsp. Содержимое этого документа предоставляет файл / WEB-INF/ jsp/createAccount/content. j sp, код которого приведен в листинге 12.10,е.

Листинг 12.10,е. /ЯЕБ-lNF/jsp/createAccount/content. jap

page contentType='text/html; eharset=UTF-8' %>

<form aotion='validate-account.jsp' method='post' ><table width='450'><tr>

Остальная часть описания таблицы здесь не приводится

Page 409: Java Server Pages

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

</table><br><input type='submit' value='create account'>

</form>

Рассматриваемый JSP-документ содержит достаточно длинное описание таблицы,которое в данном листинге не приводится (полностью таблица представлена в лис-тинге 12.11,а). Внешний вид^Р-документа, приведенного в листинге 12.10,е, показанна рис. 12.16.

-Maud) М а м Eislr»r

»tlp

& i 4 й nSlop Я*е* Hen Sts* f«o*«

FruitStand.com

Open a New Account

Personal Information

[Jneseph

JLacaite

First Name

Las) Name

Street Ad die s 5

City

Slate

Counlry

Phone Number

Credit Card Information

|S9113W OekLane

tWestctiesler

Credit Card Type | Discovery £

Card Numb«

Erpirarron Dste

Userrsarrie And Password

User Name

Confirm Рэв sword

Password Him |rrry dog's name

treeie

Thanks (or stopping by. Tnd»y is *tomftc, Msrcft 5, 2001.

РИС. 12.16. Создание новой учетной записи

Page 410: Java Server Pages

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

Таблица, показанная на рис. 12.16, находится в составе формы. Атрибут act ionформы имеет значение val idate-account, jsp, поэтому при активизации кнопкиcreate account броузер направляет запрос указанному документу. Код данного JSP-документа представлен в листинге 12.10,ж.

Листинг12.10,ж. /validate-sccount.jep

<jsp:useBean id='form' scopes'request'

class=' beans.app.forms.CreateAccountForm'/>

<jsp:setFroperty name='form' property^'*'/>

<jsp:forward page='/validate-account-action.do'/>

Представленный выше JSP-документ создает компонент bean CreateAccount-Form и заполняет его данными из формы, показанной на рис. 12.16. Этот компонентиспользуется для проверки данных формы; подробно процедура проверки будет опи-сана далее в этой главе. Затем JSP-документ, код которого показан в листинге 12.10,ж,перенаправляет запрос ресурсу val idate-account-act ion.do, который отобража-ется в действие, представленное в листинге 12.10,з.

ЛИСТИНГ 12.10,3. /WEB-INF/claaaes/actions/ValidateAccountAction. Java

{фрагмент)

package actions;

public class ValidateAccountAction extends ActionBaseimplements beans.app,Constants {

public ActionRouter perform(HttpServlet servlet,HttpServletRequest req,HttpServletResponse res)throws ServletException {

CreateAccountForm form = (CreateAccountForm)req.getAttribute("form");

boolean errorDetected - false;

return ercorDetected ?

new ActionRouter("query-account-page") :

new ActionRoutert"/new-account-aetion,do", true, false);

1

Класс ValidateAccountAction проверяет, корректно ли заполнена форма; приналичии ошибки управление передается query-account-page, что приводит к по-вторному отображению формы, представленной на рис. 12.16, и соответствующегосообщения об ошибке. Если форма заполнена правильно, управление передается ре-сурсу new-account-action.do, который отображается в действие, приведенное влистинге 12.10,и.

Page 411: Java Server Pages

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

ЛИСТИНГ 12.10,И. /ИЕВ-rNF/clasaes/actions/HewAeaountAetion. j

package actions;

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

import beans.app.User;import beans.app.Users;

import action.ActionBase;import action.ActionRouter;

public class NewAccountAction extends ActionBaseimplements beans.app.Constants (

public NewAcccuntAction[) {isSensitive = true; // Чувствительное действие

}public ActionRouter perform(HttpServlet servlet,

HttpServletRequest req,HttpServletResponse res)throws ServletException {

Users users - (Users)servlet.getservletcontext().getAttribute(USERS_KEY);

if(users == null) {throw new ServletException("Users not found " +

"in application scope");

JHttpSession session = req.getSession();

String firstName = req.getParameter("firstName");String lastMame = req.getParameter("lastName");String address •» req.getParameter("address");String city - req.getParameter("city");String state = req.getParameter("state");String country = req.getParameter("country");

String creditCardType - req.getParameter("creditCardType");String creditCardNumber =

req.getParameter("creditCardMumber");String creditCardExpiration =

req.getParameter("creditCardExpiration");

String uname = req.getParameter("userKame");String pwd = req.getParameter("password");

String pwdConfirm = req.getParameter("pwdConfirm");String pwdHint = req.getParameter("pwdHint"};

session.setAttribute{USERNAME_KEY, uname);session.setAttribute(PASSWORD_KEY, pwd);

Page 412: Java Server Pages

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

users.addUser(new User{firstName, lastKame, address, city, state,

country, creditCardType, creditCardNumber,creditCardExpiration, uname, pwd, pwdHint,"customer")); // Роль customer

req.setAttribute(USERNAME_KEY, uname);

return new ActionRouter("account-created-page");

Действие NewAccountAction, представленное в листинге 12.10,и, проверяет,присутствует ли набор пользователей в области видимости приложения; если наборотсутствует, генерируется исключение.

Если набор пользователей обнаружен в области видимости приложения, действиена основе полученных данных формы создает новый объект, представляющий поль-зователя, и добавляет его в набор. Данное действие также сохраняет в области види-мости приложения регистрационное имя и пароль, откуда они впоследствии извле-каются формой регистрации, представленной в листинге 12.10,л. По окончании ра-боты действие передает управление документу account-created-page. Содержи-мое соответствующего JSP-фай л а приведено в листинге 12,10,к.

Листинг 12.10,к. /WEB-iNF/jep/aceraviritcrested/content, jsp

<%@ page contentType='text/html; charset=UTF-8' %><%@ taglib uri='il8n' prefix='il8n' %>

<font s ize='4 ' color='blue'><il8n:message base='app' key='accountCreated.text'/>

</font>

<pxjsp:include page='../login/form.jsp' flush—' t rue ' /></p>

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

Листинг 12.10,л. /WEB-INF/jsp/login/form, jsp

<%8 page contentType='text/html; charset=UTF-8' %>

taglib uri='il8n' prefix='<4@ taglib uri='utilities' prefix='util' %>

<form action='<%= response.encodeORL("authenticate") %>'method='post'>

<table><tr>

<tdxil8n: message base=' app' key=' login, text field, name' />

Page 413: Java Server Pages

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

</td><tdxinput type=' text' name=' userName'

value='<util:sessionAttribute property="<%= beans.app.Constants.USERNAME_KEY %>"/>'/>

</td></tr><tr>

<tdxil8n:message base='app' key='login.textfield.pwd'/></td><tdxinput type='password' name='password' size='8'

value='<util:sessionAttribute property=»<%= beans.app.Constants.PASSWORD_KEY %>"/>'/>

</td></tr>

</table><br><input type='submit' valuer

'<ilBn:message key="login.button.submit"/>'/></form>

В форме, представленной в листинге 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 AuthenticateServletimplements 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 и реализует метод getUser . Если пользователь, соответст-вующий указанным регистрационному имени и паролю, существует, метод ge tUserвозвращает объект 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,н.

Page 414: Java Server Pages

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

Листинг 12.10,Н, /WEB-lNF/claases/AuthenticateServlet.Java

import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import j avax.servlet.http.HttpSession;import Java.io.lOException;

public abstract class AuthenticateServlet extends HttpServletimplements beans.app.Constants,

tags.security.Constants (abstract Object getUser(String username, String pwd);

public void service(HttpServletRequest req,HttpServletResponse res)throws lOException, ServletException {

HttpSession session = req.getSession();String uname = req.getParameter("userNarae");String pwd = req.getParameter("password");Object user = getUser(uname, pwd) ;

session.setAttribute(USERNAME_KEY, uname);session.setAttribute(PASSWORD_KEY, pwd);

if(user -= null) { // Неавторизованный доступString loginPage = (String)session.

getAttribute(LOGIN_PAGE_KEY);String errorPage = (String)session.

getAttribute(ERROR_PAGE_KEY);String forwardTo = errorPage != null ? errorPage :

loginPage;session.setAttribute(LOGIN_ERROR_KEY,

"Username: " + uname + " and " +"Password: " + pwd + " are not valid.");

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

Ielse ( // Авторизованный доступ

String protectedPage = (String)session.getAttribute(FROTECTED_PAGE_KEY);

session.removeAttribute(LOGIN_PAGE_KEY);session.removeAttribute(ERROR_PAGE_KEY);session.removeAttribute(PROTECTED_PAGE_KEY);session.removeAttribute(LOGIN_ERROR_KEY);session.setAttribute(USER_KEY, user);

getServletContext().getRequestDispatcher(res.encodeURL(protectedPage)).forward(req,res);

Page 415: Java Server Pages

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

Класс AuthenticateServlet отличается от одноименного класса, обсуждавшегосяв главе 6, лишь тем, что в сералете, представленном в листинге 12.10,н, объявлен абст-рактный метод, предназначенный для поиска пользователя. Метод service сервлетасохраняет новый объект, представляющий пользователя, в области видимости сеанса ипередаст управление защищенном ' документу, в данном случае это— /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

<%@ page contentType='text/html; charset=UTF-8' %>

<%@ t a g l i b u r i = ' a p p l i c a t i o n ' prefix='app' %><%@ t a g l i b u r i = ' i l 8 n ' pref ix=' i lBn' %><%@ t a g l i b ur i= ' tokens ' pref ix='tokens ' %><%@ taglib urA='utilities' pref ix= f uti l ' %>

<jsp:useBean id='form' scope='request'class-'beans.app.forms.CreateAccountForm'/>

<font s ize- '5 ' color-'blue'><u><il8n:message key-'createAccount.header.title'/></u>

</fontXp>

<% String error = (String)request.getAttribute["createAccount-form-error");

i f(error !- null) { %><font size='5' colors'red'>

<il8n:message key-'createAccount.error.fix'/></fontxfont size='4' color='red'><%= error %></p></font>

<% request.removeAttribute["createAccount-form-error");} %>

<form action='validate-account.jsp' method='post' ><table width='450'><tr>

<td colspan='2'xfont size='4' colors'blue'><il8n:message key='createAccount.header.personal'/>

< / f o n t x / t d x / t r x t r height=' 10' ></t>

Page 416: Java Server Pages

HTML-формы 421

<trxtd><il8n:message key=' createAccount.field.firstNaroe'/></td>

<tdxinput type=' text' name=' firstName'value='<%= form.getFirstNameU %>'/>

</tdx/tr>

<trxtd><il8n:message key=' createAccount.field.lastName'/></td>

•ctdxinput type-' text' name=' lastName'valuer'<%= form.getLastNameO %>' />

</tdx/tr>

<trxtd><il8n:message key=' createAccount.field.address' /></td>

<tdxinput type='text' name='address' size='39'value='<%- form.getAddress() %>'/>

</td></tr>

<trxtd><i!8n:message key=' createAccount.field.city'/></td>

<td><input type='text' name=' city'value='<*= form.getCity () !>'/>

</tdx/tr>

<trxtd><il8n:message key='cteateAccount.field.state'/></td>

<tdxinput type='text' name=' state'

value='<%= form.getstatet) %>'/></tdx/tr>

<tr><td><il8n:message key=' createAccount. field, country' /x/td><tdxselect name='country'>

<il8n;bean id='germany'

createAccount.option.germany'/>

<option <%=form.getCountrySelectionAttr(germany)%>/><l= germany %>

<il8n:bean id='uk'

key='createAccount.option.unitedKingdom'/>

<option <%=form.getCountrySelectionAttr(uk)%>/><%= uk %>

<il8n:bean id='china' key='createAccount.option.china'/>

<option <%=forra.getCountrySelectionAttr(china)%>/><%= china %>

</selectx/tdx/tr>

<trxtd><il8n:message key=' createAccount. field, phone' /x/td>

Page 417: Java Server Pages

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

<td><input type='text' name=' phone'value='<%= form.getState() %>'/></td>

</trXtr height='20'x/tr>

<trxtd colspan='2'xfont size='1' color='blue'><ilBn:message key='createAccount.header.credit'/x/td>

</fontx/tdx/tr><tr height=' 10' ></tr>

<trxtd><il8n:message

key=' createAccount. field.creditCardType' /x/td><td><select name=' creditCardType' >

<option <%= form.getCreditCardTypeSelectionAttr("Discover") %»

Discovery<option <%= form.getCreditCardTypeSelectionAttr(

"Master Card") %»Master Card

<option <%= form.getCreditCardTypeSelectionAttr(

"Visa") %»Visa

</selectx/tdx/tr>

<trxtd><il8n:message

key=' createAccount. field. creditCardNumber' /x/td><tdxinput type=' text' name=

f creditCardNumber'

value='<%= form.getCreditCardNumber() %>'/></td>

</tr></tr>

<trxtd>

<il8n:message

key='createAccount.field.creditCardExpiration'/></td>•ctdxinput type=' text' name=' creditCardExpiration'

value='<%= form.getCreditCardExpirationO %>'/></td>

</trxtr height='2O'x/tr>

<trxtd colspan='2'xfont size=M' color='blue'><il6n:message

key='createAccount.header.unameAndPwd'/></td></font></tdx/trxtr height=' 10

f x/tr>

<tr><td><il8n:message

key=' createAccount. field.username' /x/td><td><input type=

f text' name='userHame'

value='<%= form.getUserNameO %>'/></td></tdx/tr>

<trxtd><ilSn:message

key=' createAccount. field, pas sword' /x/td><tdxinput type='password' name='password' size=' 8'

value='<S= forro.getPasswordO %>' /></td>

Page 418: Java Server Pages

HTML-формы 423

</tdx/tr>

<trxtd><il8n:message

key='createAccount.field.pwdConfirm' /></td><td><inpirt type='password' name=

fpwdConfirm' size='8'

value='<%= form.getPwdConfirm() %>'/></td>

<tr><td><il8n:message key='createAceount.field.pwdHint'/></td>

<td><input type='text' name='pwdHint'value='<%= form.getPwdHint() %>'/></td>

</td></tr>

<7table><br><input type='submit' value='create account'>

<tokens:token/></form>

JSP-документ, приведенный в листинге 12.11,а, обращается к компоненту beanCreateAceount Form в области видимости запроса. Если форма заполнена некор-ректно, JSP-документ использует этот компонент для заполнения полей так, что поль-зователю не приходится повторно вводить данные.

Затем документ, предстааденный в листинге 12.11,а, проверяет наличие в области ви-димости запроса атрибута с именем createAceount-form-error, соответствующегоошибке. Если атрибут найден, сообщение отображается на Web-странице над формой.

Атрибут act ion регистрационной формы указывает на JSP-документ, которыйсоздает компонент bean CreateAccountForm, и инициализирует его в соответствиис информацией, введенной посредством формы. Этот документ был приведен в лис-тинге 12,Ш,ж. но для удобства рассмотрения он представлен также в листинге 12.11,6.

ЛИСТИНГ 12.11,6. /validate-aecount.j ep

<jsp:useBean id='form' scope='request'

class=' beans.app.forms.CreateAccountForm'/>

<jsp:setProperty name='form' property^'*'/>

<j sp:forward page='/validate-account-action.do'/>

JSP-документ /validate-account.jsp перепалрааляет запрос ресурсу val idate-account-action. do, который отображается в действие, приведенное в листинге 12.11,в.

Листинг 12.11,В. /WEB-lNF/classes/aetions/ValidateAccountAetion

package actions;

import Java,io.lOException;

import javax.servlet.ServletException;import javax,servlet.http.HttpServlet;

Page 419: Java Server Pages

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

import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;

import action.ActionBase;import action.ActionRouter;

import beans.app.forms.CreateAccountForm;

public class ValidateAccountAction extends ActionBaseimplements beans.app.Constants {

public ActionRouter perform(HttpServlet servlet,HttpServletRequest req,HttpServletResponse res)throws ServletException {

CreateAccountForm form = (CreateAccountForm)req.getAttribute("form");

if(form — null) {throw new ServletException("Can't find form"};

;•

String errMsg;boolean errorDetected = false;

if(!form.validate!)) [errMsg - form.getValidationError();errorDetected •= true;req.setAttributef'createAccount-form-error", errMsg);

}return errorDetected ?

new ActionRouter("query-account-page") :new ActionRouter("/new-account-action.do", true, false);

Класс действия, представленный в листинге 12.11,в, извлекает данные формы изобласти видимости запроса и вызывает метод val idate . Если метод возвращает зна-чение false, т.е. если форма некорректна, действие перенаправляет запрос доку-менту query-account-page, который был приведен в листинге 12.11,а.

Компонент bean CreateAccountForm представлен в листинге 12.11,г.

Листинг 12.11,Г. /WEB-INF/classes/beans/app/forms/

Great «Account Form.

package beans.app.forms;

import beans.html.NameElement;import beans.html.OptionsElement;import beans.html.TextElement;import beans.html. ValidatedElement,-

public class CreateAccountForm implements ValidatedElement {private NameElement firstName = new NameElement("First Name");private NameElement lastName = new NameElement("Last Name");private TextElement address = new TextElement (),-

Page 420: Java Server Pages

private TextElement city = newprivate TextElement state = newprivate TextElement phone = newprivate TextElement creditCardNumber = newprivate TextElement creditCardExpiration = newprivate TextElement userName = newprivate TextElement password = newprivate TextElement pwdConfirm = newprivate TextElement pwdHint = new

HTML-формы 425

TextElement()TextElement()TextElement()TextElement()TextElement()TextElement()TextElement()TextElement()TextElement()

private OptionsElementprivate OptionsElement

creditCardType = new OptionsElement ()country = new OptionsElement(]

private String error • "";

public String getFirstName()public String getLastName()public String getAddressOpublic String getCity()public String getState ()public String[] getCountry()public String getPhoneI

( return firstHame.getValue();}return lastName.getValue(); }return address.getValue(); }return city.getValue(); }return state.getValue(); }return country.getValue(); )return phone.getValue{); }

{ return creditCardType.

getValue (); )public String getCreditCardNumber() { return creditCardNumber.

getValue(}; }public String getCreditCardExpiration() (

return creditCardExpiration.getValue();

public String!] getCreditCardType

public String getUserName()public String getPassword()public String getPwdConfirmf)public String getPwdHintf)

{ return userName.getValue(); }{ return password.getValue(); }{ return pwdConfirm.getValue();,{ return pwdHint.getValue();}

public String getCountrySelectionAttr(String s) {return country.selectionAttr(s);

}public String getCreditCardTypeSelectionAttr(String s)

return creditCardType.selectionAttr(s);

public void setFirstName(String s)public void setLastName[String s]public void set&ddress(String s)public void setCity(String s)public void setState(String s)public void setCountry(Stringpublic void setPhone(String s

{ firstName.setValue (s);lastName.setValue(s);address.setValue(s);city.setValue(s);state.setValue(s);country.setValue(s);phone.setValue(s);

[ creditCardType.setValue(s)i ]

( creditCardNumber,setvalue(s); }

public void setCreditCardExpiration(String s) (

creditCardExpiration.setValue (s);

1

{i{I

] s) (

\public void setCreditCardType(StringT] s)

public void setCreditCardNumber(String s)

Page 421: Java Server Pages

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

public void setUserName(String s) ( userName.setvalue(s); )public void setPasswordiString s) ( password.setvalue(s); )public void setPwdConfirm{String s) { pwdConfirm.setValue(s); )public void setPwdHint(String s) ( pwdHint.setvalue(s); }

public boolean validate() {error •= "";

if(!firstName.validate()) (error += firstName.getValidationError();

Iiff ilastName.validate()) f

if {error.length!) > 0)error += "<br>";

error += lastName.getValidationError();

}return error == "";

}public String getValidationError() {

return error;}

}

Размеры представленного выше компонента bean достаточно велики, однакоструктура класса очень проста. Большой объем кода объясняется размерами формы.Компонент bean сохраняет данные формы и предоставляет доступ к ним. Кроме того,в данном компоненте реализован метод v a l i d a t e , который проверяет имя и фами-лию, введенные посредством формы. При необходимости этот метод может бытьрасширен для проверки остальных полей.

Компонент, представленный в листинге 12.11,г, использует для представления по-лей, соответствующих имени и фамилии, экземпляры класса NameElement. КлассNameElement был описан в главе 3. Для удобства рассмотрения код класса приведенв листинге 12.11,д.

ЛИСТИНГ 12.11,Д. /WEB-INF/classes/beans/html/NameElement.Java

package beans.html;

public class NameElement extends TextElement {String error, fieldName;

public NameElement(String fieldName) {this.fieldName = fieldName;

}

public boolean validate() {boolean valid = true;String value = getValuef);

error = " " ;

if{value.length() == 0) {valid = false;

Page 422: Java Server Pages

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

error = fieldName + " must be filled in";)else {

for(int i=0; i < value.length(); ++i) (char с - value.charAt(i);

if(c == ' ' || (c > '0' &ь с < "9')) {valid = false;if <c ==

l M

error = fieldKame + " cannot contain spaces";else

error = fieldName + " cannot contain digits";}

J)return valid;

Ipublic String getValidationErrorO {

return error;

t

Класс NameElement является подклассом TextElement и переопределяет методval idate . Переопределенный метод запрещает использование в имени пробелов ицифр. Дополнительную информацию о классах NameElement и TextElement можнонайти в главе 3.

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

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

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

package actions;

public class QueryAccountAction extends ActionBase {public QueryAccountAction() {

// Действие перенаправляет запрос JSP// с чувствительной формой,hasSensitiveForros •= true;

f

После выполнения действия с чувствительной формой (таковым являетсяQueryAccountAction) набор классов Model 2 создает два идентичных маркера(реализованных в виде строк). Один из этих маркеров помещается в область видимо-сти сеанса, а другой — в область видимости запроса. Затем перед выполнением чувст-

Page 423: Java Server Pages

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

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

Класс NewAccountAction, предназначенный для создания новой учетной записи,представляет собой чувствительное действие, в чем можно убедиться, глядя на при-веденный ниже фрагмент кода.

package actions;

public class NewAccountAction extends ActionBaseimplements beans.app.Constants {

public NewAccountAction() {isSensitive - true; II Чувствительное действие

\

I

В чувствительной форме, фрагмент которой приведен в листинге 12.11,е, приме-нен пользовательский дескриптор tokens:token, который копирует маркер, суще-ствующий в области видимости запроса, в запрос, генерируемый в результате активи-зации формы.

Листинг 12.11 ,е. /WEB-INF/jap/creat*Account/content. jep (фрагмент)

<%@ page contentType-'text/html; charset=UTF-8' %>. . .<%@ taglib uri=

ftokens' prefix='tokens' %>

<form action='validate-account.jsp' method='post' >

<tokens:token/></form>

SSLПомимо противодействия повторной активизации чувствительных форм, Web-

приложение должно использовать защищенный канал (SSL) для передачи конфиден-циальной информации {например, номеров платежных карт).

При использовании SSL надо, во-первых, иметь в виду, что не все контейнерысервлетов изначально поддерживают SSL. К счастью, добавить средства поддержкиSSL достаточно просто. Для сервера Tomcat 3.2 final процедура включения SSL описа-на в документе $TOMCAT_HOME/doc/tomcat-ssl-howto.html.

Во-вторых, следует указать ресурсы, для которых требуется SSL {как правило, это JSP-документы). Такие ресурсы описываются в файле web.xml. Например, в рассматривае-мом приложении фрагмент файла web.xml, в котором указано, что при создании учет-ной записи должен использоваться защищенный канал, имеет следующий вид:

Page 424: Java Server Pages

XML и DOM 429

<security-constraint><web-resource-collection>

<web-reaource-name>Credit Card Page</web-resource-naine>

<url-pattern>/WEB-INF/jsp/createAccount/content.jsp

</url-pattern>

</web-resource-collection>

<user-data-constraint><transport-guarantee>CONFIDENTIAL</transpoi:t-guarantee>

</user-data-constraint></security-constraint>

Здесь указано, что SSL применяется для обращения к ресурсу /WEB-INF/jsp/c r e a t e A c c o u n t / c o n t e n t . j sp. Ограничения, связанные с обеспечением безопасно-сти, подробно рассматриваются в главе 9.

К сожалению, на момент написания данной книги и в Tomcat S.2 final, и в Tomcat4.0 средства защиты работали некорректно. Вероятно, к тому времени, когда вы буде-те читать этот текст, ситуация для Tomcat 4.0 изменится к лучшему.

X M L M D O MЧасто, создавая Web-приложения, бывает целесообразно хранить данные в фор-

мате XML. Во-первых, в этом случае информация может быть преобразована средст-вами Document Object Model (DOM) и представлена в виде стандартной структуры,для поддержки которой существуют самые разнообразные инструментальные средст-ва. Во-вторых, XML становится стандартом de facto для передачи данных между раз-личными бизнес-приложениями.

Вопросы совместного использования XML и JSP были рассмотрены в главе 11.Здесь же мы лишь обсудим возможности применения XML в приложении, реализую-щем интерактивный магазин,

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

Листинг 12.12,a. /WEB-INF/ jep/atoref r on t/con ten t . jep (DOM-версия)

page contentType='text/html; charset-UTF-8' *>

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

taglib uri='dom' prefix='dom' *><%@ taglib uri-'html' preCix='html' %>

<font size-M' color-'blue'><il8n:message base-'app' key='storefront.form.title'/>

</fontxp>

<dom:parse id='inventory' scope='application'>

Page 425: Java Server Pages

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

<%@ include file='inventory-to-xml.j sp' %></dom:parse>

<table border='l' cellPadding='3'>

<th><il8n:message base='app'key-'storefront.table.header-picture' /></th>

<thxil8n:message base-'app'key-'storefront.table.header.item'/></th>

<th><il8n:megsage base='app'key=' storefront.table.header.description'/></th>

<thxil8n:message base=' app'key='storefront.table.header.price'/></th>

<thxil8n:message base=' app'key='storefront.table.header.addToCart'/></th>

<% String currentltem = null, currentSku = null; %>

<dom:iterate node='<%=inventory.getDocumentElement()&>' id='item' ><dom:iterate node='<%= item %>' id='itemField'>

<dom:ifNodeNameEquals node='<%= itemField !>' names='SKU'><dom:elementValue id='name' element='<%= itemField %>'/><% currentSku » name; l><trxtd>

<img src='<%= "graphics/fruit/" + name.trim() + ".jpg" %>'/>

</td></dom:ifNodeNameEquals>

<dom:ifWodeNameEquals node='<%= itemField %>' names='NAME'><dom:elementValue id='name' element='<%= itemField %>'/><% currentltem = name; %>

<td><%= name %></td><td>

<il8n:message key='<%=name + ",description"%>'/></td>

</dom:ifNodeMameEquals>

<dom:ifNodeHameEquals node='<%= itemField %>' names='PRICE'><dom:elementValue id='price' element-'<%= itemField %>'/><td>$Snbsp;<%= price %>bnbsp;/lb.</td><td>

<form action='add-selection-to-cart-action.do'><html:links name='<£= currentSku + "-" +

currentltem + "-" + price %>'><option value-<option value=<option value=<option value=<option value=<option value=<option value=<option value=<option value=<option value=

0.00'>0.00</option>1.00'>l.00</option>1.50'>1.50</option>2.00'>2.00</option>2.50'>2.50</option>3.00'>3.00</option>3.50'>3.50</option>4 . 0 0 4 .00</option>4.50'>4.50</option>5.00'>5.00</option>

Page 426: Java Server Pages

XML и DOM 431

<option valuer's.50'>5.50</option></html:links></form>

</td></dom:ifNodeNameEquals>

</dom:iterate></dom:iterate>

</table>

В JSP-документе, приведенном в листинге 12.12,а, для создания DOM-документа,представляющего перечень товаров, применяются пользовательские дескрипторы.Дескриптор dom:parse, присутствующий в листинге 12.12та, интерпретирует содер-жащиеся в нем данные как XML-код. Содержимое генерируется с помощью файлаinventory-to-xml. j sp, код которого представлен в листинге 12.12,6.

Листинг 12.12,6. /WEB-lHP/jsp/storefront/invantory-to-xml.jsp

<%@ tagl ib url='database' prefix='database' %>

<database:query id='inventory' scope='session'>SELECT * FROM Inventory

</database:query>

<?xml version="1.0" encoding="ISO-8859-l"?><FRUITS>

<database:rows quecy='inventory'><ITEM><database:columns query='inventory' columnName='name'

columnValue=' value' ><%= "<" + name + ">" %>

<%= value %><%= »</•• + n a m e + »>•- fc>

</database:columns></ITEM>

</database:rows></FRUITS>

<database:release query='inventory'/>

JSP-файл, код которого приведен в листинге 12.12,6, содержит пользовательскиедескрипторы, предназначенные для работы с базой данных. Эти дескрипторы былирассмотрены в главе 10. Представленный выше JSP-файл генерирует XML-данные,которые обрабатываются дескриптором dom: p a r s e (листинг 12.12,а).

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

Page 427: Java Server Pages

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

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

ли использованы средства, описанные на протяжении всей книги. Следует заметить,что некоторые особенности реализации рассмотрены не были, Если вы хотите де-тально ознакомиться с данным приложением, вам следует скопировать его код, обра-тившись по адресу www.phptr.com/advjsp, и внимательно изучить его.

Page 428: Java Server Pages

ФИЛЬТРЫСЕРВЛЕТОВ

Page 429: Java Server Pages

Приложение

Воснову данного приложения положен проект спецификации Servlet 2.3. На мо-мент написания данной книги окончательный вариант Servlet 2.3 еще не суще-ствовал, поэтому, когда вы будете читать этот текст, сведения, приведенные

здесь, могут не соответствовать спецификации. Коды, содержащиеся в данном при-ложении, были проверены с использованием контейнера Resinl.3; информацию оконтейнере Resin можно найти на сервере www. caucho. com.

Фильтры сервлетов, описанные в спецификации Servlet 2.3, позволяют устранитьодин из главных недостатков сервлетов и JSP — невозможность фильтрации выход-ных данных. Это ограничение затрудняло реализацию таких средств, как регистра-ция, аутентификация, XSLT-преобразование и т.д.

До появления спецификации Septet 2.3 единственной возможностью организоватьфильтрацию было создание пользовательских дескрипторов JSP. Так, например, нижепоказан пример применения дескриптора, который заставляет пользователя пройтирегистрацию. (Подробно дескриптор enf orceLogin был рассмотрен в главе 9.)

< % — В начале JSP-документа —%><%@ taglib uri-'/WEB-lNF/tlds/seeurity.tld'

prefix='authenticate' £><authenticate:enforceLogin loginPage='login.j sp'/>

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

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

Page 430: Java Server Pages

436 Приложение А. Фильтры сервлетов

Контейнер сервлетов Цепочка фильтров Сервлет

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

—*- doRtterfServletReqjest, ServerResponce, FilterChain)

—«*• service (ServletRequest, ServerResponce)

Рис. А. 1. Использование фильтров сервлетов

Бели фильтры не используются, контейнер сервлетов непосредственно вызываетметод s e r v i c e сервлета. При наличии фильтров контейнер направляет объекты,представляющие запрос и ответ, а также цепочку первому фильтру, связанному ссервлетом. Это фильтр выполняет необходимую обработку запроса, при необходимо-сти формирует ответ или изменяет содержимое полей заголовка. Затем фильтр пере-дает запрос следующему фильтру в цепочке. Таким образом запрос достигает послед-него, специального фильтра, предоставляемого контейнером сервлетов, который вы-зывает метод s e r v i c e сервлета.

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

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

1. Объявить фильтры и их отражение в дескрипторе доставки.

2. Реализовать фильтр.

Пример фильтраВ данном разделе мы рассмотрим фильтр аутентификации, который проверяет,

принадлежит ли пользователь некоторой роли. Если принадлежность роли установ-лена, фильтр вызывает следующий фильтр в цепочке и в конечном итоге запрос пе-редается JSP-доку менту. Если проверка дает отрицательный результат, выводится со-общение об ошибке. На рис. А.2 показано сообщение, отображаемое в том случае, ко-гда пользователь не принадлежит роли.

i f i l l e l l t £l«w Fffforltes Toots tttt>

http: //localhatt: SOSO/Fiher*/• - • • :

You ace not authorized to access this resource.

Й о ™

Рис. А.2. Фильтр аутентификации

Page 431: Java Server Pages

Пример фильтра 437

При реализации фильтра аутентификации надо в первую очередь задать фильтр иего отражение в дескрипторе доставки приложения. Код дескриптора доставки пока-зан в листинге АЛ.

ЛистингА.1.

<?xml version="1.0" encoding-"ISO-8859-l"?>

<!DOCTYPE web-appPUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://Java.sun.com/j2ee/dtds/web-app_2_3.dtd">

<web-app><filter>

<filter-name>Authenticate Filter</filter-name><filter-class>filters.AuthenticateFilter</filter-class>

</filter>

<filter-mapping><fiIter-name>Authenticate Filter</filter-name><url-pattern>*.jsp</url-pattern>

</filter-mapping>

<welcome-<welcome-file>tndex.jsp</welcome-

</welcome-file-list>

</web-app>

В дескрипторе доставки, приведенном выше, заданы имя и класс фильтра аутен-тификации. Здесь же указано отражение, связывающее фильтр со всеми JSP-доку-ментами.

Класс, посредством которого реализуется фильтр аутентификации, приведен влистинге А.2.

ЛИСТИНГ А.2. /WEB-INF/clasэез/filters/AuthenticateFilter.Java

package filters;

import Java.io.PrintWriter;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.Filter;import javax.servlet.FilterConfig;import javax.servlet.FilterChain;

import javax.servlet.http.HttpServletRequest;

public class AuthenticateFilter implements Filter {private FilterConfig config;

public void setFilterConfig(FilterConfig config) {

Page 432: Java Server Pages

438 Приложение А. Фильтры сервлетов

this.config = config;}public FilterConfig getFilterConfig() {

return config;

public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)throws java.io.IOException,

javax.servlet.ServletException {if(((HttpServletRequest)request).isUserlnRole("resin")) {

chain.doFilter(request, response);}else {

response.getWriter().write("You are not authorized " +"to access this resource.");

В классе, приведенном в листинге А.2, реализованы три метода, которые объявле-ны в интерфейсе javax . serv le t . F i l t e r . Метод doFi l te r проверяет роль и пере-направляет запрос, вызывая метод Fi l te rChain .doFi l te r . Перенаправление вы-полняется только в том случае, если пользователь принадлежит роли resin. Для лю-бой другой роли метод doFi l te r выводит сообщение об ошибке.

Замечание. В процессе подготовки данной книги к публикации выяснилось, чтонабор методов фильтров будет изменен. Метод setFi l terConfig, объявленный винтерфейсе F i l t e r , заменен методом i n i t , кроме того, в интерфейсе добавлен ме-тод destroy и удален метод getFilterConfig.

РезюмеФильтры сервлетов являются мощным средством, дополняющим Servlet API.

Фильтры могут быть связаны с сервлетом или с классом ресурсов, задаваемых посред-ством шаблона URL, что позволяет применять один или несколько фильтров к наборудокументов, например к JSP-, HTML- или XML-файлам.

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

Page 433: Java Server Pages

Научно-популярное издание

Дэвид М. Гери

Java Server Pages.Библиотека профессионала

Литературный редактор

Верстка

Художественный редактор

Корректоры

ИЛ, Попова

А .В, Плаксюк

С.А. ЧерУиткозинский

JI.A. Гордиенхо, О.В. Mutuyi

Т.А. Корзун

Издательский дом "Вильяме",101509, Москва, ул. Лесная, д. 43, стр. 1.

Изд, лиц. ЛР N° 090230 от 23.06.99Госкомитета РФ по печати.

Подписано в печать 26.04.2002. Формат 70x100/16.Гарнитура NewBaskerville. Печать офсетная.

Усл. печ. л. 33,6. Уч.-изд. л. 20,00.Тираж 3000 экз. Заказ № 424.

Отпечатано с диапозитивов в ФГУП "Печатный двор"Министерства РФ по делам печати,

телерадиовещания и средств массовых коммуникаций.197110, Санкт-Петербург, Чкаловский пр., 15.

Page 434: Java Server Pages

• Файл взят с сайта • http://www.natahaus.ru/ • • где есть ещё множество интересных и редких книг. • • Данный файл представлен исключительно в • ознакомительных целях. • • Уважаемый читатель! • Если вы скопируете данный файл, • Вы должны незамедлительно удалить его • сразу после ознакомления с содержанием. • Копируя и сохраняя его Вы принимаете на себя всю • ответственность, согласно действующему • международному законодательству . • Все авторские права на данный файл • сохраняются за правообладателем. • Любое коммерческое и иное использование • кроме предварительного ознакомления запрещено. • • Публикация данного документа не преследует за • собой никакой коммерческой выгоды. Но такие документы

Page 435: Java Server Pages

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