У вас вопросы?
У нас ответы:) SamZan.net

документов Создание компонентов ben на основе XMLданных

Работа добавлена на сайт samzan.net:

Поможем написать учебную работу

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

Предоплата всего

от 25%

Подписываем

договор

Выберите тип работы:

Скидка 25% при заказе до 26.12.2024

XML

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

Генерация ХМL-документов.

Создание компонентов bean на основе XML-данных.

Обработка сгенерированных XML-данных.

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

SimpleAPIforXML(SAX).

SAX-разбор с применением пользовательских
дескрипторов.

Document Object Model (DOM).

Пользовательские дескрипторы для DOM-разбора.

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

Совместное использование XSLT и JSP.

Генерация HTML-кода путем XSLT-преобразования
с помощью пользовательского дескриптора.

Применение XSLT для генерации JSP-кода
на этапе компиляции.

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

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 будут рассмотрены далее в данной главе.

310      Глава 11. XML

Помимо генерации и разбора XML-данных, разработчикам часто приходится решать задачи преобразования XML-n «формации в другой формат; например, может возникнуть необходимость представить данные в виде 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 можно скопировать, обратившись к узлу http: //www. Java, aun.com, a сервер Tomcat находится по адресу http: //jakarta.apache.org/tomcat/index.   html.

Xerces и Xalan — это соответственно программа XML-разбора и XSLT-процессор, Дополнительную информацию о данных продуктах можно найти по адресу http: // xml.apache.org/xalan/index.html.

Генерация 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,util.Date ()   %> </date> </document>

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

Внешний вид Web-страницы, которую генерирует код, содержащийся в листинге 11.1, показан на рис. 11.2.

Единственным видом динамического содержимого на данной Web-странице является информация о дате. Как видно из рис. 11.2, выражение вычисляется, и результат включается в сгенерированный XML-документ.

Генерация XML-данных с помощью компонентов bean

На данный momceit вы уже обладаете знаниями, достаточными для того, чтобы создать XML-flOKy\fCHT. содержащий JSP-скриптлеты п пользовательские дескрипторы, предназначенные для генерации динамического содержимого. В этом разделе мы рассмотрим JSP-документ, который генерирует динамическое содержимое с помощью компонентов bean. Внешний вид такого документа показан ни рис. 11.3.

312      Глава 11. XML

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

Листинг 1.2,a. /inventory.jap

<html>

<head>

<title>Inventory</title>

</head>

<body>

<table cellspacing="15">

<th><u>Item</u></th><th><u>Description</u></th><th><u>Price</u></th>

     

        

        <tr>

           <td>Antique broach</td>

           <td>

              from the early 1700's

           </td>

           <td>1238.99</td>

        </tr>

     

        

        <tr>

           <td>Gumby and Pokey</td>

           <td>

              pliable action figures

           </td>

           <td>2.99</td>

        </tr>

     

        

        <tr>

           <td>Underwood typewriter</td>

           <td>

              1942, excellent shape

           </td>

           <td>299.95</td>

        </tr>

     

        

        <tr>

           <td>Lawnmower</td>

           <td>

              One of the first gas-engine mowers

           </td>

           <td>499.99</td>

        </tr>

     

        

        <tr>

           <td>Superman comic book</td>

           <td>

              Series 1A7, quite rare

           </td>

           <td>2.99</td>

        </tr>

     

        

        <tr>

           <td>Escher original drawing</td>

           <td>

              signed in blue ink

           </td>

           <td>3459.95</td>

        </tr>

     

        

        <tr>

           <td>Roman helmet</td>

           <td>

              estimated 200 B.C.

           </td>

           <td>95.99</td>

        </tr>

     

        <tr>

           <td>School desk</td>

           <td>

              made in 1949, with inkwell

           </td>

           <td>995.99</td>

        </tr>

     

  </table>

</body>

</html>   

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

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

те, что JSP-документ отвечает за XML-представление объектов. Вопросы генерации объектами собственного XML-кода будут рассмотрены далее.

Классы Inventory и Item не содержат ничего примечательного и приведены здесь только для того, чтобы приложение выглядело завершенным.

Листинг 11.2,6. /WEB-INF/classes/beans/Inventory. Java

package   beans;

import   java.util.Iterator; import  java.util.Vector;

public class   Inventory implements   Java.io.Serial!zable   { 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,в. /WEB-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 getPrice() { return price; }

public String getDescription{) { return description; }

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

314      Глава 11. XML

Совет

При генерации XML-документов следует указывать тип содержимого text/xral

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

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

<xslt:apply xsl='date.xsl'>

<%@ include file='genXML.jsp' %> </xslt:apply>

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

Компоненты bean, генерирующие XML-код

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

В качестве альтернативы непосредственному созданию XML-кода с помощью JSP-документа можно использовать компоненты bean, генерирующие собственное XML-представление, Достоинством таких компонентов является тот факт, что они сами ответственны за созданный ими XML-код, В качестве примера рассмотрим компоненты Inventory и Item, представленные в листингах 11.3,а и 11.3,6. Вместо того чтобы создавать XML-данные средствами JSP, как это показано в листинге 11.2,а, классы Inventory и Item сами создают требуемый код.

Листинг 11.3,3. /WEB-INF/classea/beans/Inventory.Java

package beans;

import java.util.Iterator;

import java.util.Vector;

public class Inventory implements java.io.Serializable {

  private Vector items = new Vector();

  public Inventory() {

     items.addElement(new Item("Antique broach",

                    "from the early 1700's",

                    (float)1238.99));

     items.addElement(new Item("Gumby and Pokey",

                    "pliable action figures",

                    (float)2.99));

     items.addElement(new Item("Underwood typewriter",

                    "1942, excellent shape",

                    (float)299.95));

     items.addElement(new Item("Lawnmower",

                    "One of the first gas-engine mowers",

                    (float)499.99));

     items.addElement(new Item("Superman comic book",

                    "Series 1A7, quite rare",

                    (float)2.99));

     items.addElement(new Item("Escher original drawing",

                    "signed in blue ink",

                    (float)3459.95));

     items.addElement(new Item("Roman helmet",

                    "estimated 200 B.C.",

                    (float)95.99));

     items.addElement(new Item("School desk",

                    "made in 1949, with inkwell",

                    (float)995.99));

  }

  public Iterator getItems() {

     return items.iterator();   

  }

}   

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

Листинг 11.3,6. /WEB-INF/classes/beans/Item, Java

package beans;

public class Item implements java.io.Serializable {

  private String description, name;

  private float price;

  public Item(String name, String description, float price) {

 this.description = description;

 this.name = name;

 this.price = price;

  }

  public String getName()        { return name; }

  public float  getPrice()       { return price; }

  public String getDescription() { return description; }

}   

Создание компонентов bean на основе XML-данных

Java и XML сочетаются друг с другом настолько хорошо, что разработчик может без труда отобразить XML-данные в компоненты bean, а также реализовать обратное отображение. На момент написания данной книги приближался к завершению проект Adelard компании Sun, предназначенный для генерации компонентов bean на ба-

316      Глава 11. XML

зс XML. Дополнительную информацию о проекте Adelard и XML Productivity Kit можно найти по следующим UR.L:

http://Java.sun.com/xml/

http://ww.alphaworks.ibra.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>

<i while(it.hasNext())   {   %>

<% item = (beans.Item)it.next О; %>

<name><*=  item.getName(j   %></name> <description>

<%=  item.getDescriptionO   %>
</description>
/"

<price><%- item.getPriceO   %></price> </item>

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

</inventory>

:streamToFile>

Класс поддержки дескриптора util: streamToFile представлен в листинге 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 javax.servlet.jsp.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.close();

     }

     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,

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 можно найти по адресу http: //www .megginson. com/SAX.

Средства разбора SAX работают быстро, поскольку в отличие от Document Object Model (DOM), они не строят в памяти дерево, представляющее структуру XML-документа.

При работе SAX используется обратный вызов; средства разбора обращаются к объектам, которые называются обработчиками (handler), оповещая их о нахождении компонентов — элементов, атрибутов и текста. Подход, используемый при разборе средствами SAX, напоминает модель делегирования событий, описанную в главе 7.

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

org.xml.sax.ContentHandler

org.xml.sax.DTDHandlec

org.xml.sax.ErrorHandler

Процесс работы с использованием SAX включает следующие пять этапов.

  1.  Реализация одного или нескольких обработчиков.

Создание объекта чтения XML {org. xml. sax. XMLReader).

Связывание обработчиков, построенных на первом этапе, с объектом чтения,
созданном на втором этапе.

Создание источника входных данных (org. xml .sax. Input Sour se).

Вызов метода XMLReader .parse; при вызове этому методу передается источ
ник входных данных, созданный на этапе 4.

В процессе разбора объект чтения XML вызывает методы обработчиков; например, в интерфейсе ErrorHandler объявлен метод warning, который вызывается тогда, когда SAX генерирует предупреждение.

Часто SAX-приложен и я реализуют все три интерфейса обработчиков, а это означает необходимость написания кода 19 методов. В качестве альтернативного подхода можно использовать класс DefaultHandler, содержащийся в пакете org.xml. sax. helpers. В этом классе реализованы все три указанных выше интерфейса; объявленные в этих интерфейсах методы не выполняют никаких действий. Создавая приложение, разработчик переопределяет требуемые методы. В листинге 11.5,а приведен код компонента bean, в котором инкапсулированы средства SAX-разбора с использованием Apache Xerces.

/ В SAX2 также определен дополнительный интерфейс EnlityReference, который в данной книге

не jmif-vfinijiiiiitmnm. - Прим. авт.

Разбор 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);

     xmlReader.parse(new InputSource(fileReader));

     return vector;

  }

  public void startElement(String uri, String localName,

                           String qName, Attributes attrs) {

     currentElement = new SAXElement(uri,localName,qName,attrs);

     vector.addElement(currentElement);

     elementText = new String();

  }

  public void characters(char[] chars, int start, int length) {

     if(currentElement != null && elementText != null) {

        String value = new String(chars, start, length);

        elementText += value;

     }

  }

  public void endElement(String uri, String localName,

                         String qName) {

     if(currentElement != null && elementText != null)

        currentElement.setValue(elementText.trim());

     currentElement = null;

  }

}  

Класс SAXParserBean является подклассом DefaultKandler и реализует все три интерфейса SAX-обработчиков, поэтому первое из перечисленных выше действий выполняется автоматически.

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/bearis/icml/saac/SAXElemeiit. 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 setValue(String value) {

     this.value = value;

  }

  public String getNamespace()      { return namespace; }

  public String getLocalName()      { return localName; }

  public String getQualifiedName()  { return qualifiedName; }

  public String getValue()          { return value; }

  public Attributes getAttributes() { return attributes; }

}   

Экземпляры класса SAXElement содержат информацию об XML-элементах, которая доступна другим объектам только для чтения. Экземпляр класса SAXElement создается и заполняется данными в теле метода ContentHandler. startElement (см. листинг 11.5,а).

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

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

Содержимое XML-файла, разбор которого осуществляет приложение, показано в листинге II,5,в.

ЛИСТИНГ 11.5,в. /booklnvantory .ami

<?xml version="1.0" encoding="ISO-8859-1"?>

<inventory>

<book>

    <ISBN>0393040009</ISBN>

    <title>Points Unknown</title>

    <price>$23.96</price>

</book>

<book>

    <ISBN>1579550088</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>    

322      Глава 11. XML

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

Листинг 11.5,г. /test^sax. jsp

<html><head><title>SAX Example</title>

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

</head>

<body>

<font size='5'>

  Inventory as of <%= new java.util.Date() %>

</font><p>

<table cellspacing='15'>

  <th><u>Item</u></th>

  <th><u>Description</u></th>

  <th><u>Price</u></th>

  <sax:iterateElements id='element'

                  xmlFile='f:/books/jsp/src/xml/inventory.xml'>

  <% if(element.getLocalName().equals("item")) { %>

     <tr>

  <% } %>

  <% if(element.getLocalName().equals("description")) { %>

     <td><%= element.getValue() %></td>

  <% } %>

  <% if(element.getLocalName().equals("name")) { %>

     <td><%= element.getValue() %></td>

  <% } %>

  <% if(element.getLocalName().equals("price")) { %>

     <td><%= element.getValue() %></td></tr>

  <% } %>

  </sax:iterateElements>

</table>

</body>

</html>   

JSP-flOK)pMCHT, представленный в листинге, осуществляет перебор элементов (SAXElement}, которые были созданы в процессе разбора компонентом SAXParserBean. Имя дескриптора возвращает метод SAXElement .getLocalName, а значение дескриптора позволяет определить метод SAXElement. getValue.

Разбор 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'%>

</head>

<body>

<% long time = System.currentTimeMillis(); %>

<font size='5'>

  Inventory as of <%= new java.util.Date() %>

</font><p>

<table cellspacing='15'>

  <th><u>Item</u></th>

  <th><u>Description</u></th>

  <th><u>Price</u></th>

  <sax:iterateElements id='element'

                  xmlFile='f:/books/jsp/src/xml/inventory.xml'>

     <sax:ifElementNameEquals element='<%= element %>'

                                names='item'>

        <tr>

     </sax:ifElementNameEquals>

     <sax:ifElementNameEquals element='<%= element %>'

                                names='name description'>

        <td><%= element.getValue() %></td>

     </sax:ifElementNameEquals>

     <sax:ifElementNameEquals element='<%= element %>'

                                names='price'>

        <td><%= element.getValue() %></td></tr>

     </sax:ifElementNameEquals>

  </sax:iterateElements>

</table>

<%= System.currentTimeMillis() - time %>

</body>

</html>   

Дескриптор iterateEleraents использует класс SAXParserBean, приведенный в листинге 11.5,а, для разбора XML-документа и перебора элементов, a ifElementNameEquals включает тело дескриптора в зависимости от имени текущего элемента.

Дескриптор iterateElements создает для текущего элемента переменную сценария; имя этой переменной определяется атрибутом id данного дескриптора. Таким образом, каждый из XML-элементов, содержащихся в файле inventory.xml, становится доступным посредством переменной сценария element.

Переменная сценария, сгенерированная дескриптором iterateEleraents, передается дескриптору ifElementNameEquals. Этот дескриптор включает содержащиеся в нем данные в том случае, если имя элемента совпадает с именем, указанным посредством атрибута names.

На рис. 11.5 показано более сложное приложение, которое использует рассмотренные выше дескрипторы для просмотра файла описания библиотеки дескрипторов (TLD — Tag Library Descriptor).

324     Глава 11. XML

 

Листинг 11.7,a. database. tld

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE taglib PUBLIC

 "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"

 "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">

<taglib>

  <tlibversion>1.0</tlibversion>

  <jspversion>1.1</jspversion>

  <shortname>

     Sun Microsystems Press Database Tag Library

  </shortname>

  <info>

     You can execute database queries and iterate over the

     results with the tags in this library. This library also

     includes tags for prepared statements and transactions.

  </info>

  <tag>

     <name>transaction</name>

     <tagclass>tags.jdbc.TransactionTag</tagclass>

     <bodycontent>tagdependent</bodycontent>

     <attribute>

        <name>file</name>

        <required>false</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

  </tag>   

  <tag>

     <name>prepareStatement</name>

     <tagclass>tags.jdbc.PrepareStatementTag</tagclass>

     <bodycontent>tagdependent</bodycontent>

     <attribute>

        <name>scope</name>

        <required>false</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

     <attribute>

        <name>id</name>

        <required>true</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

  </tag>   

  <tag>

     <name>executePreparedStatement</name>

     <tagclass>tags.jdbc.ExecutePreparedStatementTag</tagclass>

     <bodycontent>None</bodycontent>

     <attribute>

        <name>id</name>

        <required>true</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

     <attribute>

        <name>scope</name>

        <required>false</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

     <attribute>

        <name>variables</name>

        <required>true</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

  </tag>   

  <tag>   

     <name>query</name>

     <tagclass>tags.jdbc.QueryTag</tagclass>

     <bodycontent>tagdependent</bodycontent>

     <attribute>

        <name>id</name>

        <required>true</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

     <attribute>

        <name>scope</name>

        <required>false</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

     <attribute>

        <name>update</name>

        <required>false</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

  </tag>

  <tag>   

     <name>rows</name>

     <tagclass>tags.jdbc.RowsTag</tagclass>

     <bodycontent>JSP</bodycontent>

     <attribute>

        <name>query</name>

        <required>true</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

     <attribute>

        <name>startRow</name>

        <required>false</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

     <attribute>

        <name>endRow</name>

        <required>false</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

  </tag>

  <tag>   

     <name>columnNames</name>

     <tagclass>tags.jdbc.ColumnNamesTag</tagclass>

     <teiclass>tags.jdbc.ColumnNamesTagInfo</teiclass>

     <bodycontent>JSP</bodycontent>

     <attribute>

        <name>query</name>

        <required>true</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

     <attribute>

        <name>id</name>

        <required>true</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

  </tag>

  <tag>   

     <name>columns</name>

     <tagclass>tags.jdbc.ColumnsTag</tagclass>

     <teiclass>tags.jdbc.ColumnsTagInfo</teiclass>

     <bodycontent>JSP</bodycontent>

     <attribute>

        <name>query</name>

        <required>true</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

     <attribute>

        <name>id</name>

        <required>true</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

  </tag>

  <tag>   

     <name>release</name>

     <tagclass>tags.jdbc.ReleaseTag</tagclass>

     <bodycontent>JSP</bodycontent>

     <attribute>

        <name>query</name>

        <required>true</required>

        <rtexprvalue>true</rtexprvalue>

     </attribute>

  </tag>

