Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
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 включает следующие пять этапов.
Создание объекта чтения 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, можно описать следующим образом.
Управление передается документу, представляющему "витрину". Этот документ
извлекает данные из базы, предоставляет пользователю возможность выбирать
продукцию и отображает данные о выбранных товарах в "корзинке", роль ко
торой выполняет левая часть 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-страницы. В результате будет выбран новый регион и произойдет обновление текущей страницы.
Сценарий развития "выбор пользователем языка взаимодействия" можно разделить на следующие этапы.
Приложение изменяет регион в соответствии с выбранным флагом.
Приложение повторно отображает текущую 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 отображают текст на выбранном языке. Эти дескрипторы используют информацию о регионе, связанную с сеансом конкретного пользователя.
Аутентификация
Обращаться к рассматриваемому приложению может каждый, но только зарегистрированные пользователи имеют возможность проверить свои покупки. Ограничения, связанные с защитой, реализованы в рамках сценария развития под названием "аутентификация пользователя", В данном сценарии развития можно выделить следующие этапы.
Если пользователь не зарегистрирован, дескриптор передает управление стра
нице регистрации.
На странице регистрации пользователь активизирует ссылку, соответствую
щую созданию учетной записи.
Приложение отображает форму, предназначенную для создания новой учетной
записи.
Пользователь заполняетформуи активизирует кнопку создания учетной записи.
Приложение повторно выводит форму регистрации, и пользователь регистри
руется.
Файлы, участвующие в реализации сценария развития "аутентификация пользователя", показаны на рис. 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>$ <%= price %> /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 сервлета.
Фильтр может отказаться передавать запрос; в этом случае сам фильтр завершает обработку запроса и формирует ответ, а остальные фильтры в цепочке и сервлет управления не получают. Фильтры имеют доступ к остальным звеньям цепочки, поэтому один фильтр может управлять вызовом других фильтров.
Для использования фильтров сервлетов необходимо выполнить следующие действия.
Реализовать фильтр.
Пример фильтра
В данном разделе мы рассмотрим фильтр аутентификации, который проверяет, принадлежит ли пользователь некоторой роли. Если принадлежность роли установлена, фильтр вызывает следующий фильтр в цепочке и в конечном итоге запрос передается 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.