</taglib>        

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

Листинг 11.7,6. /test

<html><head><title>SAX Example</title>

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

  <%@ page import='tags.util.IteratorTag' %>

  <% boolean inAttributes = false, bodyContentSpecified = false;

     String attributeData = null; %>

</head>

<body>

<%-- Display the tag lib's shortname and info --%>

<sax:iterateElements id='element'

               xmlFile='f:/books/jsp/src/xml/database.tld'>

  <sax:ifElementNameEquals element='<%=element%>'

                             names='shortname'>

     <font size='5'><%= element.getValue() %></font><p>

  </sax:ifElementNameEquals>

  <sax:ifElementNameEquals element='<%=element%>'

                             names='info'>

     <p><font size='4'><%= element.getValue() %></font></p>

  </sax:ifElementNameEquals>

</sax:iterateElements>

<%-- Display a table with the tag lib's name, tagclass,

    bodycontent, and attributes --%>

<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'>

           <tr><td><%= element.getValue() %></td>

        </sax:ifElementNameEquals>

        

        <sax:ifElementNameEquals element='<%=element%>'

                                   names='tagclass'>

           <td><%= element.getValue() %></td>

        </sax:ifElementNameEquals>

        <sax:ifElementNameEquals element='<%=element%>'

                                   names='bodycontent'>

           <td><%= element.getValue() %></td>

           <% bodyContentSpecified = true; %>

          </sax:ifElementNameEquals>

        <sax:ifElementNameEquals element='<%=element%>'

                                   names='attribute'>

           <% inAttributes = true;

              attributeData = new String();

     

              if( ! bodyContentSpecified) { %>

                 <td>JSP</td>   

           <% }

           bodyContentSpecified = false; %>

        </sax:ifElementNameEquals>

     <% }

     else {   // in attributes %>

        <sax:ifElementNameEquals element='<%=element%>'

                                   names='name'>

           <% attributeData += element.getValue() + " "; %>

        </sax:ifElementNameEquals>

        <sax:ifElementNameNotEquals element='<%=element%>'

                     names='attribute name required rtexprvalue'>

           <td><%= attributeData %></td>

           <% inAttributes = false; %>

        </sax:ifElementNameNotEquals>

  <%   } %>

  </sax:iterateElements>

  <td><%= attributeData %></td></tr>

</table></p>

</body>

</html>     

326  Глава 11. XML

JSP-документ, показанный в листинге 11.7,6, дважды перебирает элементы файла database. tld; один раз — для обработки элементов shortname и info, а второй — для построения HTML-таблицы. Однако разбор XML-файла (database . tld) выполняется только один раз.

Тело iterateElements можно сравнить с оператором switch, реализованным средствами пользовательских дескрипторов; особенности обработки элемента зависят от его имени. Например, для отображения значения элемента shortname используется размер шрифта, равный 5.

В листинге 11.7,в приведен код класса поддержки дескриптора iterateElements.

Листинг 11.7,в. /WEB-INF/classes/tags/xml/sax/SAXParserTag. Java

package tags.xml.sax;

import java.util.Collection;

import java.util.Vector;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

import beans.xml.sax.SAXParserBean;

import tags.util.IteratorTag;

import org.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.PAGE_SCOPE);

     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("IO Exception: " +

                                   ex.toString());

        }

        catch(SAXException ex) {

              throw new JspException("SAX Exception" +

                                   ex.toString());

        }

     }

     return collection;

  }

}   

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

Класс SAXParserTag, представленный в листинге 11.7,в, является подклассом класса IteratorTag, который был рассмотрен и главе 2. Дескриптор iterate-Elements создает две переменные сценария, соответствующие текущему и следующему объектам в наборе.

Класс SAXParserTag использует компонент, предназначенный для SAX-разбора {он был рассмотрен ранее в этой главе). Метод parse компонента возвращает набор, содержащий все элементы XML-файла. Этот набор устанавливается в теле метода SAXParserTag. doStartTag; перебор организуется методами суперкласса.

328      Глава 11. XML

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

Для определения переменных сценария используется подкласс класса TagExtra-Info, который рассматривался в главе 2. Код класса SAXParserTaglnfo приведен в листинге 11.7,г.

Листинг 11.7,г. /WEB-INF/classes/tage/xnu/saVSAXParserTagXnf о. 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 SAXParserTagInfo extends TagExtraInfo {

  public VariableInfo[] getVariableInfo(TagData data) {

     return new VariableInfo[] {

        new VariableInfo(data.getId(),

                         "beans.xml.sax.SAXElement",

                         true, VariableInfo.NESTED),

        new VariableInfo("next", // scripting var's name

              "beans.xml.sax.SAXElement", // variable's type

              true, // whether variable is created

              VariableInfo.NESTED) // scope

     };

  }

}    

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

Класс поддержки дескриптора sax: ifEleraentNameEquals приведен в листинге П.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;

  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 {

        while(!nameFound)

           nameFound = elementName.equals(tok.nextToken());

     }

     catch(NoSuchElementException ex) {

        // no more tokens

     }

     return nameFound ? EVAL_BODY_INCLUDE : SKIP_BODY;

  }

  public void release() {

     names = null;

     element = null;

  }

}     

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

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

Класс поддержки дескриптора sax : ifElementNameNotEquals приведен в листинге П.7,е.

ЛИСТИНГ 11.7,6. /WEB- INF/classes /tags/ . . . / IfElementNameNotEqualsTag.Java

package tags.xml.sax;

import java.util.StringTokenizer;

import java.util.NoSuchElementException;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.TagSupport;

import beans.xml.sax.SAXElement;

public class IfElementNameNotEqualsTag extends

                                      IfElementNameEqualsTag {

  public int doStartTag() throws JspException {

     int rvalue = super.doStartTag();

     return rvalue == EVAL_BODY_INCLUDE ? SKIP_BODY :

                                          EVAL_BODY_INCLUDE;

  }

}   

330      Глава 11. XML

Класс IfElementNameNotEqualsTag является подклассом IfElementName-EqualsTag. Метод doStartTag возвращает значение, обратное по сравнению со своим суперклассом.

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

Document Object Model (DOM)

Document Object Model — это интерфейс, обеспечивающий доступ к документам и их обновление. Программа разбора, созданная на базе этого интерфейса, строит Древовидную структуру, к узлам которой можно обращаться и изменять их содержимое. Средство просмотра XML, построенное с использованием Swing, показано на рис. 11.6; в окне отображается визуальное представление построенного дерева.

Дерево, изображенное на рис. 11.6, соответствует следующему XML-коду,

<?xml version-"]..Q" encoding="ISO-8859-l"?>

// Полностью код представлен в листинге 11.5,в <inventory> <book>

<ISBN>0393040009</ISBN>

Разбор 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/ classes /bean s/wnl/dom/DQMParserBean. Java

package beans.xml.dom;

import java.io.IOException;

import java.io.FileInputStream;

import org.apache.xerces.parsers.DOMParser;

import org.xml.sax.InputSource;

import org.xml.sax.SAXException;

import org.w3c.dom.Document;

public class DOMParserBean {

  private DOMParserBean() {} // disallow instantiation

  public static Document getDocument(String file)

                               throws SAXException, IOException {

     DOMParser parser = new DOMParser();

     parser.parse(new InputSource(new FileInputStream(file)));

     return parser.getDocument();

  }

}    

Код, ориентированный на конкретное средство разбора, сосредоточен в статическом методе DOMParserBean. getDocument, который создает документ DOM. При вызове этому методу передается имя файла. Класс DOMParserBean существует исключительно ради реализации метода getDocument, следовательно, создавать экземпляр данного класса нецелесообразно. По этой причине конструктор класса объявлен как private. (Поскольку создать экземпляр класса невозможно DOMParserBean не является "полноценным" компонентом bean.)

Имея документ, созданный в результате работы метода DOMParserBean. getDocument, вы можете обращаться к любой его части и при необходимости вносить требуемые изменения. После окончания работы с документом вы можете снова создать на его основе XML-файл. На рис. 11.7 показано приложение, которое позволяет изменять значения цен в XML-файле, показанном на рис. 11.6.

2Java-пршюжечж, показанное на рас. 11.6, не будет обсуждаться в данной книге, но вы можете скопировать его, обратившись по адресу http://vnma.phptr.com/advisp. - Прим. авт.

332      Глава 11. XML

Приложение на рис. 11.7 состоит из двух JSF-документов и иллюстрирует три алгоритма, реализуемых большинством DOM-прилжоений.

Разбор XML-кода и создание DOM-документа.

Модификация существующего документа.

Создание 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

<html><head><title>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' %>

  <%@ page import='beans.xml.dom.DOMParserBean' %>

  <%@ page import='javax.servlet.http.HttpServletRequest' %>

  <%@ page import='java.util.Enumeration' %>

</head>

<body>

<% Document document = (Document)application.getAttribute(

                                            "document");

  if(document == null) {

     document = DOMParserBean.getDocument(

                "f:/books/jsp/src/xml/bookInventory.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.getChildNodes();

     int childCnt = list.getLength();

     if(childCnt > 0) {

        String lastTitle = null;

        for(int i=0; i < childCnt; ++i) {

           Node next = list.item(i);

           String value = next.getNodeValue();

           if(next.getNodeType() == Node.ELEMENT_NODE) {

              String 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, request);

           }

        }

     }

  }

  private void showDocument(Node node, JspWriter out)

                                            throws JspException {

     NodeList list = node.getChildNodes();

     int childCnt = list.getLength();

     if(childCnt > 0) {

        String lastTitle = null;

        for(int i=0; i < childCnt; ++i) {

           Node next = list.item(i);

           try {

              String value = next.getNodeValue();

              if(next.getNodeType() == 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:</font><form>");

                    }

                    else if(nodeName.equals("title")) {  

                       lastTitle = text;

                       out.print("</p><b>Title: </b>" + text +

                                 "<br/>");

                    }

                    else if(nodeName.equals("price")) {

                       out.print("price: " +

                       "<input type='text' size='6' name='" +

                       lastTitle + "' value='" + text+ "'/><p>");

                    }

                 }

              }

           }

           catch(java.io.IOException ex) {

              throw new JspException(ex.getMessage());

           }

           showDocument(next, out);

         }

      }

   }

   private void addButtons(JspWriter out) throws JspException {

     try {

        out.print("<hr><input type='submit' " +

                  "value='Update DOM Document'/></form><p>");

        out.print("<form action='printXML.jsp'>");

        out.print("<input type='submit' value='Print XML'" +

                  "/></form></p>");

     }

     catch(java.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];

      }

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

   }

%>

</body>

</html>    

334  Глава 11. XML

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

Как легко заметить, скриптлет проверяет, существует ли документ в области видимости приложения; если документ отсутствует, производится разбор XML-файла и созданный в результате разбора документ сохраняется в области видимости приложения. Затем последовательно вызываются методы updatePrices, showDocument и addButtons.

Методы updatePrices и showDocument предназначены для обхода древовидной структуры DOM и допускают рекурсивные вызовы. Метод updatePrices копирует параметры запроса, представляющие цены на книги, в соответствующие узлы документа, а метод showDocument отображает XML-код в окне броузера. При вызове обоих методов им передается ссылка на DOM-узел, и в обоих методах содержится управляющая конструкция, представленная в листинге 11.8,в.

336     Глава 11. 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,- jsp

<htmlxheadxtitle>DOM Example</title>

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

<l@ page import='org.w3c.dom.Document' %>

<%@ page impoct='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 printXML(Node node, JspWriter out)

throws JspException { NodeList list = node.getChildNodes(); int childCnt = list.getLengthO;

for(int i=0; i < childCnt;

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

Node next = list.item(i);

if (next.getNodeType[) == Node.ELEMENT_NODE) ( String name = next.getNodeNamef); try {

out.printC'Slt;" + name + "figt;<br/>"}; if(name.equals("inventory"))

out.print("<br/>"); I

catch(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.getNodeType() = Node.ELEMENT_NODE) { String name = next.getNodeNamef); try {

out.print["filt;" + "/" + name + "Sgt;<br/>")

if(name.equals("book"))

out.print ("<br/>"); }

catch{Exception ex) О } }

private String getElementText(Node element) ( Node child = element.getFirstChild(); String text = null;

if(child != null)

text = child.getNodeValue();

return text;

Подобно showDocument и updatePrices, метод printXML осуществляет перебор дерева документа и выполняет рекурсивные вызовы. Продвигаясь вниз по дереву, метод выводит открывающие дескрипторы, а возвращаясь по стеку рекурсивных вызовов, выводит закрывающие дескрипторы.

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

338      Глава 11. XML

Пользовательские дескрипторы для DOM-разбора

Простой JSP-файл, представленный в листинге 11.9,а, функционально идентичен громоздкому документу, который был показан в листинге 11.8,6. Данные, выводимые обоими документами, показаны в левом окне на рис. 11.7.

Листинг 11.9,a. test dom books with tags.jsp

<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:ifNodeNameEquals node='<%= book %>' names='book'> <% String lastTitle = null; %>

<dom: iterate n.ode='<*= book %>' id=f bookElement' > <dom:ifNodeIsElement node='<%- bookEleraent %>'> <dom:elementValue id='value'

element='<%= bookElement %>'/>

<dom:ifNodeNameEquals node='<%= bookElement %>'

names-'title'>

<% lastTitle = value; %>

</pxb>Title:</b>inbsp;<%= value %><br/> </dom:ifNodeNameEquals>

<dom:ifWodeNameEquals node='<%= bookElement %>'

names='price' > price:Snbsp; <input type^'text' size=f5' value='<%= value %>'

name='<%= lastTitle %>'/> </dom:ifNodeNameEquals>

</dom:ifKodeIsElement> </dom:iterate> </dom: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>

Разбор 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>

В данном разделе мы рассмотрим классы поддержки для дескрипторов, приведенных в табл. 11,1. В листинге 11.9,6 содержится код класса поддержки для дескриптора parse.

Листинг 11,9,6. /WEB-INF/classes/tags/janl/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;

  public void setXmlFile(String xmlFile) {

     this.xmlFile = xmlFile;

     this.force = false;

  }

  public void setId(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 || force)

        parse();

     return SKIP_BODY;

  }

  private void parse() throws JspException {

     try {

        pageContext.setAttribute(id,

                    DOMParserBean.getDocument(xmlFile),

                    PageContext.APPLICATION_SCOPE);

     }

     catch(Exception ex) {

        throw new JspException(ex.getMessage());

     }

  }

  public void release() {

     xmlFile = null;

     force = false;

  }

}     

340      Глава 11. XML

Дескриптор parse выполняет разбор указанного XML-файла; по умолчанию разбор производится один раз. Полученный в результате DOM-документ сохраняется в области видимости приложения и доступен другим дескрипторам посредством переменной сценария. Вы можете указать дескриптору parse повторно выполнить разбор, для этого надо задать значение true атрибута force {по умолчанию значение этого атрибута равно true).

Для выполнения разбора дескриптор parse вызывает статический метод DOMParserBean. getDocument. Этот метод, инкапсулирующий непереносимые фрагменты кода, рассматривался ранее в этой главе.

Переменная сценария для DOM-документа определяется с помощью класса DOMParserTaglnfo, являющегося подклассом класса TagExtralnfo. Код класса DOMParserTaglnfo приведен в листинге 11.9,в.

Листинг 11.9,в. /WEB-IHF/classes/tage/xml/dom/DOMParaarTaglnfо. Java

package tags.xml.dom;

import javax.servlet.jsp.tagext.TagData;

import javax.servlet.jsp.tagext.TagExtraInfo;

import javax.servlet.jsp.tagext.VariableInfo;

public class DOMParserTagInfo extends TagExtraInfo {

  public VariableInfo[] getVariableInfo(TagData data) {

     return new VariableInfo[] {

        new VariableInfo(data.getId(),

                         "org.w3c.dom.Document",

                         true, VariableInfo.AT_END)

     };

  }

}    

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

Имя переменной сценария, используемой для доступа к документу, определяется с помощью атрибута id дескриптора parse.

Изменения вносятся в документ, созданный с помощью дескриптора parse, посредством пользовательского дескриптора updatePric.es. Дескриптор update-Prices устанавливает значения цен в соответствии с параметрами запроса. Класс поддержки дескриптора updatePrices приведен в листинге 11.9,г.

Листинг 11.9,г. /WEB-INF/classes/tags/app/UpdatePricesTag. 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.APPLICATION_SCOPE);

     if(document != null)

        updatePrices(document);

     return EVAL_PAGE;

  }

  private void updatePrices(Node node) throws JspException {

     ServletRequest request = pageContext.getRequest();

     NodeList list = node.getChildNodes();

     int childCnt = list.getLength();

     if(childCnt > 0) {

          String lastTitle = null;

        for(int i=0; i < childCnt; ++i) {

           Node next = list.item(i);

           String value = next.getNodeValue();

           if(next.getNodeType() == Node.ELEMENT_NODE) {

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

  }

}   

342      Глава 11. XML

Метод doEndTag приведенного выше класса ищет документ в области видимости приложения. Если документ найден, он передается в качестве параметра методу updatePrices, который выполняет обход дерева документа. Принцип обхода был представлен в листинге 11.8,в.

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

Для каждого элемента price, найденного в документе, метод updatePrices ищет параметр запроса с именем, совпадающим с названием книги. Если параметр найден, соответствующий элемент заменяется значением параметра.

После разбора XML-файла и обновления цен JSP-документ, показанный в листинге 11.8,в, перебирает дочерние узлы документа, используя для этого дескриптор iterate. Класс поддержки данного дескриптора приведен в листинге 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 javax.servlet.jsp.JspException;

import org.w3c.dom.Document;

import org.w3c.dom.Node;

import org.w3c.dom.NodeList;

public class NodeIteratorTag extends tags.util.IteratorTag {

  private Node node;

  public void setNode(Node node) {

     this.node = node;

  }

  public int doStartTag() throws JspException {

     Node parent = node;

     if(node instanceof Document)

        parent = ((Document)node).getDocumentElement();

        

     setCollection(collection(parent.getChildNodes()));

     return super.doStartTag();

  }

  public void release() {

     node = null;

  }

  private Collection collection(NodeList list) {

     Vector vector = new Vector();

     int length = list.getLength();

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

        vector.addElement(list.item(i));

     }

     return vector;

  }

}    

Представленный выше класс поддержки является подклассом класса tags.util. IteratorTag, который рассматривался в главе 2. Данный дескриптор осуществляет перебор набора и создает переменную сценария для текущего элемента набора.

Прочие классы поддержки, такие как NodelteratorTag, приведенный в листинге 11.9,д, являются расширением IteratorTag; при этом в них переопределяется метод

344      Глава 11. XML

cioStartTag. Перед тем как вернуть super .doStartTag, вызывается метод Iterator-Tag .setCollection.

Класс NodelteratorTag перебирает узлы DOM-дерева. Эти узлы являются дочерними по отношению к уэлу, указанному посредством атрибута node. Если родительский узел является экземпляром класса Document, в переменную parent записывается значение, полученное при вызове метода Document .getDocumentElement. Этот метод возвращает корневой элемент документа.

Переменная сценария, созданная с помощью класса NodelteratorTag, определяется посредством класса NodelteratorTaglnfo, код которого показан б листинге 11.9,е. Имя переменной сценария задается с помощью атрибута id дескриптора iterate.

Листинг 11.9,е. AreB-INF/claases/tags/janl/dam/NodelteratorTaglnfо. Java

package tags.xml.dom;

import javax.servlet.jsp.tagext.TagData;

import javax.servlet.jsp.tagext.TagExtraInfo;

import javax.servlet.jsp.tagext.VariableInfo;

public class NodeIteratorTagInfo extends TagExtraInfo {

  public VariableInfo[] getVariableInfo(TagData data) {

     return new VariableInfo[] {

        new VariableInfo(data.getId(),

                         "org.w3c.dom.Node",

                         true, VariableInfo.NESTED)

     };

  }

}     

В JSP-документе, показанном в листинге 11.9,а, содержатся три вспомогательных дескриптора: ifNodeNameEquals, ifNodelsElement и elementValue. Первые два дескриптора включают содержимое в зависимости от отмени узла или от того, является ли узел элементом. Дескриптор elementValue создает переменную сценария для указанного элемента.

Класс поддержки дескриптора ifNodeNameEquals приведен в листинге П.9,ж.

ЛИСТИНГ 11.9,Ж. /WEB-INF/classes/tags/aanl/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 IfNodeNameEqualsTag extends TagSupport {

  private Node node = null;

  private String names = null;

  public void setNode(Node node)     { this.node = node; }

  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 {

        while(!nameFound)

           nameFound = nodeName.equals(tok.nextToken());

     }

     catch(NoSuchElementException ex) {

        // no more tokens

     }

     return nameFound ? EVAL_BODY_INCLUDE : SKIP_BODY;

  }

  public void release() {

     names = null;

     node = null;

  }

}   

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

Класс, представленный в листинге 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 IfNodeIsElementTag extends TagSupport {

  private Node node = null;

  public void setNode(Node node) {

     this.node = node;

  }

  public int doStartTag() throws JspException {

     return node.getNodeType() == Node.ELEMENT_NODE ?

            EVAL_BODY_INCLUDE : SKIP_BODY;

  }

  public void release() {

     node = null;

  }

}    

346  Глава 11. XML

В листинге 11.9,и представлен класс поддержки дескриптора element Value, который сохраняет значение элемента в переменной сценария.

Листинг 11.9,и. /WEB-INF/classes/taga/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 setId(String id) {

     this.id = id;

  }

  public int doEndTag() throws JspException {

     if(element.getNodeType() == Node.ELEMENT_NODE) {

        String value = getElementText(element);

        pageContext.setAttribute(id, value);

     }

     else

        throw new JspException("Node must be an element");

     return EVAL_PAGE;

  }

  public void release() {

     element = null;

  }

  private String getElementText(Node element) {

     Node child = element.getFirstChild();

     String text = null;

     if(child != null)

        text = child.getNodeValue();

     return text;

  }

}    

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

Имя переменной сценария, созданной с помощью класса GetElementValueTaglnfo, определяется посредством обязательного атрибута id дескриптора element-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.10,6. /date.xsl

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

- <xsl:template match="/">

- <html>

- <head>

  <title>Processing XML Generated with JSP</title> 

  </head>

- <body>

- <font size="4">

  Today's Date is:

  <xsl:apply-templates /> 

  </font>

  </body>

  </html>

  </xsl:template>

- <xsl:template match="date">

  <xsl:apply-templates /> 

  </xsl:template>

  </xsl:stylesheet>

XSLT — это декларативный язык, основанный на правилах-шаблонах. Каждое правило-шаблон состоит из обртца (pattern) и действия (action), которые задаются в составе дескриптора xsl: template.

Например, влистинге 11.10,а используются два правила-шаблона:

<xsl:template match-"/">...</xsl:template> <xsl:template match="date">...</xsl:template>

В данных правилах-шаблонах используются образцы "/" (корневой элемент) и "date" (элемент date). Действия задаются в теле дескриптора.

Для правила, распознающего корневой элемент ("/")> создается следующий HTML-код.

<html><head>

<title>Processing XML Generated with JSP</title> </head> <body><font size=M'>

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

Today's Date is: </font> </body> </html>

Правило для корневого элемента не включает дату; вместо этого в нем содержится дескриптор xsl: apply-templates, который рекурсивно применяет правила для элементов, дочерних по отношению к корневому. В данном случае правилу "date" соответствует элемент date.

В состав правила "date" входит дескриптор xsl: apply-templates. Если элемент не содержит дочерних элементов, xsl: apply-templates включает тело элемента в состав выходных данных. В нашем примере генерируется дата "Fri Dec 15 11:46:45 MST2000".

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

Совместное использование XSLT и JSP

На практике JSP и XSLT часто используются совместно. Два способа такого применения условно показаны на рис. 11.9.

Как показано на рис. 11.9, XSLT-преобразования XML-кода можно выполнять в процессе выполнения программы, применяя для этого пользовательский дескриптор. Кроме того, вы можете применять XSLT к XML на этапе компиляции при генерации JSP-документа. Рассмотрим подробнее каждый из этих подходов.

Генерация HTML-кода путем XSLT-преобразования с помощью пользовательского дескриптора

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

<xslt:apply xsl='inventory.xsl'>

<%@ include file='inventory,xml' %> </xslt:apply>

350      Глава 11. XML

В приведенном выше фрагменте кода лист стилей, содержащийся в файле с именем inventory .xsl, используется для преобразования XML-кода, который находится в файле inventory .xml. При необходимости XML-данные можно помещать непосредственно в тело дескриптора; соответствующий пример приведен ниже.

<xslt:apply xsl='inventory.xsl'>

<?xml version='1.0' encoding^'ISO-8859-1'?>

</xslt:apply>

На рис. 11.10 показано Web-приложение, в котором дескриптор xslt: apply используется для выполнения XSLT-преобразования XML-файла. Содержимое XML-файла представлено в листинге 11.11,а.

ЈJDDM ЕкягфЬ • МеткоК \rtvi

HtЈxp4oiH

felt     Edit     Ufaw     Fffrorlk;     loots     ф|р

Ш

. Adieu [g] hup //IowIkbI eOaa/xml/tEsLdocTi.isD

GoJ

Inventory as of Sat Dec 16 16:24:16

MST 2000

Item

Destrlndon

Price

Лит LiU'j broach

from Ihe early 1700's

123Э.99

Gumhy and Pokey

pliable atlion figures

im

Underwood lypewnler

19J2, exceilent shape

299 95

LawnnKMnt

Опв cflhefirsl gas-engine mowers    499.S9

Superman comic book

Series 1A7, quite rare

7 99

Escher original drawing

signed in blue ink

3459.95

Roman helmet

estimated 200 В С

95.93

School desk

made in1949. with inkwell

995.99

ЈJDtjrB

*•; Loc^ titianet

Рис. 11.10. Применение пользовательского дескриптора для выполнения XSLT-преобразоаания

Листинг 11.11,a. /inventory.юп1

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>

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

</item>

. . . Остальные пункты не указаны .... </inventory>

Код^Р-документа, показанного на рис. 11.10, представлен в листинге 11.11,6.

Листинг 11.11,6. /test xslt.jsp

<html><head><title>XSLT Example*:/1itle>

<%@ taglib uri='/WEB-INF/tlds/xslt.tld' prefix=fxslt' </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>

</body></html>

JSP-документ, показанный в листинге 11.11,6, применяет лист стилей inventory, xsl к данным в файле inventory, xml, используя для этого дескриптор xs It: apply. Содержимое файла inventory, xsl приведено в листинге 11.11,в.

Листинг 11.11,8. /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'> <thxu>Item</u></th> <th><u>Description</u></th> <thxu>Price</u></th> <xsl:apply=templates/> </table> </body> </html>

:template>

<xsl:template match="item">

352  Глава 11. XML

<trxxsl:apply=templates/x/tr> </xsl:template> <xsl;template match="item/*">

<tdxxsl: apply=templates/x/td> </xsl:template> </xsl:stylesheet>

С помощью листа стилей, представленного в листинге 11.1,в, создается HTML-таблица. Основная часть HTML-кода, включая дескриптор TABLE и заголовки таблицы, генерируется при обработке корневого элемента. Строки таблицы создаются для каждого элемента item, а данные включаются в таблицу при обработке элементов name, description и price.

Класс поддержки для пользовательского дескриптора xslt: apply приведен в листинге 11.11,г.

Листинг 11.11,г. /WEB-INF/classes/tags/aaul/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.xslt.XSLTProcessorBean;

public class XSLTApplyTag extends BodyTagSupport {

  private String xsl;

  public void setXsl(String xsl) {

     this.xsl = xsl;

  }

  public int doAfterBody() throws JspException {

     XSLTProcessorBean xslBean = new XSLTProcessorBean();

     ServletContext context = pageContext.getServletContext();

     String body = bodyContent.getString().trim();

     try {

        if(body.equals("")) {

           throw new JspException("The body of this tag " +

                                  "must be XML.");

        }

        xslBean.process(new StringReader(body),

                            context.getResourceAsStream(xsl),

                            getPreviousOut());

     }

     catch(Exception ex) {

        throw new JspException(ex.getMessage());

     }

     return SKIP_BODY;

  }

}     

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

Класс поддержки, представленный в листинге 11.11,г, использует для выполнения XSLT-преобразования компонент bean. Если тело дескриптора отсутствует, генерируется исключение, в противном случае класс поддержки вызывает метод process компонента 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.xalan.xslt.XSLTProcessor;

import org.apache.xalan.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, таким образом, данный компонент инкапсулирует непереносимый код. Используя фабрику X5LT-npo<(eccopOB, метод process получает XSLT-процессор, использует его для выполнения XSLT-n ре образования и записывает результаты в поток JspWriter.

354      Глава 11. XML

Применение XSI-Тдля генерации JSP-кода на этапе компиляции

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

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

Лист стилей, применяемый при обработке файла inventory, xml, показан в листинге II.12,а.

Файл inventory, xsl, представленный в листинге 11.12,а, задается в командной строке следующим образом;

>  java org.apache.xalan.xslt.Process   -in  inventory.xml  -xsl inventory.xsl  -out  inventory.jsp

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

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

Листинг 11.12,6. JSP-доку мент, полученный в результате JSP-преобразования

<?>unl version^"].. О" 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> <th><u>Description</u></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-синтаксис.)

■■■■':•: г.!."8 . :: ■ "

Совет

Использование XML-формата при построении JSP

Вместо того чтобы применять скриптлеты и выражения, вы можете составить JSP-документ в XML-формате. Спецификация JSP 1.1 определяет XML-запись всех эле-MeHTOBjSP, например, дескриптор jsp:scriptlet можно использовать вместо обычной записи скриптлета.

356      Глава 11. XML

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

Два подхода к выполнению XSLT-преобразования

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

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

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

XPatli — это язык, используемый в процессе XSLT-преобразования для проверки соответствия XML-элементов образцам. Ранее в данной главе встречалось правило-шаблон, в котором образец "/" обозначал корневой элемент:

<xsl:template match='V">...</xsl:template>

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

Средства XPath удобно применять для поиска XML-элементов, но их использование снижает производительность при XSLT-преобразовании,

XPath — независимый язык, поэтому большинство XSLT-процессоров позволяет использовать XPath без XSLT. В данном разделе вы узнаете об использовании XPath при работе с XSLT-процессором Apache Xalan.

JSP-документ, показанный на рис. 11.11, содержит пользовательский дескриптор, предназначенный для выбора элементов из XML-файла. Этот дескриптор использует компонент bean, который, в свою очередь, применяет XPath для поиска элементов в XML-файле.

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

Код}5Р-документа, показанного на рис. 11.11, приведен в листинге 11.13,а.

Пользовательский дескриптор selectNodes, содержащийся в листинге 11.13,а, перебирает узлы указанного XML-файла. Выбор узла осуществляется в соответствии с XPath-выражением. Кроме того, пользовательский дескриптор обеспечивает доступ к переменной сценария, которая представляет текущий узел. Как видно из листинга 11.13,а, переменная сценария имеет имя node.

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/joni/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.DOMParserBean;

import beans.xml.xpath.XPathBean;

import tags.util.IteratorTag;

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

     }

     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; ++i) {

        vector.addElement(list.item(i));

     }

     return vector;

  }

}    

Подобно классу поддержки, приведенному в листинге ПДд, представленный выше класс является подклассом IteratorTag (класс IteratorTag был рассмотрен в главе 2).

Метод XPathTag .doStartTag использует компонент bean XPathBean, который инкапсулирует вызовы XPath. Этот компонент обрабатывает XPath-выражение и возвращает объект NodeList, содержащий список узлов, которые соответствуют выражению. Метод XPathTag. doStartTag преобразует список в набор, который передается методу IteratorTag . setCollection. Затем XPathTag .doStartTag вызывает метод super .doStartTag, в результате чего вступает в действие класс IteratorTag, который перебирает набор, установленный посредством вызова setCollection.

Код класса XPathBean показан в листинге 11.13,г.

ЛИСТИНГ 11.13,Г. /WEB-INF/classes/beans/xml/xpath/XPathBean, ;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;

import org.apache.xalan.xpath.XPathProcessorImpl;

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() { } // defeat instantiation

  public static NodeList process(Node node, String expr)

                                            throws SAXException {

     XPathSupport s = new XMLParserLiaisonDefault();

     PrefixResolverDefault pr = new PrefixResolverDefault(node);

     XPathProcessorImpl processor = new XPathProcessorImpl(s);

     XPath xpath = new XPath();

     processor.initXPath(xpath, expr, pr);

     XObject xo = xpath.execute(s, node, pr);

     return xo.nodeset();

  }

}     

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

360      Глава 11. XML

Подобно классам SAXParserBean, DOMParserBean и XSLTProcessorBean, которые были рассмотрены ранее в этой главе, класс XPathBean инкапсулирует код, ориентированный на конкретный процессор. Этот код, сосредоточенный в статическом методе XPathBean.process, обрабатывает XPath-выражение. сравнивая его с узлом DOM (предполагается, что этот узел является корневым узлом документа или его фрагмента). Этот метод возвращает объект NodeList, содержащий все узлы, соответствующие XPath-выражению.

Как и DOMParserBean, класс XPathBean реализует единственный статический метод. В связи с этим, чтобы предотвратить попытки создания экземпляра класса, конструктор XPathBean объявлен как private.

Резюме

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

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

Для разбора XML-данных используются два стандартных API: SAX и DOM, Эти средства рассматривались на протяжении данной главы; здесь также обсуждались пользовательские дескрипторы для выполнения SAX- и DOM-разбора.

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

ПРИЛОЖЕНИЕ НА БАЗЕ JSP

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

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

Исходная страница.

"Витрина".

Проверка выбранных товаров.

Приобретение товара.
Базовый набор классов Model 2.

Модель.

Просмотры: JSP-документы и шаблоны.

Контроллеры: сервлеты и действия.

Интернационализация.

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

HTML-формы.

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

SSL.

XMLhDOM.

 

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

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

Приложение соответствует архитектуре MVC Model 2.

Приложение поддерживает три языка: английский, немецкий и китайский.

Приложение осуществляет аутентификацию.

В приложении используются|5Р-шаблоны.

Приложение работает с базой данных,

В приложении используются средства XML и DOM.

Приложение поддерживает формы, чувствительные к повторной активизации.

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

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

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

Описанное в данной главе приложение содержит большой объем кода, поэтому мы будем рассматривать его в три этапа.

Основной сценарий развития событий: исходная страница—> "витрина"—>
выбор продукта—> покупка.

Архитектура MVC: модель, просмотр и контроллеры.

Прочие средства: П8п, аутентификация, HTML-формы, формы, чувствитель
ные к повторной активизации, SSL и XML.

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

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

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

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

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

  1.  Пользователь   обращается   к   исходной   странице   и   активизирует   кнопку
    Go Shopping.

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

Пользователь активизирует кнопку Checkout, расположенную в левой части
Web-страницы.

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

Пользователь активизирует кнопку Purchase the Items Listed Above на странице
проверки.

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

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

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

Почти все средства, использованные при создании приложения, за исключением каталога graphics и нескольких файлов в корневом каталоге, сосредоточены в подкаталогах /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. Если же вы собираетесь модифицировать прило-

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

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

<Context   path="/case-study"

tiocBase="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'/>

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

Исходная страница

Набор файлов, предназначенных для формирования приветственного сообщения, состоит из одного файла /index, jsp. Этот файл указам в /WEB-INF/web.xml следующим образом:

<?xml version="l.0" encoding="ISO-8859-l"?>

<!DOCTYPE web-app

PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http:Z/java.sun.com/j2ee/dtds/web-app_2.2.dtd">

<welcome-file-list>

<welcome-file>index.jsp</welcome-file> </welcome-file-list>

</web-app>

При работе с Tomcat или Resin файл /index, jsp вызывается при обращении к URL http://localhost: 8080/case-study (для других контейнеров номер порта может отличаться). Содержимое файла /index, jsp  представлено в листинге 12.1,а.

Листинг 12.1,a. /index.jsp

 <%@ page contentType='text/html; charset=UTF-8' %>

<%@ taglib uri='regions' prefix='region' %>

<%@ include file='/WEB-INF/jsp/regionDefinitions.jsp' %>

<region:render region='HOMEPAGE_REGION'/>

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

В файле /index.jsp применяется библиотека пользовательских дескрипторов, предназначенная для поддержки областей. Эта библиотека рассматривалась в главе 4.

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

Определение областей содержится в файле /WEB-INF/ jsp/regionDefinitions. jsp, который включается в состав /index* jsp. Внешний вид исходной Web-страницы показан на рис, 12.3.

Как и другие JSP-документы, входящие в состав приложения, исходная страница состоит из одной области, включающей четыре раздела: заголовок, полосу в левой части страницы, содержимое и нижний колонтитул. В левой части страницы размещены изображения трех флагов, которые используются для выбора языка, а также кнопка Go Shopping, В разделе заголовка выводится строка "Welcome to Fruit-stand.com" и горизонтальная линия. Основной текст размещен в разделе содержимого, а в нижнем колонтитуле отображается строка, включающая дату.

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

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

пользование шаблонов делает возможным замену компонентов. Преимущества применения шаблонов были подробно рассмотрены в главе 4.

Файл regionDef initions. j sp определяет области, используемые в составе приложения. Частично содержимое этого файла приведено в листинге 12.1,6 (полностью файл regionDef initions . jsp будет представлен далее в этой главе).

Листинг 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 section='title'

              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='LOGIN_REGION' region='STOREFRONT_REGION'>

  <region:put section='header'

              content='/WEB-INF/jsp/login/header.jsp'/>

  <region:put section='sidebar'

              content='/WEB-INF/jsp/login/sidebar.jsp'/>

  <region:put section='content'

              content='/WEB-INF/jsp/login/content.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'

              content='/WEB-INF/jsp/homepage/content.jsp'/>

</region:define>

<region:define id='CREATE_ACCOUNT_REGION' region='LOGIN_REGION'>

  <region:put section='content'

              content='/WEB-INF/jsp/createAccount/content.jsp'/>

  <region:put section='sidebar'

              content='/WEB-INF/jsp/createAccount/sidebar.jsp'/>

</region:define>

<region:define id='LOGIN_FAILED_REGION' region='LOGIN_REGION'>

  <region:put section='content'

              content='/WEB-INF/jsp/loginFailed/content.jsp'/>

</region:define>

<region:define id='ACCOUNT_CREATED_REGION' region='LOGIN_REGION'>

  <region:put section='content'

              content='/WEB-INF/jsp/accountCreated/content.jsp'/>

</region:define>

<region:define id='CHECKOUT_REGION' region='LOGIN_REGION'>

  <region:put section='content'

              content='/WEB-INF/jsp/checkout/content.jsp'/>

</region:define>

<region:define id='PURCHASE_REGION' region='LOGIN_REGION'>

  <region:put section='content'

              content='/WEB-INF/jsp/purchase/content.jsp'/>

</region:define>

<region:define id='SHOW_HINT_REGION' region='LOGIN_REGION'>

  <region:put section='content'

              content='/WEB-INF/jsp/showHint/content.jsp'/>

</region:define>     

В файле regionDef initions. j sp определена область STORE FRONT_REG ION, предназначенная для реализации "витрины" приложения. Область HOMEPAGE_REGION расширяет STOREFRONT_REGION и переопределяет разделы sidebar и content. Разделы title, background, header и footer используются без изменений.

JSP-файлы, соответствующие разделам header и footer, приведены соответственно в листингах 12.1,в и 12.1,г.

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

Листинг 12.1,В. /WEB-INF/jsp/storefront/header. jsp

<%@ page contentType='text/html; charset=UTF-8' %>

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

<font size='6' color='blue'>

  <i18n:message key="storefront.title"/>

</font>

<hr/>

<br/>

  

Листинг 12.1,г. /WEB-INF/jsp/storefrent/footer, jsp

<%@ page contentType='text/html; charset=UTF-8' %>

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

<hr><p>

<table>

  <tr>

     <td><img src='graphics/duke.gif'/></td>

     <td>

        <i18n:message key='login.footer.message'/><i>

        <i18n:format date='<%=new java.util.Date()%>'

                dateStyle='<%=java.text.DateFormat.FULL%>'/></i>.

     </td>

  </tr>

</table>

</p>  

Текст, отображаемый в приложении, воспроизводится посредством дескрипторов il8n:message и il8n: format, которые обеспечивают интернационализацию приложения. Интернационализация предполагает изменение текста, а также формата чисел, даты и обозначения денежных единиц. Дескрипторы il8n:message и il8n : format используются для отображения заголовка и нижнего колонтитула "витрины".

Дескриптор il8n:message выводит текст, заданный в файле свойств. Например, ниже приведена часть файла /WEB-INF/classes/a.pp_en.properties, используемого для поддержки английского языка,

storefront.title=Welcome to FruitStand.com

storefront.form.title=Please select from our fresh fruts.

storefront.table.header.picture=Picture

storefront.table.header.item=Item

storefront.table.header.description=Description

storefront.table.header.price=Price

storefront.table.header.addToCart=Add To Cart

В состав приложения также входят файлы свойств для немецкого и китайского языков; полробно вопросы поддержки языков будут рассмотрены далее в этой главе.

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

JSP-файл, соответствующий содержимому исходной страницы, приведен в листинге 12.1, д.

Листинг 12.1,д. /WЈB-INF/jep/homepa.ge/content.jsp

<%@ page contentType='text/html; charset=UTF-8' %>

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

<font size='5' color='blue'>

  <i18n:message key='homepage.title'/>

</font>

<i18n:message key='homepage.text'/>

   

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

Выбор товаров

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

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

Листинг 12.2,6. /WEB-INF/classes/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 ActionBase

                        implements beans.app.Constants {

  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 ties приложения, представленном в листинге 12.2,в.

Листинг 12.2,в. /WEB-INF/classes/actions. properties

# Отображение имен в действия, используемое сервлетом действий

go-shopping-aotion =actions.GoShoppingAction

query-account-action -actions.QueryAccountAction

new-account-action =actions.NewAccountAction

show-hint-action =actions.ShowHintAction

update-locale-action =actions.UpdateLocaleAction

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

add-selection-to-cart-action=actions.AddToCartAction
checkout-action =actions.CheckoutAction

validate-account-action    =actions.ValidateAccountAction
purchase-action ^actions.PurchaseAction

# Отображение имен в JSP-документы, используемое маршрутизаторами

storefront-page =/WEB-INF/jsp/storefront/page.jsp

login-failed-page =/WEB-INF/jsp/loginFailed/page.jsp

query-account-page =/WEB-INF/jsp/createAccount/page.jsp
account-created-page=/WEB-INF/jsp/accountCreated/page.jsp

show-hint-page =/WEB-INF/jsp/showHint/page.jsp

checkout-page =/WEB-lNF/jsp/checkout/page.jsp

purchase-page =/WEB-INF/jsp/purchase/page.jsp

В файле actions .properties определены два набора логических имен. Имена первого набора отображаются в действия; например имя go-shopping-action, do соответствует действию actions. GoShoppingAction, код которого приведен в листинге 12.2,в. Имена второго набора отображаются в документы; например, в этом наборе присутствует имя storefront-page, используемое действием go-shopping-action, do.

"Витрина"

Рассмотрим кратко, как пользователь получает доступ к "витрине" интерактивного магазина. Сначала он обращается к исходной странице, задавая URL http: //local-host: 8080/case_study. В результате такого обращения отображается приветственное сообщение, за вывод которого отвечает документ / index. j sp. Затем пользователь активизирует кнопку Go Shopping, в результате чего формируется запрос к ресурсу go-shopping-action.do. Управление передается действию GoShoppingAction, которое создает "корзинку" покупателя и перенаправляет запр *с документу, отображающему "витрину" магазина. Внешний вид "витрины" показан на рис. 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.

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

Область STORE FRONT_REGION включает четыре файла: header, jsp, sidebar, jsp, content. j sp и footer. j sp. Как вы уже знаете, заголовок (header. j sp) и нижний колонтитул {footer. j sp) используются также исходной страницей, и коды этих файлов были представлены ранее в листингах 12.1,в и 12.1,г. В листинге 12.3,6 приведен файла, ответственного за отображение полосы в левой части Web-страницы.

Листинг 12.3,6. /WEB-INF/jsp/storefront/sidebajr. jsp

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

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

Подобно области HOMEPAGE_REGION, полоса в левой части области STOREFRONT REGION включает файл flags.jsp, поэтому пользователь имеет возможность выбрать требуемый язык. Кроме того, полоса в области STOREFRONT_REGION включает еще один разделяемый компонент, посредством которого отображается текущее содержимое "корзинки" покупателя. Этот компонент реализуется посредством файла /WEB-INF/j sp/ shared/cart, jsp, содержимое которого будет представлено далее в листинге 12.4,6.

Отображение основных сведений в области STOREFRONT_REGION реализуется с помощью файла /WEB-INF/ jsp/storef ront/content. jsp, который приведен в листинге 12.3,в.

ЛИСТИНГ 12.3,6. /WEB-INF/jsp/storefront/content. jsp

<%@ page contentType='text/html; charset=UTF-8' %>

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

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

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

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

<font size='4' color='blue'>

  <i18n:message key='storefront.form.title'/>

</font><p>

<database:query id='inventory' scope='session'>

  SELECT * FROM Inventory

</database:query>

<% String currentItem = null, currentSku = null; %>

<table border='1' cellpadding='5'>

  <tr><th><i18n:message key='storefront.table.header.picture'/>

     </th>

     <database:columnNames query='inventory' id='name'>

        <logic:stringsNotEqual compare='SKU' to='<%= name %>'>

           <% String hdrKey = "storefront.table.header." +

                               name.toLowerCase(); %>

           <th><i18n:message key='<%= hdrKey %>'/></th>

           <logic:stringsEqual compare='NAME' to='<%= name %>'>

              <th><i18n:message

                      key='storefront.table.header.description'/>

              </th>

           </logic:stringsEqual>

        </logic:stringsNotEqual>

     </database:columnNames>

     <th><i18n:message key='storefront.table.header.addToCart'/>

     </th>

  </tr>

  <tr>

  <database:rows query='inventory'>

     <database:columns query='inventory' columnName='name'

                                        columnValue='value'>

        <logic:stringsEqual compare='SKU' to='<%= name %>'>

           <% currentSku = value; %>

           <td><img src='<%= "graphics/fruit/" + currentSku +

                             ".jpg" %>'/></td>

        </logic:stringsEqual>

        

        <logic:stringsEqual compare='NAME' to='<%= name %>'>

           <% currentItem = value; %>

           <td><%= value %></td>

           <td><i18n:message key='<%=value + ".description"%>'/>

           </td>

        </logic:stringsEqual>

        <logic:stringsEqual compare='PRICE' to='<%= name %>'>

           <td><%= value %></td>

           <td>

              <form action='add-selection-to-cart-action.do'>

                 <html:links name='<%= currentSku  + "-" +

                                       currentItem + "-" +

                                       value %>'>

                    <option value='0.00'>0.00</option>

                    <option value='1.00'>1.00</option>

                    <option value='1.50'>1.50</option>

                    <option value='2.00'>2.00</option>

                    <option value='2.50'>2.50</option>

                    <option value='3.00'>3.00</option>

                    <option value='3.50'>3.50</option>

                    <option value='4.00'>4.00</option>

                    <option value='4.50'>4.50</option>

                    <option value='5.00'>5.00</option>

                    <option value='5.50'>5.50</option>

                 </html:links>

              </form>

           </td>

           </tr><tr>

        </logic:stringsEqual>

     </database:columns>

  </database:rows>

</table>

</p>

<database:release query='inventory'/>   

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

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

В файле content. j sp содержится таю^е пользовательский дескриптор html: links, который рассматривался в главе 2, С помощью этого дескриптора оформляются пункты раскрывающегося списка; при активизации пункта списка генерируется запрос, который

передается ресурсу add-selection-to-cart-action.do. Логическое имя add-selec-tion-to-cart-action.do отображается в класс actions.AddToCartAction; это отображение задается в файле свойств actions.properties, содержимое которого было показано в листинге 12.2,в. Класс AddToCartAction будет подробно рассмотрен ниже.

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

При активизации одного из пунктов, показанных на рис. 12.4,а, генерируется запрос, который направляется действию actions.AddToCartAction. Код класса AddToCartAction показан в листинге 12.4,а.

ЛИСТИНГ 12.4,a. /WEB-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;

// sku stands for stock keeping unit, an accounting term for

// something that's in stock pending sale. This action's request

// has a parameter that looks like this: sku-fruit-price=amount;

// for example, 1002-banana-0.69=0.75.

public class AddToCartAction extends ActionBase

                            implements 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.nextElement();

     Iterator it = cart.getItems().iterator();

     boolean fruitWasInCart = false;

     while(it.hasNext()) {

        Item item = (Item)it.next();

        if(item.getName().equals(fruit)) {

           fruitWasInCart = true;

           item.setAmount(item.getAmount() +

                          Float.parseFloat(amount));   

        }

     }

     if(!fruitWasInCart) {

        cart.addItem(new Item(Integer.parseInt(sku), fruit,

                              Float.parseFloat(price),

                              Float.parseFloat(amount)));

     }

     return new ActionRouter("storefront-page");

  }

}    

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

При вызове действия, приведенного выше, ему передается единственный параметр в формате квд_товира-1и1им,еновстш-цена=коАичество. Например, если пользователь выбрал два фунта грейпфрутов по цене $0,49, то параметр запроса будет иметь вид 1004-grapefruit-0.49=2.0. Действие AddToCartAction выполняет разбор параметра и использует полученную информацию для изменения содержимого "корзинки" покупателя.

Метод perform рассматриваемого действия возвращает маршрутизатор действий, который указывает на Web-страницу "витрины". В результате "витрина" обновляется, и содержимое "корзинки" отображается в полосе а левой части страЕшцы. JSP-доку-мент, соответствующий "корзинке" покупателя, представлен в листинге 12.4,6.

ЛИСТИНГ 12.4,6. /WEB-INF/jsp/shared/cart, jsp

<%@ taglib uri='application' prefix='app' %>

<img src='graphics/cart.gif'/>

<table cellpadding='3'>

  <app:iterateCart id='cartItem'>

     <tr>

        <td><%= cartItem.getName()   %></td>

        <td><%= cartItem.getAmount() %></td>

     </tr>

  </app:iterateCart>

</table>

<form action='checkout-action.do'>

  <input type='submit'value='checkout'/>

</form>   

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

JSP-документ, кол которого показан в листинге 12.4,6, применяет для перебора содержимого "корзинки" пользовательский дескриптор, специфический для данного приложения. Класс поддержки этого дескриптора показан в листинге 12.4,в.

Листинг 12.4,в. /WEB-lNF/classes/tags/app/CartlteratorTag. 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 CartIteratorTag extends tags.util.IteratorTag

                              implements 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("CartIteratorTag can't find " +

                               "cart");

     }

     setCollection(cart.getItems());

     return super.doStartTag();

  }

}    

Рассматриваемый пользовательский дескриптор организует перебор товаров, помещенных в "корзинку" покупателя; информация о текущем товаре доступна через переменную сценария, имя которой задается посредством атрибута id дескриптора. Код в листинге 12.4,в не дает представления о действиях, выполняемых дескрипторами, поскольку основные функциональные возможности реализованы в родительском классе IteratorTag, который рассматривался в главе 2. Метод CartlteratorTag. doStartTag иызывает метод setCollection, определенный в суперклассе, а затем, перед окончанием выполнения, обращается к методу super. doStartTag.

Помимо содержимого "корзинки" в левой части Web-страницы отображается также кнопка Checkout, при активизации которой генерируется запрос к ресурсу checkout-action.do. В файле actions .properties, код которого представлен в листинге 12.2,в, логическое имя checkout-action.do отображается в класс actions. CheckoutAction. Код класса CheckoutAction приведен в листинге 12.5,а,

ЛИСТИНГ 12.5,3. /WEB-INF/classes/CheckoutAction. 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;

public class CheckoutAction extends ActionBase

                        implements beans.app.Constants {

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

        throw new ServletException("Cart not found");

     }

     return new ActionRouter("checkout-page");

  }

}   

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

Рассматриваемое действие проверяет, создан ли объект, представляющий "корзинку" пользователя, если данный объект отсутствует, в методе CheckoutAction.perform генерируется исключение. Если "корзинка" присутствует, метод perform возвращает маршрутизатор действий, в результате чего запрос перенаправляется к документу, который позволяет покупателю проверить свой выбор.

Проверка выбранных товаров

Документ, внешний вид которого показан на рис. 12.5, отображает счет, включающий перечень товаров, находящихся в "корзинке". Для завершения транзакции пользователь должен активизировать кнопку Purchase The Items Listed Above.

Код документа, позволяющего покупателю проверить свой выбор, приведен в листинге 12.5,6.

Листинг 12.5,6. /WEB-INF/jsp/checkout/page, jsp

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

<%@ taglib uri='regions' prefix='region' %>

<security:enforceLogin

  loginPage='/WEB-INF/jsp/login/page.jsp'

  errorPage='/WEB-INF/jsp/loginFailed/page.jsp'/>

<region:render region='CHECKOUT_REGION'/>

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

Как и документ, реализующий "витрину" {листинг 12.3,а), рассматриваемый документ воспроизводит область, В данном случае используется область CHECKOUT_REGION, определенная следующим образом:

<region:define   id='CHECKOUT_REGIONf   region-'LOGIH_REGION'> <region:put  section='content'

content=' /WEB-INF/jsp/checkout/content.jsp'/> </region:define>

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

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

Листинг 12.5,в. /WEB-INF/jsp/checkout/content. jsp

<%@ page contentType='text/html; charset=UTF-8' %>

<%@ page import='beans.app.User' %>

<%@ taglib uri='application' prefix='app'  %>

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

<font size='4' color='blue'>

  <i18n:message base='app' key='checkout.title'/>

</font><p>

<img src='graphics/cart.gif'/>

<table cellpadding='10'>

  <th><i18n:message base='app'

                    key='checkout.table.header.item'/></th>

  <th><i18n:message base='app'

                    key='checkout.table.header.amount'/></th>

  <th><i18n:message base='app'

                    key='checkout.table.header.pricePerLb'/></th>

  <th><i18n:message base='app'

                    key='checkout.table.header.price'/></th>

  <% double total = 0.0; %>

  <app:iterateCart id='item'>

     <% String  name = item.getName();

        float    amt = item.getAmount(),

               price = item.getPrice();       

     %>

     <tr>

        <td><%= name %></td>

        <td><%= amt %> lbs.</td>

        <td><i18n:format currency='<%=new Double(price)%>'/></td>

        <td><i18n:format currency='<%=new Double(price*amt)%>'/>

        </td>

     </tr>

     <% total += price * amt; %>

  </app:iterateCart>

  <tr>

     <td colspan='4'><hr/></td>

  </tr>

     <td><b><i18n:message base='app' key='checkout.table.total'/>

     </b></td>

     <td></td><td></td>

     <td><i18n:format currency='<%= new Double(total) %>'/></td>

  </tr>

</table><p>

<% User user = (User)session.getAttribute(

              tags.security.Constants.USER_KEY); %>

<font size='4' color='blue'>

  <i18n:message base='app' key='checkout.billTo'/><p>

</font>

  <%= user.getFirstName() %> <%= user.getLastName() %><br/>

  <%= user.getAddress() %><br/>

  <%= user.getCity() %>, <%= user.getState() %><br/>

  <%= user.getCountry() %><br/>

  <%= user.getCreditCardType() %><br/>

</p>

<form action='purchase-action.do'>

  <input type='submit'

        value='<i18n:message key="checkout.purchase.button"/>'/>

</form>

 

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

Подобно полосе в левой части Web-страницы (листинг 12.4,6), документ, определяющий содержимое области проверки, применяет для перебора товаров, находящихся в "корзинке", пользовательский дескриптор Cartlterator, На основании данных, извлекаемых посредством данного дескриптора, формируется счет.

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

В состав страницы, предназначенной для проверки выбора пользователя, также входит форма с кнопкой. При активизации этой кнопки формируется запрос к ресурсу purchase-act ion. do, который перенаправляется действию actions . Purchase-Action. Код класса PurchaseAction приведен влистинге 12.5,г.

ЛИСТИНГ 12.5,Г. /WEB-INF/classes/actions/PurohasaAcfcion. 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 PurchaseAction extends ActionBase

                        implements beans.app.Constants,

                                    tags.security.Constants {

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

        throw new ServletException("Cart not found");

     }

     return new ActionRouter("purchase-page");

  }

}   

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

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

Приобретение товара

Приобретение товара осуществляется с помощью простого JSP-документа, который отображает благодарность пользователю за покупку и выводит дату выполнения заказа. Внешний вид этого документа приведен на рис. 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:deiine  id='PURCHASE_REGION'   region='LOGIN_REGION'> <region:put  section=f content'

content='/WEB-INF/jsp/purchase/content.jsp'/> </region:define>

Базовый набор классов 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. jsp

<%@ page contentType='text/html; charset=UTF-8' %>

<%@ taglib uri='application' prefix='app'  %>

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

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

<font size='4' color='blue'>

  <i18n:message base='app' key='purchase.title'/><p>

  <i18n:message base='app' key='purchase.willBeShippedOn'/>

  <i18n:format date='<%= new java.util.Date() %>'

          dateStyle='<%= java.text.DateFormat.SHORT %>'/></p>

</font>

   

В данном документе для отображения текста используется дескриптор i 16n :message, кроме того, форматирование даты выполнения заказа осуществляется посредством дескриптора il8n: format.

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

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

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

Модель

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

User: покупатель, пользующийся услугами магазина.

Users: набор пользователей, инициализируемый посредством базы данных.

Item: товар, предназначенный для продажи.

Inventory: набор товаров.

ShoppingCart: совокупность товаров, выбранных потребителем.

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

ряд

Кроме перечисленных выпи- компонентов bean в приложении также определен

констант.

В процессе работы приложения используется база данных, содержащая описание набора товаров и список пользователей. База данных создается посредством Java-приложения Create DB, которое находится в каталоге /WEB- INF/classes /util.

База данных

[sku

11001

NAME apple

РИСЕ

0.29

1002

banana

0 69

,003

cantaloupe

0.19

1004

grapefruit

049

1005

grapes

a79

,006

kiwi

0.99

1007

peach

039

100В

pear

0.89

1003

pineapple

029

1010

strawberry

0.88

1011

watermelon

0.29

На рис. 12.8 показана таблица Inventory базы данных. Таблица состоит из строк (записей), каждая из которых включает код, наименование товара и цену.

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

Код приложения CreateDB, которое создает базу данных, приведен в листинге 12.6.

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

Базовый набор классов 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) {

        ex.printStackTrace();

     }

  }

  private void createTables(Statement stmt) {

     try {

        stmt.execute("CREATE TABLE Users (" +

            "FIRST_NAME VARCHAR(15), " +

            "LAST_NAME VARCHAR(25), " +

            "ADDRESS VARCHAR(35), " +

            "CITY VARCHAR(15), " +

            "STATE VARCHAR(15), " +

            "COUNTRY VARCHAR(25), " +

            "CREDIT_CARD_TYPE VARCHAR(10), " +

            "CREDIT_CARD_NUMBER VARCHAR(20), " +

            "CREDIT_CARD_EXPIRATION VARCHAR(10), " +

            "USER_ID VARCHAR(15), " +

            "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) {

        ex.printStackTrace();

     }

  }

  private void populateTables(Statement stmt) {

     try {

        stmt.execute("INSERT INTO Users VALUES " +

        "('James', 'Wilson', '24978 Roquert Lane', 'Ithica'," +

        " 'New York', 'USA', 'Visa', '124-3393-62975', '01/05',"+

        " 'jwilson', 's2pdpl8', 'license', 'customer')");

        stmt.execute("INSERT INTO Inventory VALUES " +

                          "('1001', 'apple', '0.29')," +

                          "('1002', 'banana', '0.69')," +

                          "('1003', 'cantaloupe', '0.19')," +

                          "('1004', 'grapefruit', '0.49')," +

                          "('1005', 'grapes', '0.79')," +

                          "('1006', 'kiwi', '0.99')," +

                          "('1007', 'peach', '0.39')," +

                          "('1008', 'pear', '0.69')," +

                          "('1009', 'pineapple', '0.29')," +

                          "('1010', 'strawberry', '0.89')," +

                          "('1011', 'watermelon', '0.29')");

     }

     catch(SQLException ex) {

        ex.printStackTrace();

     }

  }

  private void loadJDBCDriver() {

     try {

        Class.forName("COM.cloudscape.core.JDBCDriver");

     }

     catch(ClassNotFoundException e) {

        e.printStackTrace();

     }

  }

  private Connection getConnection(String dbName) {

     Connection con = null;

     try {

        con = DriverManager.getConnection(

           "jdbc:cloudscape:" + dbName + ";create=true");

     }

     catch(SQLException sqe) {

        System.err.println("Couldn't access " + dbName);

     }

     return con;

  }      

}    

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

Приложение, код которого показан в листинге 12.6, устанавливает соединение с СУБД Cloudscape и создает базу в каталоге f: /databases/sunpress. Затем приложение заполняет таблицы базы данных, а после разрывает соединение с базой и прекращает работу.

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

При необходимости можно без труда адаптировать данное приложение для работы с базой другого производителя. Для этого достаточно изменить имя драйвера в методе loadJDBCDriver и URL базы в методе getConnection.

Компоненты bean

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

Листинг 12.7,a. /WEB-INF/classes/baans/app/User . Java

package beans.app;

// Users are immutable to eliminate multithreading concerns.

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

     this.firstName            = firstName;

     this.lastName             = lastName;

     this.address              = address;   

     this.city                 = city;

     this.state                = state;     

     this.country              = country;

     this.creditCardType       = creditCardType;

     this.creditCardNumber     = creditCardNumber;

     this.creditCardExpiration = creditCardExpiration;

     this.userName             = userName;  

     this.password             = password;  

     this.pwdHint              = pwdHint;   

     this.roles                = roles;

  }

  public String getFirstName() { return firstName; }

  public String getLastName()  { return lastName;  }

  public String getAddress()   { return address;   }

  public String getCity()      { return city;      }

  public String getState()     { return state;     }

  public String getCountry()   { return country;   }

  public String getCreditCardType()   { return creditCardType; }

  public String getCreditCardNumber() { return creditCardNumber;}

  public String getCreditCardExpiration() {

     return creditCardExpiration;

  }

  public 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) &&

            getPassword().equals(pwd);

  }

}    

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

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

Код класса Users, описывающего набор пользователей, приведен в листинге 12.7,6.

ЛИСТИНГ 12.7,6. /WEB-INF/classes/beans/app/Users . 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,

                           PASSWORD=11, PASSWORD_HINT=12,

                           ROLES=13;

  private final Hashtable users = new Hashtable();

  public Users(ResultSet rs) {

     try {

        ResultSetMetaData rsmd = rs.getMetaData();

        if(rsmd.getColumnCount() > 0) {

           boolean moreRows = rs.next(); // point to first

                                         // row initially

           while(moreRows) {

              addUser(new User(

                    ((String)rs.getObject(FIRST_NAME)).trim(),

                    ((String)rs.getObject(LAST_NAME)).trim(),

                    ((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)).trim(),

                    ((String)rs.getObject(PASSWORD_HINT)).trim(),

                    ((String)rs.getObject(ROLES)).trim()));

              moreRows = rs.next(); // move to next row

           }

        }

     }

     catch(SQLException ex) {

        // can't throw an exception from a constructor

     }

  }

  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;

 

     if(user != null) {

        found = user.equals(username, password);

     }

     return found ? user : null;

  }

  public User getUser(String username) {

     return (User)users.get(username);

  }

  public Hashtable getUsers() {

     return users;

  }

  public String getPasswordHint(String username) {

     User user = getUser(username);

     return user != null ? user.getPwdHint() : null;

  }

}   

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

Поскольку информация о пользователях содержится в базе данных, конструктору Users передается объект ResultSet, используемый при создании объектов User. Кроме конструктора, в состав класса Users входят другие методы, предоставляющие доступ к информации о пользователях.

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

Как и User, класс Users устойчив к обращению из нескольких потоков, поскольку члены класса не могут изменяться.

В листинге 12.7,в представлен код класса Item, представляющего продукт, предназначенный для продажи.

ЛИСТИНГ 12.7,в. /WEB-INF/classes/beans/app/Item. Java

package beans.app;

// This is an item in an inventory or shopping cart (same thing)

// with four properties: sku, (stock keeping unit) name, price,

// and amount. Items are nearly immutable to eliminate

// multithreading concerns.

public class Item implements java.io.Serializable {

  private final int sku; // stock keeping unit

  private final float price;

  private final String name;

  private float amount;

  public Item(int sku, String name, float price, float amount) {

     this.sku    = sku;

     this.name   = name;

     this.amount = amount;

     this.price  = price;

  }

  public int    getSku()    { return sku;    }

  public String getName()   { return name;   }

  public float  getPrice()  { 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,г,

Базовый набор классов Mode! 2     393

Листинг 12.7,г. /WSB-INF/clasaas/beana/app/Inventory. 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 addItem(Item item) {

     items.add(item);

  }

  public void removeItem(Item item) {

     items.remove(item);

  }

  public Vector getItems() {

     return items;   

  }

}     

Класс Inventory соответствует образу разработки facade и представляет собой упрощенный интерфейс для вектора, элементами которого являются объекты товаров (образ разработки facade был рассмотрен в главе 3). Кроме того, класс Inventory предоставляет метод getltems, который возвращает вектор товаров. Класс, вызывающий этот метод, не может модифицировать вектор.

Код класса ShoppingCart приведен в листинге 12.7,д.

Листинг 12.7,д. /WEB-iNF/classes/beana/app/ShoppingCart.

package beans.app;

public class ShoppingCart extends Inventory {

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

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

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

Листинг 12.7,е. /WEB-INF/clasзаз/beans/app/Constants.Java

package beans.app;

// These constants are mostly used by this application's actions

// -- see /WEB-INF/classes/actions.

public interface Constants {

  // this prefix provides some degree of uniqueness for the

  // constants defined below.

  static final String prefix = "beans.app";

  static final String

     // Keys for attributes

     LOCALE_KEY           = prefix + ".locale",

     SHOPPING_CART_KEY    = prefix + ".cart",

     USERS_KEY            = prefix + ".users",

     USERNAME_KEY         = prefix + ".username",

     PASSWORD_KEY         = prefix + ".password",

     CONFIRM_PASSWORD_KEY = prefix + ".cnfrmpwd",

     PASSWORD_HINT_KEY    = prefix + ".pwdhint",

     // Default values

     DEFAULT_I18N_BASE = "app";

}   

Константы, определенные в Constants, доступны классам, реализующим данный интерфейс. Например, класс PurchaseAction реализует интерфейс Constants и использует переменную SHOPPING_CART_KEY.

Просмотры: JSP-документы и шаблоны

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

На рис. 12.9 показаны файлы, с помощью которых создаются девять JSP-документов, входящих в состав приложения. Каждый из подкаталогов каталога /WEB-INF/ j sp, за исключением shared и templates, содержит все JSP-файлы, соответствующие одному JSP-документу. Например, в каталоге /WEB-INF/jsp/accountCreated находятся файлы page. j sp и content. j sp JSP-документа, предназначенного для создания учетной записи.

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

Каждый из JSP-документов определяется посредством области, использующей шаблон, и содержимого, которое включается в шаблон. Все области приложения используют один и тот же шаблон, который находится в файле /WEB-INF/jsp/templates/ hscf. j sp. Этот шаблон организует отображение заголовка, полосы в левой части Web-страницы, содержимого и нижнего колонтитула.

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

Все области приложения определены в одном файле /WEB-INF/jsp/region-Def initions . j sp, код которого представлен в листинге 12.7,ж.

Листинг 12.7,ж./WEB-lNF/jsp/regionDefinitions.jsp

<%@ taglib uri='regions' prefix='region' %>

<region:define id='STOREFRONT_REGION'

        template='/WEB-INF/jsp/templates/hscf.jsp'>

  <region:put section='title'

              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='LOGIN_REGION' region='STOREFRONT_REGION'>

  <region:put section='header'

              content='/WEB-INF/jsp/login/header.jsp'/>

  <region:put section='sidebar'

              content='/WEB-INF/jsp/login/sidebar.jsp'/>

  <region:put section='content'

              content='/WEB-INF/jsp/login/content.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'

              content='/WEB-INF/jsp/homepage/content.jsp'/>

</region:define>

<region:define id='CREATE_ACCOUNT_REGION' region='LOGIN_REGION'>

  <region:put section='content'

              content='/WEB-INF/jsp/createAccount/content.jsp'/>

  <region:put section='sidebar'

              content='/WEB-INF/jsp/createAccount/sidebar.jsp'/>

</region:define>

<region:define id='LOGIN_FAILED_REGION' region='LOGIN_REGION'>

  <region:put section='content'

              content='/WEB-INF/jsp/loginFailed/content.jsp'/>

</region:define>

<region:define id='ACCOUNT_CREATED_REGION' region='LOGIN_REGION'>

  <region:put section='content'

              content='/WEB-INF/jsp/accountCreated/content.jsp'/>

</region:define>

<region:define id='CHECKOUT_REGION' region='LOGIN_REGION'>

  <region:put section='content'

              content='/WEB-INF/jsp/checkout/content.jsp'/>

</region:define>

<region:define id='PURCHASE_REGION' region='LOGIN_REGION'>

  <region:put section='content'

              content='/WEB-INF/jsp/purchase/content.jsp'/>

</region:define>

<region:define id='SHOW_HINT_REGION' region='LOGIN_REGION'>

  <region:put section='content'

              content='/WEB-INF/jsp/showHint/content.jsp'/>

</region:define>   

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

Большинство областей, определенных в листинге 12.7,ж, являются расширениями других областей и используют характеристики своих предков. Так, например, область HOMEPAGE_REGION использует заголовок, фон и нижний колонтитул области STOREFRONT_REGION. Все остальные области используют содержимое LOGIN_REGION или STOREFRONT_REGION.

Определение всех областей в одном файле упрощает процесс сопровождения. Способность областей расширять другие области позволяет совместно использовать содержимое и делает определения областей более удобными для изучения.

Единый шаблон для рассматриваемого в данной главе приложения приведен в листинге 12.7,з.

ЛИСТИНГ 12.7,3. /WEB-INF/ jsp/template/h*cf. jsp

<html><head>

 <%@ taglib uri='regions' prefix='region' %>

 <title><region:render section='title'/></title>

</head>

<body background='<region:render section='background'/>'>

<table>

  <tr valign='top'>

     <td><region:render section='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>   

Шаблон, приведенный в листинге 12.7,з, использует для отображения содержимого HTML-таблицу. Содержимое предоставляют JSP-файлы, на которые ссылаются области. Например, при воспроизведении области LOGIN_REGION шаблон использует содержимое, заданное в определении этой области, а именно заголовок, фон, нижний колонтитул и содержимое страницы регистрации.

Заметьте, что в каждом каталоге приложения, соответствующем JSP-документу, находится JSP-файл с именем page. j sp. Этот файл предназначен для воспроизведе-

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

ния соответствующей области. Например, в листинге 12.7,и приведено содержимое файла page. j sp для страницы регистрации. Этот JSP-файл воспроизводит область

LOGIN  REGION.

Листинг 12.7,и. /WEB-lNF/jsp/login/page. jsp

<%@ taglib uri='regions' prefix='region' %>

<region:render region='LOGIN_REGION'/>

В приложении, реализующем интерактивный магазин, файлы page. j sp отвечают за поддержку функциональных возможностей, общих для всех документов. Например, файл page, jsp, представленный в листинге 12.7,к, позволяет обращаться к странице проверки только зарегистрированным пользователям.

Листинг 12.7, к. /wsb-inf/j эр/ checkout /page. jsp

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

<%@ taglib uri='regions' prefix='region' %>

<security:enforceLogin

  loginPage='/WEB-INF/jsp/login/page.jsp'

  errorPage='/WEB-INF/jsp/loginFailed/page.jsp'/>

<region:render region='CHECKOUT_REGION'/>

В файле, показанном в листинге 12.7,к, присутствует пользовательский дескриптор security:enforceLogin. Этот дескриптор обрабатывает часть документа, следующую за ним, только в том случае, если пользователь зарегистрирован, в противном случае запрос перенаправляется странице регистрации. Подробно дескриптор security:enforceLogin описан в главе 9. Средства поддержки аутентификации в приложении, реализующем интерактивный магазин, обсуждаются далее в этой главе.

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

Области определяют содержимое, отображаемое с помощью шаблонов. Возможность отдельно определять области позволяет создавать единое описание нескольких JSP-документов. Такой подход упрощает создание и сопровождение JSP-документов.

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

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

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

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

Контроллеры: сервлеты и действия

В приложениях, соответствующих архитектуре "модель-просмотр-контроллер" (MVC), данные хранятся в модели и отображаются с помощью просмотров. Контроллеры объединяют модель и просмотры. Контроллер реагирует на события и может обращаться к модели перед тем, как передать управление просмотру.

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

Сервлеты

В рассматриваемом приложении используются четыре сервлета, расположенные в каталоге /WEB-INF/classes (рис. 12.10).

l-i

Рис. 12.10. Сервлеты и файл web.xml

Сервлет ActionServlet представляет собой часть базового набора Model 2 и обрабатывает все HTTP-запросы, оканчивающиеся символами " . do". Поскольку данный сервлет был подробно описан в главе 6, здесь он обсуждаться не будет.

Класс AuthenticateServlet представляет собой абстрактный базовый класс, подклассом которого является AppAuthenticateServlet. Эти сервлеты, используемые для поддержки аутентификации, будут рассмотрены далее в этой главе.

Сервлет SetupServlet загружается при запуске приложения и инициализирует базу данных. Код этого сервлета приведен в листинге 12.8,6.

Конфигурация всех сервлетов, используемых в данном приложении, задается в файле web.xml, код которого представлен в листинге 12.8,а.

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

ЛИСТИНГ 12.8,а, /WEB-lNF/web.icnl

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app

 PUBLIC "-//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-class>AppAuthenticateServlet</servlet-class>

  </servlet>

  <servlet>

     <servlet-name>action</servlet-name>

     <servlet-class>ActionServlet</servlet-class>

     <init-param>

        <param-name>action-mappings</param-name>

        <param-value>actions</param-value>

     </init-param>

  </servlet>

  <servlet>

     <servlet-name>setup</servlet-name>

     <servlet-class>SetupServlet</servlet-class>

     <init-param>

        <param-name>jdbcDriver</param-name>

        <param-value>COM.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>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>

  </servlet-mapping>

  <servlet-mapping>

     <servlet-name>authenticate</servlet-name>

     <url-pattern>/authenticate</url-pattern>

  </servlet-mapping>

 <welcome-file-list>

       <welcome-file>index.jsp</welcome-file>

 </welcome-file-list>

 <taglib>

   <taglib-uri>utilities</taglib-uri>

   <taglib-location>/WEB-INF/tlds/utilities.tld</taglib-location>

 </taglib>

 <taglib>

   <taglib-uri>application</taglib-uri>

   <taglib-location>/WEB-INF/tlds/app.tld</taglib-location>

 </taglib>

 <taglib>

   <taglib-uri>i18n</taglib-uri>

   <taglib-location>/WEB-INF/tlds/i18n.tld</taglib-location>

 </taglib>

 <taglib>

   <taglib-uri>security</taglib-uri>

   <taglib-location>/WEB-INF/tlds/security.tld</taglib-location>

 </taglib>

 <taglib>

   <taglib-uri>database</taglib-uri>

   <taglib-location>/WEB-INF/tlds/database.tld</taglib-location>

 </taglib>

 <taglib>

   <taglib-uri>html</taglib-uri>

   <taglib-location>/WEB-INF/tlds/html.tld</taglib-location>

 </taglib>

 <taglib>

   <taglib-uri>logic</taglib-uri>

   <taglib-location>/WEB-INF/tlds/logic.tld</taglib-location>

 </taglib>

 <taglib>

   <taglib-uri>dom</taglib-uri>

   <taglib-location>/WEB-INF/tlds/dom.tld</taglib-location>

 </taglib>

 <taglib>

   <taglib-uri>xslt</taglib-uri>

   <taglib-location>/WEB-INF/tlds/xslt.tld</taglib-location>

 </taglib>

 <taglib>

   <taglib-uri>regions</taglib-uri>

   <taglib-location>/WEB-INF/tlds/regions.tld</taglib-location>

 </taglib>

 <taglib>

   <taglib-uri>xpath</taglib-uri>

   <taglib-location>/WEB-INF/tlds/xpath.tld</taglib-location>

 </taglib>

 <taglib>

   <taglib-uri>tokens</taglib-uri>

   <taglib-location>/WEB-INF/tlds/tokens.tld</taglib-location>

 </taglib>

</web-app>             

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

...  Информация об остальных библиотеках здесь не приводится  . </web-app>

В файле web. xml определяются имена и классы сервлетов и при необходимости приводятся инициализацию иные параметры. Так, например, для сервлета Setup-Servlet указаны параметры, определяющие JDBC-драйвер, JDBC URL, регистрационное имя и пароль.

Кроме того, в файле web. xml задаются библиотеки пользовательских дескрипторов, применяемые в приложении. Поскольку количество библиотек достаточно велико, файл web.xml в листинге 12.8,а приведен не полностью. Помимо библиотеки application в приложении используются следующие библиотеки:

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 javax.servlet.ServletConfig;

import javax.servlet.ServletContext;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import beans.app.User;

import beans.app.Users;

import beans.jdbc.DbConnectionPool;

public class SetupServlet extends HttpServlet

                      implements beans.app.Constants,

                                 tags.jdbc.Constants {

  private DbConnectionPool pool;

  public void init(ServletConfig config) throws ServletException{

     super.init(config);

     ServletContext ctx = config.getServletContext();

     createDbConnectionPool(config, ctx);

     try {

        ctx.setAttribute(USERS_KEY, loadUsers(ctx));

     }

     catch(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("jdbcURL"),

                 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 {

           // wait for a maximum of 10 seconds for a connection

           // if pool is full

              conn = (Connection)pool.getConnection(10000);

        }

        catch(Exception ex) {

           throw new SQLException(ex.getMessage());

        }

        Statement stmt = conn.createStatement();

        ResultSet rs = stmt.executeQuery("SELECT * FROM USERS");

        Users users = new Users(rs);

        pool.recycleConnection(conn);

        return users;

     }

     return null;

  }

}   

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

Сервлет, код которого приведен в листинге 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.

Интернационализация

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

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

Action  ActionBase ActionFector.ii ActionR outer  SensitiveActionListenei

Классы из базового набора Model 2         

AddToCartAction

CheckoutAction

GoShoppingAction

NewftccountAction

PuichaseAction

Q ueryAocountAction

ShowHintAction

U pdat e L о с а I eA с tion

VaiidateAccou

( Действия, специфические дпя приложения    )

Рис. 12.11. Классы действий а приложении Mode! 2

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

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

  1.  Пользователь щелкает мышью на изображении флага в левой части Web-страницы.

Приложение изменяет регион в соответствии с выбранным флагом.

Приложение повторно отображает текущую Web-страницу.

На рис. 12.13 показаны файлы, участвующие в реализации описанного выше сценария развития.

Интернационализация      405

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

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

Листинг 12.9,a. /WEB-INF/classes/appen.properties

click=click here=here

messages.login-header-title=FruitStand.com messages.today=Todaу is (0, date}

homepage.title=A Model 2 JSP Application

homepage.text=<p>This mockup of a fruit stand uses many of the techniques discussed in Advanced JavaServer Pages( published by Sun Microsystems Press and Prentice-Hall in May 2001.

... Остальная часть сообщения homepage.title здесь не приводится

Интернационализация  407

login.title=FruitStand.com

login,form.title=Please Login

login.button.submit=login

login.textfield.name=Name

login.textfield.pwd=Password

login.footer.message=Thanks for stopping by. Today is

login.failed.title=Login Failed

login.failed.message=Please enter a valid username and password, or

create a new account

... Остальная часть файла пропущена ...

В листинге 12.9,6 приведен фрагмент файла свойств, поддерживающего немецкий язык.

Листинг 12.9,6. /WEB-INF/clasaaa/app_de. properties

click=Klick here=Hier

messages.login-header-title=FruitStand.com messages.today=Huite   ist   [0,   date}

homepage.title=Eine  Model   2   JSP  Anwendung

В отличие от английского и немецкого вариантов, в файле свойств, предназначенном для поддержки китайского языка, текстовые сообщения задаются в виде управляющих последовательностей Unicode (листинг 12.9,в). Подробно набор Unicode рассматривался в главе S.

■■!■■ -;:;■:■ .;■::. '.   s;,.as :>.,...;,-,.. .. .    .-.-

Листинг 12.9,В. /WEB-INF/classes/app-zh.properties

click=\u70b9\u51fb here=\u8fd9\u91cc

messages.Iogin-header-title=\u6b22\u8fce\u514 9\u4e34 messages.today=\u<leca\u5929\u662f fO, date}

homepage.title=\u4e00\u4e2a\u6a21\u5fOf\u4e8cJSP\u8303\u4f8b

В листинге 12.9,г приведено содержимое файла flags . jsp

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

Листинг 12,9,г, /WEB-INF /jsp/shared/flags, jsp

<% String thisPage = request.getServletPath();

  String updateLocaleAction = "update-locale-action.do?page=" +

                              thisPage + "&country="; %>

<table width='160'>

  <tr><td>

     <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/flags/chinese_flag.gif'/></a></tr>

  </td><td height='25'></td>

</table>   

JSP-файл, показанный в листинге 12.9,г, задает действие для каждого из флагов в

соответствии с текущей Web-страницей и страной, которую представляет этот флаг. Например, файл flags, jsp включается в документ /WEB-INF/ j sp/storefront/ sidebar, поэтому, если пользователь щелкает на изображении китайского флага, вызывается следующее действие:

http://localhost:808Q/case-study/update-locale-action.do?page=/WEB-INF/jsp/storefront/page,jsp&country=ZH

Действие, связанное с флагом в файле flags.jsp, приводит к вызову класса UpdateLocaleAction, код которого представлен в листинге 12.9, д.

Листинг 12.9,д. /WEB-INF/classes/action/UpdateLocalAction. 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");

     req.getSession(true).setAttribute(LOCALE_KEY, locale);

     res.setLocale(locale);

     return new ActionRouter(forwardTo, true, false);

  }

}        

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

Действие UpdateLocaleAction получает посредством параметра запроса информацию о стране и, соответственно, о требуемом языке (запрос генерирует JSP-файл, представленный в листинге 12.9,д). Имея сведения о языке, действие устанавливает атрибут сеанса, который определяет текущий регион, задает регион для ответа и перенаправляет запрос исходному документу.

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

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

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

  1.  Если пользователь пытается выполнить проверку, дескриптор определяет, за
    регистрирован ли пользователь.

Если пользователь не зарегистрирован, дескриптор передает управление стра
нице регистрации.

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

Приложение отображает форму, предназначенную для создания новой учетной
записи.

Пользователь заполняетформуи активизирует кнопку создания учетной записи.

Приложение повторно выводит форму регистрации, и пользователь регистри
руется.

Файлы, участвующие в реализации сценария развития "аутентификация пользователя", показаны на рис. 12.14.

После активизации кнопки Checkout на странице "витрины" пользователь перенаправляется к документу проверки. Код этого документа показан в листинге 12.10,а.

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

Листинг 12.10,a. /WEB-INF/jsp/checkout/page.jэр

<%@ taglib uri='security' prefix='security'%> <%@ taglib uri='regions' prefix='region' %>

<security:enforceLogin

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

loginPage=' /WEB-INF/jsp/login/page.jsp' errorPage='/WEB-lNF/jsp/loginFailed/page.jsp'/>

<region:render region='CHECKOUT_REGION'/>

Класс поддержки дескриптора security: enforceLogin, присутствующего в листинге 12.10,а, показан влистинге 12.10,6.

Листинг 12.10,6. /WEB-INF/jsp/classes/tags/security/ EnforeaLoginTag. Java (фрагмент}

package tags.security;

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

         implements Constants {

  private String loginPage, errorPage;

  public void setLoginPage(String loginPage) {

     this.loginPage = loginPage;

  }

  public void setErrorPage(String errorPage) {

     this.errorPage = errorPage;

  }

  public int doEndTag() throws JspException {

     HttpSession session = pageContext.getSession();

     HttpServletRequest req = (HttpServletRequest)pageContext.

                                                getRequest();

     String protectedPage = req.getServletPath();

     if(session.getAttribute(USER_KEY) == null) {

        session.setAttribute(LOGIN_PAGE_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());

        }

     }

     return EVAL_PAGE;

  }

  public void release() {

     loginPage = errorPage = null;

  }

}        

Дескриптор security: enforceLogin проверяет, присутствует ли объект User в области видимости сеанса; при наличии объекта обрабатывается остальная часть документа, в противном случае управление передается документу регистрации. Содержимое документа регистрации приведено в листинге 12.10,в.

ЛИСТИНГ 12.10.В. /WEB-INF/jsp/login/content.jsp

<%@ page contentType='text/html; charset=UTF-8' %>

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

<font size='5' color='blue'>

  <i18n:message key='login.form.title'/>

</font><hr>

<jsp:include page='form.jsp' flush='true'/>

<jsp:include page='../shared/createAccountLink.jsp' flush='true'/>

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

Представленный выше 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/ jsp/shared/createAccountLmk. jsp

taglib uri='il8n'   prefix='il8n'   %> <%@  taglib uri='utilities'   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. jsp формирует ссылку на ресурс query-account-action . do. Внешний вид страницы регистрации приведен на рис. 12.15.

Ссылка, формируемая в файле createAccountLink. jsp, указывает на ресурс query-account-action.do, который отображается в QueryAccountAction. Действие QueryAccountAction приведено в листинге 12.10.Д.

ЛИСТИНГ 12.10,Д. /WEB-INF/classes/CueryAccountAction.Java

package actions;

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 QueryAccountAction extends ActionBase { public QueryAccountAction() {

// Данное действие обращается к J5P-документу 

// с чувствительной формой 

hasSensitiveForms = true; } public ActionRouter perform(HttpServlet servlet,

HttpServletRequest req, HttpServletResponse 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,е.

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

Рассматриваемый JSP-документ содержит достаточно длинное описание таблицы, которое в данном листинге не приводится (полностью таблица представлена в листинге 12.11,а). Внешний вид^Р-документа, приведенного в листинге 12.10,е, показан на рис. 12.16.

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

Таблица, показанная на рис. 12.16, находится в составе формы. Атрибут action формы имеет значение validate-account, jsp, поэтому при активизации кнопки create account броузер направляет запрос указанному документу. Код данного JSP-документа представлен в листинге 12.10,ж.

Представленный выше JSP-документ создает компонент bean CreateAccount-Form и заполняет его данными из формы, показанной на рис. 12.16. Этот компонент используется для проверки данных формы; подробно процедура проверки будет описана далее в этой главе. Затем JSP-документ, код которого показан в листинге 12.10,ж, перенаправляет запрос ресурсу validate-account-action.do, который отображается в действие, представленное в листинге 12.10,з.

ЛИСТИНГ 12.10,3. /WEB-INF/classes/actions/ValiddataAccountAction. Java

{фрагмент)

package actions;

import java.io.IOException;

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;

import beans.app.forms.CreateAccountForm;

public class ValidateAccountAction extends ActionBase

                        implements 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.setAttribute("createAccount-form-error", errMsg);

     }

     return errorDetected ?

        new ActionRouter("query-account-page") :

        new ActionRouter("/new-account-action.do", true, false);

  }

}        

Класс ValidateAccountAction проверяет, корректно ли заполнена форма; при наличии ошибки управление передается query-account-page, что приводит к повторному отображению формы, представленной на рис. 12.16, и соответствующего сообщения об ошибке. Если форма заполнена правильно, управление передается ресурсу new-account-action.do, который отображается в действие, приведенное в листинге 12.10,и.

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

ЛИСТИНГ 12.10,И. /WЕВ-INF/classes/actions/NewAccoyntAction.java

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 ActionBase

                          implements beans.app.Constants {

  public NewAccountAction() {

     isSensitive = true; // this is a sensitive action

  }

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

     }

     HttpSession session = req.getSession();

     String  firstName = req.getParameter("firstName");

     String  lastName  = 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("creditCardNumber");

     String  creditCardExpiration =

             req.getParameter("creditCardExpiration");

     String  uname = req.getParameter("userName");

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

     users.addUser(

          new User(firstName, lastName, address, city, state,

                   country, creditCardType, creditCardNumber,

                   creditCardExpiration, uname, pwd, pwdHint,

                   "customer")); // customer is a role

     req.setAttribute(USERNAME_KEY, uname);

     return new ActionRouter("account-created-page");

  }

}            

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

Действие NewAccountAction, представленное в листинге 12.10,и, проверяет, присутствует ли набор пользователей в области видимости приложения; если набор отсутствует, генерируется исключение.

Если набор пользователей обнаружен в области видимости приложения, действие на основе полученных данных формы создает новый объект, представляющий пользователя, и добавляет его в набор. Данное действие также сохраняет в области видимости приложения регистрационное имя и пароль, откуда они впоследствии извлекаются формой регистрации, представленной в листинге 12.10,л. По окончании работы действие передает управление документу account-created-page. Содержимое соответствующего JSP-фай л а приведено в листинге 12.10,к.

Листинг 12.10,к. /WEB-T.NF/Jsр/AccountCreated /content, jsp

<%@ page contentType='text/html; charset=UTF-8' %>

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

<font size='4' color='blue'>

  <i18n:message base='app' key='accountCreated.text'/>

</font>

<p><jsp:include page='../login/form.jsp' flush='true'/></p>

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

Листинг 12.10,л. /WEB-INF/jsp/login/form, jsp

<%@ page contentType='text/html; charset=UTF-8' %>

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

<%@ taglib uri='utilities' prefix='util' %>

<form action='<%= response.encodeURL("authenticate") %>'

     method='post'>

  <table>

     <tr>

        <td><i18n:message base='app' key='login.textfield.name'/>

        </td>

        <td><input type='text' name='userName'

           value='<util:sessionAttribute property=

                 "<%= beans.app.Constants.USERNAME_KEY %>"/>'/>

        </td>

     </tr><tr>

        <td><i18n:message base='app' key='login.textfield.pwd'/>

        </td>

        <td><input type='password' name='password' size='8'

           value='<util:sessionAttribute property=

                 "<%= beans.app.Constants.PASSWORD_KEY %>"/>'/>

        </td>

     </tr>

  </table>

  <br>

  <input type='submit' value=

        '<i18n:message key="login.button.submit"/>'/>

</form>

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

В форме, представленной в листинге 12.10.Л, нет ничего примечательного. Единственная особенность, достойная внимания, состоит в том, что поля редактирования, соответствующие имени пользователя и паролю, заполняются данными, хранящимися в области видимости сеанса, поэтому пользователю нет необходимости повторно вводить их. Действие NewAccountAction, приведенное в листинге 12.10,и, помещает имя пользователя и пароль в область видимости сеанса.

Атрибут action регистрационной формы имеет значение authenticate; в файле web.xml это логическое имя отображается в AppAuthenticateServlet. Код серв-лета AppAuthenticateServlet  представлен в листинге 12.10,м.

Листинг 12.10,М- /WEB-lNF/classes/AppAuthenticafceServlet. Java

import javax.servlet.ServletContext;

import beans.app.Users; import beans.app.User;

public class AppAuthenticateServlet extends AuthenticateServlet

implements beans.app.Constants,

tags.jdbc.Constants {

public Object getUser(String username, String password) ( ServletContext ctx = getServletContext(); Users users = (Users)ctx.getAttribute(USERS^ECEY); return users.getUser(username, password);

Класс AppAuthenticateServlet является подклассом абстрактного класса AuthenticateServlet и реализует метод getUser. Если пользователь, соответствующий указанным регистрационному имени и паролю, существует, метод getUser возвращает объект User, в противном случае возвращается значение null. Код класса AuthenticateServlet приведен в листинге 12,10,н.

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

Листинг 12.10,н. /WEB-lNF/classes/AuthenticateServlet.Java

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 java.io.IOException;

public abstract class AuthenticateServlet extends HttpServlet

                                implements beans.app.Constants,

                                        tags.security.Constants {

  abstract Object getUser(String username, String pwd);

  public void service(HttpServletRequest req,

                      HttpServletResponse res)

                      throws IOException, ServletException {

     HttpSession session = req.getSession();

     String      uname   = req.getParameter("userName");

     String      pwd     = req.getParameter("password");

     Object      user    = getUser(uname, pwd);

     session.setAttribute(USERNAME_KEY, uname);

     session.setAttribute(PASSWORD_KEY, pwd);

     if(user == null) { // not authorized

        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.encodeURL(forwardTo)).forward(req,res);

     }

     else { // authorized

        String protectedPage = (String)session.

                                getAttribute(PROTECTED_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);

     }

  }

}        

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. /WEB-INF/jsp/createAccount/content.jsp

<%@ page contentType='text/html; charset=UTF-8' %>

<%@ taglib uri='application' prefix='app'    %>

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

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

<%@ taglib uri='utilities'   prefix='util'   %>

<jsp:useBean id='form' scope='request'

                      class='beans.app.forms.CreateAccountForm'/>

<font size='5' color='blue'><u>

  <i18n:message key='createAccount.header.title'/></u>

</font><p>

<% String error = (String)request.getAttribute(

                                 "createAccount-form-error");

  if(error != null) { %>

     <font size='5' color='red'>

        <i18n:message key='createAccount.error.fix'/>

     </font><font 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'><font size='4' color='blue'>

        <i18n:message key='createAccount.header.personal'/>

     </font></td></tr><tr height='10'></tr>

     <tr><td>

        <i18n:message key='createAccount.field.firstName'/></td>

     <td><input type='text' name='firstName'

               value='<%= form.getFirstName() %>'/>

     </td></tr>

     <tr><td>

        <i18n:message key='createAccount.field.lastName'/></td>

     <td><input type='text' name='lastName'

               value='<%= form.getLastName() %>'/>

     </td></tr>

     <tr><td>

        <i18n:message key='createAccount.field.address'/></td>

     <td><input type='text' name='address' size='39'

               value='<%= form.getAddress() %>'/>

     </td></tr>

     <tr><td>

        <i18n:message key='createAccount.field.city'/></td>

     <td><input type='text' name='city'

               value='<%= form.getCity() %>'/>

     </td></tr>

     <tr><td>

        <i18n:message key='createAccount.field.state'/></td>

     <td><input type='text' name='state'

               value='<%= form.getState() %>'/>

     </td></tr>

     <tr><td>

        <i18n:message key='createAccount.field.country'/></td>

        <td><select name='country'>

        <i18n:bean id='germany'

                  key='createAccount.option.germany'/>

         <option <%=form.getCountrySelectionAttr(germany)%>/>

           <%= germany %>

        <i18n:bean id='uk'

                  key='createAccount.option.unitedKingdom'/>

         <option <%=form.getCountrySelectionAttr(uk)%>/>

           <%= uk %>

           

        <i18n:bean id='china' key='createAccount.option.china'/>

         <option <%=form.getCountrySelectionAttr(china)%>/>

           <%= china %>

     </select></td></tr>

     <tr><td>

        <i18n:message key='createAccount.field.phone'/></td>

        <td><input type='text' name='phone'

                 value='<%= form.getState() %>'/></td>

     </tr><tr height='20'></tr>

     <tr><td colspan='2'><font size='4' color='blue'>

        <i18n:message key='createAccount.header.credit'/></td>

     </font></td></tr><tr height='10'></tr>

     <tr><td>

        <i18n:message

                  key='createAccount.field.creditCardType'/></td>

        <td><select name='creditCardType'>

           <option <%= form.getCreditCardTypeSelectionAttr(

                       "Discover")%>>

              Discovery

           <option <%= form.getCreditCardTypeSelectionAttr(

                       "Master Card")%>>

              Master Card

           <option <%= form.getCreditCardTypeSelectionAttr(

                       "Visa")%>>

              Visa

        </select></td></tr>

     <tr><td>

        <i18n:message

                key='createAccount.field.creditCardNumber'/></td>

        <td><input type='text' name='creditCardNumber'

                  value='<%= form.getCreditCardNumber() %>'/>

        </td>

     </tr></tr>

     

     <tr><td>

        <i18n:message

            key='createAccount.field.creditCardExpiration'/></td>

        <td><input type='text' name='creditCardExpiration'

                  value='<%= form.getCreditCardExpiration() %>'/>

        </td>

     </tr><tr height='20'></tr>

     <tr><td colspan='2'><font size='4' color='blue'>

        <i18n:message

                    key='createAccount.header.unameAndPwd'/></td>

     </font></td></tr><tr height='10'></tr>

     <tr><td>

        <i18n:message

            key='createAccount.field.username'/></td>

        <td><input type='text' name='userName'

                 value='<%= form.getUserName() %>'/></td>

        </td></tr>

        

        <tr><td>

           <i18n:message

                        key='createAccount.field.password'/></td>

        <td><input type='password' name='password' size='8'

                 value='<%= form.getPassword() %>'/></td>

        </td></tr>

        

        <tr><td>

           <i18n:message

                      key='createAccount.field.pwdConfirm'/></td>

        <td><input type='password' name='pwdConfirm' size='8'

                 value='<%= form.getPwdConfirm() %>'/></td>

        

        <tr><td>

           <i18n:message key='createAccount.field.pwdHint'/></td>

        <td><input type='text' name='pwdHint'

                 value='<%= form.getPwdHint() %>'/></td>

        </td>

     </tr>

  </table>

  <br>

  <input type='submit' value='create account'>

<tokens:token/>

</form>        

 

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

JSP-документ, приведенный в листинге 12.11,а, обращается к компоненту bean CreateAccount Form в области видимости запроса. Если форма заполнена некорректно, JSP-документ использует этот компонент для заполнения полей так, что пользователю не приходится повторно вводить данные.

Затем документ, предстаадегшый в листинге 12.11,а, проверяет наличие в области видимости запроса атрибута с именем createAccount-form-error, соответствующего ошибке. Если атрибут найден, сообщение отображается на Web-странице над формой.

Атрибут action регистрационной формы указывает на JSP-документ, который создает компонент bean CreateAccountForm, и инициализирует его в соответствии с информацией, введенной посредством формы. Этот документ был приведен в листинге 12,Ш,ж. но для удобства рассмотрения он представлен также в листинге 12.11,6.

ЛИСТИНГ 12.11,6. /validate-aecount.jsp

<jsp:useBean id='form' scope='request'

class=' beans.app.forms.CreateAccountForm'/>

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

<jsp:forward page='/validate-account-action.do'/>

JSP-документ /validate-account.jsp перепалраиляет запрос ресурсу validate-account-action. do, который отображается в действие, приведенное в листинге 12.11,в.

Листинг 12.11,В. /WEB-lNF/classes/aetions/ValidateAccountAetion

package actions;

import java.io.IOException;

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;

import beans.app.forms.CreateAccountForm;

public class ValidateAccountAction extends ActionBase

                        implements 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.setAttribute("createAccount-form-error", errMsg);

     }

     return errorDetected ?

        new ActionRouter("query-account-page") :

        new ActionRouter("/new-account-action.do", true, false);

  }

}       

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

Класс действия, представленный в листинге 12.11,в, извлекает данные формы из области видимости запроса и вызывает метод validate. Если метод возвращает значение false, т.е. если форма некорректна, действие перенаправляет запрос документу query-account-page, который был приведен в листинге 12.11,а.

Компонент bean CreateAccountForm представлен в листинге 12.11,г.

Листинг 12.11,Г. /WEB-INF/ classes /beans /app/fоrms/ CreateAccountForm.

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

  private TextElement                  city = new TextElement();

  private TextElement                 state = new TextElement();

  private TextElement                 phone = new TextElement();

  private TextElement      creditCardNumber = new TextElement();

  private TextElement  creditCardExpiration = new TextElement();

  private TextElement              userName = new TextElement();

  private TextElement              password = new TextElement();

  private TextElement            pwdConfirm = new TextElement();

  private TextElement               pwdHint = new TextElement();

  private OptionsElement   creditCardType = new OptionsElement();

  private OptionsElement          country = new OptionsElement();

  private String error = "";

  public String getFirstName()   { return firstName.getValue();}

  public String getLastName()    { return lastName.getValue(); }

  public String getAddress()     { return address.getValue();  }

  public String getCity()        { return city.getValue();     }

  public String getState()       { return state.getValue();    }

  public String[] getCountry()   { return country.getValue();  }

  public String getPhone()       { return phone.getValue();    }

  public String[] getCreditCardType() { return creditCardType.

                                               getValue(); }

  public String getCreditCardNumber() { return creditCardNumber.

                                               getValue(); }

  public String getCreditCardExpiration() {

     return creditCardExpiration.getValue();

  }

  public String getUserName()    { return userName.getValue(); }

  public String getPassword()    { return password.getValue(); }

  public String getPwdConfirm()  { return pwdConfirm.getValue();}

  public String getPwdHint()     { 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) { firstName.setValue(s);   }

  public void setLastName(String s)  { lastName.setValue(s);    }

  public void setAddress(String s)   { address.setValue(s);     }

  public void setCity(String s)      { city.setValue(s);        }

  public void setState(String s)     { state.setValue(s);       }

  public void setCountry(String[] s) { country.setValue(s);     }

  public void setPhone(String s)     { phone.setValue(s);       }

  public void setCreditCardType(String[] s) { creditCardType.

                                              setValue(s); }

  public void setCreditCardNumber(String s) { creditCardNumber.

                                              setValue(s); }

  public void setCreditCardExpiration(String s) {

     creditCardExpiration.setValue(s);

  }

  public void setUserName(String s)   { userName.setValue(s);   }

  public void setPassword(String 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();

     }

     if(!lastName.validate()) {

        if(error.length() > 0)

           error += "<br>";

        error += lastName.getValidationError();

     }

     return error == "";

  }

  public String getValidationError() {

     return error;   

  }

}    

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

Компонент, представленный в листинге 12.11,г, использует для представления полей, соответствующих имени и фамилии, экземпляры класса NameElement. Класс NameElement был описан в главе 3. Для удобства рассмотрения код класса приведен в листинге 12.11,д.

ЛИСТИНГ 12.11,Д. /WEB-INF/classes/beans/html/NameElenient.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 = getValue();

     error = "";

     if(value.length() == 0) {

        valid = false;

        error = fieldName + " must be filled in";

     }

     else {

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

           char c = value.charAt(i);

           if(c == ' ' || (c > '0' && c < '9')) {

              valid = false;   

              if(c == ' ')

                 error = fieldName + " cannot contain spaces";

              else           

                 error = fieldName + " cannot contain digits";

           }

        }

     }

     return valid;

  }

  public String getValidationError() {

     return error;   

  }

}         

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

Класс NameElement является подклассом TextElement и переопределяет метод validate. Переопределенный метод запрещает использование в имени пробелов и цифр. Дополнительную информацию о классах NameElement и TextElement можно найти в главе 3.

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

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

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

package actions;

public class QueryAccountAction extends ActionBase { public QueryAccountAction() {

// Действие перенаправляет запрос JSP // с чувствительной формой, hasSensitiveForms ■= true; f

После выполнения действия с чувствительной формой (таковым является QueryAccountAction) набор классов Model 2 создает два идентичных маркера (реализованных в виде строк). Один из этих маркеров помещается в область видимости сеанса, а другой — в область видимости запроса. Затем перед выполнением чувст-

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

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

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

package actions;

public class NewAccountAction extends ActionBase

implements beans.app.Constants { public NewAccountAction() {

isSensitive - true; // Чувствительное действие \

}

В чувствительной форме, фрагмент которой приведен в листинге 12.1 1 .е. применен пользовательский дескриптор tokens:token, который копирует маркер, существующий в области видимости запроса, в запрос, генерируемый в результате активизации формы.

Листинг 12.11 ,е. /WEB-inF/jsp/creat*Account/content. jsp (фрагмент)

<%@ 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, в котором указано, что при создании учетной записи должен использоваться защищенный канал, имеет следующий вид:

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/ createAccount/content. j sp. Ограничения, связанные с обеспечением безопасности, подробно рассматриваются в главе 9.

К сожалению, на момент написания данной книги и в Tomcat S.2 final, и в Tomcat 4.0 средства защиты работали некорректно. Вероятно, к тому времени, когда вы будете читать этот текст, ситуация для Tomcat 4.0 изменится к лучшему.

XMLhDOM

Часто, создавая Web-приложения, бывает целесообразно хранить данные в формате XML. Во-первых, в этом случае информация может быть преобразована средствами Document Object Model (DOM) и представлена в виде стандартной структуры, для поддержки которой существуют самые разнообразные инструментальные средства. Во-вторых, XML становится стандартом de facto для передачи данных между различными бизнес-приложениями.

Вопросы совместного использования XML и JSP были рассмотрены в главе 11. Здесь же мы лишь обсудим возможности применения XML в приложении, реализующем интерактивный магазин,

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

Листинг 12.12,a. /WEB-INF/ jsp/storefront/con ten t. jsp (DOM-версия)

 <%@ page contentType='text/html; charset=UTF-8' %>

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

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

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

<font size='4' color='blue'>

  <i18n:message base='app' key='storefront.form.title'/>

</font><p>

<dom:parse id='inventory' scope='application'>

  <%@ include file='inventory-to-xml.jsp' %>

</dom:parse>

<table border='1' cellPadding='3'>

<th><i18n:message base='app'

                  key='storefront.table.header.picture'/></th>

<th><i18n:message base='app'

                  key='storefront.table.header.item'/></th>

<th><i18n:message base='app'

                 key='storefront.table.header.description'/></th>

<th><i18n:message base='app'

                  key='storefront.table.header.price'/></th>

<th><i18n:message base='app'

                  key='storefront.table.header.addToCart'/></th>

<% String currentItem = 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; %>

        <tr><td>

           <img src=

              '<%= "graphics/fruit/" + name.trim() + ".jpg" %>'/>

        </td>

     </dom:ifNodeNameEquals>

     <dom:ifNodeNameEquals node='<%= itemField %>' names='NAME'>

        <dom:elementValue id='name' element='<%= itemField %>'/>

        <% currentItem = name; %>

           <td><%= name %></td>

           <td>

    <i18n:message key='<%=name + ".description"%>'/>

   </td>

     </dom:ifNodeNameEquals>

     <dom:ifNodeNameEquals node='<%= itemField %>' names='PRICE'>

        <dom:elementValue id='price' element='<%= itemField %>'/>

        <td>$&nbsp;<%= price %>&nbsp;/lb.</td>

        <td>

           <form action='add-selection-to-cart-action.do'>

           <html:links name='<%= currentSku + "-" +

                                 currentItem + "-" + price %>'>

              <option value='0.00'>0.00</option>

              <option value='1.00'>1.00</option>

              <option value='1.50'>1.50</option>

              <option value='2.00'>2.00</option>

              <option value='2.50'>2.50</option>

              <option value='3.00'>3.00</option>

              <option value='3.50'>3.50</option>

              <option value='4.00'>4.00</option>

              <option value='4.50'>4.50</option>

              <option value='5.00'>5.00</option>

              <option value='5.50'>5.50</option>

           </html:links>

           </form>

        </td>

     </dom:ifNodeNameEquals>

  </dom:iterate>

</dom:iterate>

</table>

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

В JSP-документе, приведенном в листинге 12.12,а, для создания DOM-документа, представляющего перечень товаров, применяются пользовательские дескрипторы. Дескриптор dom:parse, присутствующий в листинге 12.12,а, интерпретирует содержащиеся в нем данные как XML-код. Содержимое генерируется с помощью файла inventory-to-xml. j sp, код которого представлен в листинге 12.12,6.

Листинг 12.12,6. /WEB-IHF/jsp/storefront/inventory-to-xml.

<%@  taglib uri='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  %> <%=  “>/”  + name +   “<”%>

</database:columns> </ITEM>

</database:rows> </FRUITS>

<database:release query='inventory'/>

JSP-файл, код которого приведен в листинге 12.12,6, содержит пользовательские дескрипторы, предназначенные для работы с базой данных. Эти дескрипторы были рассмотрены в главе 10. Представленный выше JSP-файл генерирует XML-данные, которые обрабатываются дескриптором dom: parse (листинг 12.12,а).

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

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

Резюме

В данной главе обсуждалось законченное приложение, при создании которого были использованы средства, описанные на протяжении всей книги. Следует заметить, что некоторые особенности реализации рассмотрены не были, Если вы хотите детально ознакомиться с данным приложением, вам следует скопировать его код, обратившись по адресу www.phptr.com/advjsp, и внимательно изучить его.

ФИЛЬТРЫ СЕРВЛЕТОВ

Приложение

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

436      Приложение А. Фильтры сервлетов

Контейнер сервлетов

Цепочка фильтров

Сервлет

Фильтр предоставляется контейнером

Фильтр реализуется разработчиком

—*- doRtterfServletReqjest, ServerResponce, FilterChaln) —«*■ service (ServletRequest, ServsrResponce)

Рис. А. 1. Использование фильтров саралетов

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

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

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

  1.  Объявить фильтры и их отражение в дескрипторе доставки.

Реализовать фильтр.

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

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

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

При реализации фильтра аутентификации надо в первую очередь задать фильтр и его отражение в дескрипторе доставки приложения. Код дескриптора доставки показан в листинге АЛ.

ЛистингА.1.

<?xml version="1.0" encoding="ISO-8859-l"?>

<!DOCTYPE web-app

PUBLIC "-//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>

<filter-name>Authenticate Filter</filter-паже>

<url-pattern>*.jsp</url-pattern> </filter-mapping>

<welcome-

<welcome-file>index.jsp</welcome-</welcome-file-list>

</web-app>

В дескрипторе доставки, приведенном выше, заданы имя и класс фильтра аутентификации. Здесь же указано отражение, связывающее фильтр со всеми JSP-доку-ментами.

Класс, посредством которого реализуется фильтр аутентификации, приведен в листинге А.2.

ЛистингА.2. /WEB-INF/clasaes/filters/AuthenticateFilter.Java

package filters;

import Java,io.PrintWrtter; 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) {

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 . servlet .Filter. Метод doFilter проверяет роль и перенаправляет запрос, вызывая метод FilterChain.doFilter. Перенаправление выполняется только в том случае, если пользователь принадлежит роли resin. Для любой другой роли метод doFilter выводит сообщение об ошибке.

Замечание. В процессе подготовки данной книги к публикации выяснилось, что набор методов фильтров будет изменен. Метод setFilterConfig, объявленный в интерфейсе Filter, заменен методом init, кроме того, в интерфейсе добавлен метод destroy и удален метод getFilterConfig.

Резюме

Фильтры сервлетов являются мощным средством, дополняющим Servlet API. Фильтры могут быть связаны с сервлетом или с классом ресурсов, задаваемых посредством шаблона URL, что позволяет применять один или несколько фильтров к набору документов, например к JSP-, HTML- или XML-файлам.

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

Научно-популярное издание Дэвид М. Гери

Java Server Pages.

Библиотека профессионала

Литературный редактор Верстка

Художественный редактор Корректоры

ИЛ, Попова

А .В, Плаксюк

С.А. Чернокозгтский

Л. А. Гордиенко, О.В. Mutuyi

Т.А. Корзун

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

101509, Москва, ул. Лесная, д. 43, стр. 1.

Изд, лиц. ЛР № 090230 от 23.06.99

Госкомитета РФ по печати.

Подписано в печать 26.04.2002. Формат 70x100/16.

Гарнитура NewBaskerville. Печать офсетная.

Усл. печ. л. 33,6. Уч.-изд. л. 20,00.

Тираж 3000 экз. Заказ № 424.

Отпечатано с диапозитивов в ФГУП "Печатный двор"

Министерства РФ по делам печати,

телерадиовещания и средств массовых коммуникаций.

197110, Санкт-Петербург, Чкаловский пр., 15.




1. Статистика 1
2. Муниципальное образование
3. задание на 151013 Найдите ошибку в образовании формы слова
4. Кубок Адмиралтейского района СанктПетербурга Номинация состоится 28 декабря 2013г
5. Установіть відповідність між назвою грецької колонії та описом місця її розташування 1
6. тема тормозов АБС BS ntilock Brke System предназначена предотвратить блокировку колес при торможении и сохранить у
7. Методы работы аудиторов внешних и внутренних- наблюдение осмотр опрос анализ с применением статистическ
8. Системное исследование функционирования предприятия
9. Советская доколхозная деревня накануне коллективизации (1920-е гг)
10.  С Новым годом Дорогие друзья Поздравляем Вас с наступающим Новым годом Желаем в следующ
11. відсутність мієлінової оболонки нервового волокна слабкий розвиток дендритів нейроцита мала кількі
12.  С открытым очистным пространством; 2
13. з курсу Основи охорони праці для спеціальності ПТТ МКМ Дайте визначення поняттю
14. модульная организация компьютера 1.
15. Для начисления амортизации объектов основных средств может использоваться один из следующих способов-
16. полет тогда попробуйте приготовить знаменитый коктейль Б52
17. своему трактуют вопрос о происхождении денег
18. Вариант 18 Группа 11201512 Выполнила- Соболевская Н
19. задание на проектирование
20. Наука гражданского права