Java Developer's Guide to E-Commerce with XML and JSP, Bill Brodgen, Chris Minnick
- Разработка системы показа новостей
- Гибкость отображения
- Элементы текстов сообщений
- Расположение сообщений в зависимости от их новизны
- Информация для управления сообщениями
- Корневой элемент документа
- Простота ввода данных
- Минимальная нагрузка на сервер
- Система показа новостей
- Внешний вид web-страницы
- Класс NewsFormatter
- Использование класса NewsFormatter
- Код для сервлета TheNewsServ
- Пример JSP-страницы
- Добавление свежих новостей
- Сервлет CompanyNewsServ
- Класс NewsUpKeep
- Публикация новостей фирмы для привлечения посетителей
- Критерии дизайна системы показа новостей на XML
- Гибкое представление новостей с помощью сервлета
- Представление новостей с помощью JSP-страниц
- Простая система для добавления новостей
Вы, наверное, встречали web-сайты небольших организаций, в которых самые свежие новости относятся к событиям полугодовой давности. Эти организации теряют прекрасный шанс привлечь внимание случайных посетителей. Может быть, так происходит потому, что люди, находящиеся в курсе происходящих в фирме событий, не общаются с web-мастером своей организации или просто страницы с новостями слишком неудобно редактировать. Мы, естественно, собираемся предложить вам решение, основанное на технологии XML, которое мы подробно обсуждаем в этой главе.
Разработка системы показа новостей
Мы хотим предложить посетителям нашего web-сайта последние новости фирмы в компактном и удобном для чтения формате. Помните, что обычно в вашем распоряжении имеется всего лишь несколько секунд, чтобы привлечь внимание к вашему сайту случайного посетителя, который просто "прогуливается" по сети. Мы хотим, чтобы посетителю было понятно, как найти на сайте подробную информацию, если его что-то заинтересовало, как войти в систему каталога и, будем надеяться, что-нибудь приобрести.
Если вы подумаете, какими характеристиками должна обладать система показа новостей, в ваш список, вероятно, войдут следующие пункты:
- гибкий способ отображения;
- удобный ввод данных;
- минимальная нагрузка на сервер.
Наряду с обеспечением этих свойств при разработке системы вы, в первую очередь, должны будете решить, какие элементы и атрибуты XML использовать для представления данных, затем сделать выбор между моделями DOM и SAX (механизмами отображения данных для пользователя) и, наконец, определить механизм добавления новых сообщений.
Гибкость отображения
Поскольку мы решили остановиться на XML как на исходной среде хранения данных, мы можем обеспечить гибкость отображения посредством хранения данных, необходимых для всех доступных способов представления информации. Мы имеем в виду следующее: вероятно, вы замечали, что на разных коммерческих web-сайтах новости компании представлены обычно одним из трех способов:
- заголовки новостей со ссылкой на полный текст;
- несколько наиболее интригующих строк со ссылкой на полный текст;
- полный текст сообщения.
Существуют и другие форматы сообщений: электронный информационный бюллетень, печатная копия информационного бюллетеня, корпоративные сообщения. Для этих форматов также применимы описанные выше три способа (заголовок, краткое сообщение и полный текст). Первая наша задача при разработке системы отображения новостей - создать такой документ XML, который поддерживал бы все три формата.
Элементы текстов сообщений
Хотя в области искусственного интеллекта и понимания компьютером естественных языков сделаны большие успехи, никто не рассчитывает, что компьютер напишет хороший заголовок, проанализировав текст сообщения. Поэтому приходится согласиться с тем, что нужен человек, который для каждого способа представления новостей создаст отдельный заголовок. В сообщении обычно указывается дата, а иногда рядом еще и место, где произошло то событие, о котором идет речь, например: "Остин, Техас, 1 января 2000". Эта задача тоже должна выполняться человеком.
Некоторые сообщения очень выигрывают, когда сопровождаются графикой, звуковыми клипами или ссылками на другие сайты, поэтому продумайте, как сконструировать документ XML, чтобы его можно было дополнить различными элементами, способствующими увеличению привлекательности страницы новостей. Мы решили, что было бы слишком сложно и неудобно снабжать систему показа новостей в нашем примере всеми возможными "украшениями". Поэтому мы будем хранить тексты сообщений для второго и третьего способов (краткое и полное сообщение), используя тег XML <[[CDATA...]]>.
Поскольку анализаторы XML не пытаются анализировать текст, содержащийся внутри раздела CDATA, в этот раздел вы можете поместить любую разметку HTML, не сбивая с толку анализатор.
Элементы, содержащие дату, заголовок, краткое сообщение и полный текст, показаны в листинге 8.1.
Листинг 1. Дата, заголовок, короткое и полное сообщения (thenews.xml)
- <date>Austin, TX, Jun 14 2000</date>
- <head>Best Seller at a Great Price</head>
- <short>
- <![CDATA[Due to a special deal with the publisher, we can now offer
- <i>Dryer Lint Art</i>
- at 50% off the retail price.]]>
- </short>
- <long>
- <![CDATA[This books starts with simple Dryer Lint projects suitable for the novice and advances through easy stages tothe (literally)
- <b>monumental</b>
- recreation of famous monuments in that most flexible of craft materials, dryer lint. Even though you may never attempt major constructions like the Statue of Liberty project documented in the final chapter, your projects will benefit by a study of this famous creation. Includes UML diagrams.]]>
- </long>
Другим аспектом гибкости является способность выборочно представлять сообщения в соответствии с темой, интересующей посетителя. Предполагая, что спектр возможных интересов посетителей сайта XMLGifts.com очень широк, мы хотим показать каждому посетителю те новости, которые связаны с его излюбленной темой, и в том месте сайта, куда он с наибольшей вероятностью заглянет. В такой структуре неизбежны перекрывающиеся области; например, книга о музыкальной группе может оказаться интересной как для покупателей книг, так и для покупателей музыкальных компакт-дисков. Следовательно, каждое сообщение должно быть снабжено одной или несколькими пометками, которые указывают, к каким тематическим категориям можно его отнести; а формат представления новостей должен допускать переключения между различными темами сообщений.
Для того чтобы пометить сообщение и отнести его тем самым к определенной категории, мы можем использовать элемент или атрибут. Следуя советам, приведенным в разделе "Элементы или атрибуты?" главы 2, можно заключить, что в данном случае лучше использовать атрибуты, так как тема сообщения - это данные о содержимом элемента, и мы предполагаем, что количество тем сообщений будет ограниченным.
Расположение сообщений в зависимости от их новизны
Самые свежие новости должны располагаться первыми. Так как документ XML автоматически сохраняет порядок следования элементов, новые элементы должны добавляться к началу документа. Более того, было бы неплохо предусмотреть возможность отображения только самых свежих новостей. Следовательно, нам нужен способ представления "возраста" сообщений.
После долгих колебаний между многочисленными способами представления даты, которые позволили бы нам отображать только недавние сообщения, мы остановились на использовании простого целочисленного представления количества дней, прошедших с 1 января 1970 года. Для этого значение типа long, возвращаемое методом System.currentTimeMillis(), делится на количество миллисекунд в сутках, и полученное число становится значением атрибута timestamp тега Newsitem. Альтернативные варианты - использование классов Java DateFormat или Calendar - были отвергнуты, так как они подразумевают создание большого количества объектов, а мы хотим, чтобы показ новостей создавал минимальную нагрузку на сервер.
Информация для управления сообщениями
Поскольку вы или ваши служащие будут обновлять страницу новостей в режиме подключения к сети, было бы полезно отслеживать, кто какое сообщение написал. (Например, чтобы знать, кто должен получать нагоняй за допущенную ошибку.) Для этого используется атрибут <author> элемента <Newsitem>. Сервлет обновления сообщений, описанный в разделе "Добавление свежих новостей" этой главы, обладает простым механизмом контроля доступа, в котором используются имя автора и пароль; этот то самое имя автора, которое становится значением атрибута author.
Чтобы создать документ HTML, в котором заголовок содержит ссылку на полную версию текста, нужно использовать уникальный идентификатор. Мы, например, выбрали простейший вариант: при создании каждого сообщения <NewsItem> ему присваивается серийный номер, который и становится значением его атрибута id.
Корневой элемент документа
Мы отслеживаем некоторые параметры, используемые во всем файле, с помощью атрибутов, которые задаются в корневом элементе документа, Newsfile. Очередной атрибут id - это просто nextid. Присваивание нового значения каждому следующему атрибуту nextid является обязанностью программы, добавляющей новые сообщения или, в случае редактирования в автономном режиме, автора элемента Newsitem.
Корневой элемент также является подходящим местом для хранения атрибутов, связанных с различными заданными по умолчанию параметрами отображения. В приведенном ниже примере имеется только один такой атрибут, longtemplate, который идентифицирует заданный по умолчанию файл - шаблон HTML, используемый для форматирования сообщений.
В листинге 8.2 показано, как используются все перечисленные теги.
Листинг 2. Элемент <Newsfile> с одним элементом <Newsitem> (thenews.xml)
- <?xml version="1.0" standalone="yes" ?>
- <!-выходные данные NewsUpKeep -->
- <Newsfile longtemplate="tmlong.html" nextid="1010" >
- <Newsitem timestamp="11045" topic="CDs" author="wbrogden" id="1008" >
- <head>Your Favorite Music Now Available</head>
- <date>Austin, Feb 1, 2000</date>
- <short>
- <![CDATA[XMLGifts proudly announces the availability of the CD that has all the geeks singing, <i>It's Dot Com Enough for Me.</i>]]>
- </short>
- <long>
- <![CDATA[<p>
- <i>It's Dot Com Enough For Me.</i>
- now in stock!</p>
- <p>All those great songs created during breaks in all-night coding sessions - now recorded by top Silicon Valley garage bands on our private label. <i>It's Dot Com Enough for Me</i> will have you singing along - or maybe laughing till the Jolt cola spurts out your nose. Seventeen songs from geeks at Sun, Microsoft, Apple, Cisco, and other top tech outfits. </p>]]>
- </long>
- </Newsitem>
- </Newsfile>
Простота ввода данных
Поскольку мы выбрали простой формат новостей, показанный в листинге 8.2, ввод данных осуществляется тоже достаточно просто. Система, основанная на формах HTML и сервлетах, описанная в разделе "Добавление свежих новостей" этой главы, позволяет добавлять новые сообщения через Интернет.
Тем не менее легкость ввода данных никак не связана с контролем за качеством самого текста. Для достижения лучших результатов следует убедиться, что темы, согласно которым классифицируются сообщения, и стиль текста всегда согласуются между собой. Также необходимо составить список требований и рекомендаций по составлению текстов сообщений и убедиться в том, что эти списки доступны всем служащим фирмы, уполномоченным размещать сообщения на сайте.
Минимальная нагрузка на сервер
Поскольку мы надеемся, что посещаемость нашего сайта будет достаточно велика, мы хотим, чтобы отображение главной страницы выполнялось по возможности просто, то есть чтобы страница быстро загружалась, а ее генерирование не требовало больших затрат ресурсов со стороны сервера. Рассмотрим следующие альтернативные варианты отображения новостей.
- Статические страницы новостей (Static News Pages). Статические страницы быстро загружаются, и главная страница благодаря XML может формироваться заново при появлении нового сообщения. Однако использование статических страниц исключает возможность их индивидуальной настройки для постоянных посетителей.
- Страницы новостей, генерируемые сервлетами (Servlet-Generated News Pages). Сервлеты Java могут генерировать все, что потребуется; при этом предполагается, что они используют файлы с шаблонами HTML для регулировки многочисленных атрибутов внешнего вида сайта.
- JSP-страницы (JavaServer Pages). Преимущество JSP-страниц перед сервлетами заключается в том, что для изменения внешнего вида страницы web-дизайнеру не нужно уметь программировать на Java.
При выборе методов обработки следует сделать выбор между моделями SAX и DOM, а также между анализаторами XML, проверяющими либо не проверяющими допустимость документа. Модель SAX подразумевает работу анализатора для доступа к каждой странице. При использовании модели DOM доступ к страницам осуществляется быстрее, но зато все приходится хранить в памяти. Предполагая, что количество новостей будет не больше нескольких сотен, затраты ресурсов на хранение всей модели DOM в памяти не окажутся слишком значительными, поэтому мы считаем, что в данном случае модель DOM является безусловно оптимальным вариантом.
Мы не видим никаких причин в использовании анализатора, проверяющего допустимость документа. Так как новые сообщения будут создаваться автоматически, вероятность ошибки форматирования весьма невелика. Кроме того, вряд ли ошибки, связанные с недопустимостью документа, могут смутить пользователя.
Система показа новостей
Окончательный вариант устройства системы показа новостей изображается блок-схемой, приведенной на рис. 8.1. Обработка информации, происходящая на сервере, представлена в правой части блок-схемы, а обработка в автономном режиме - в левой части. Исходный файл XML может редактироваться как в режиме подключения к сети, так и автономно, но предпочтительным является режим подключения, так как он позволяет автоматически задавать атрибуты id и timestamp.
Рис. 1. Обработка сообщений
Объектная модель документа поддерживается в памяти с помощью класса DOMlibrary, который был описан в главе 7. Обновление исходного файла XML в режиме подключения выполняется сервлетом CompanyNewsServ и классом NewsUpKeep, которые обсуждаются далее в этой главе. Формирование web-страниц новостей на основе текстов сообщений осуществляется сервлетами или JSP-страницами, использующими класс NewsFormatter, который обсуждается в следующем разделе. Для создания информационных бюллетеней и печатной версии требуются другие форматы, которые легко написать, основываясь на приведенных ниже примерах.
Внешний вид web-страницы
Прежде чем мы углубимся в рассмотрение кода нашей системы показа новостей, давайте посмотрим, как может выглядеть разрабатываемая страница. На рис. 8.2 показана web-страница, посвященная новостям фирмы, в контексте более крупного сайта. Мы не включили в нее навигационный интерфейс и другие элементы, которые должны присутствовать в реальном сайте.
Рис. 2. Web-страница новостей, генерируемая с помощью JSP-страницы
JSP-страница, используя класс NewsFormatter, который подробно описывается в следующем разделе, сгенерировала показанную на рисунке страницу на основе файла XML. В левой части экрана приводятся заголовки последних новостей по всем темам. Самое свежее сообщение приводится полностью в центре страницы. Справа показаны новости в кратком изложении, но первая из них (полная версия которой расположена по центру) пропущена. При щелчке мышью на заголовке или кратком изложении сообщения появляется полная версия этого сообщения.
Класс NewsFormatter
Ключевым классом Java для формирования новостных сообщений является класс NewsFormatter. Как показано в листинге 8.3 и следующих листингах, класс NewsFormatter включает в себя объект File, который указывает на исходный файл XML. Класс NewsFormatter использует класс DOMlibrary, описанный в главе 7, чтобы получить объект Document, содержащий все данные из файла XML. Конструктор получает список узлов NodeList, содержащий все узлы Newsitem, и задействует его для создания массива с названием itemNodes. Этот массив требуется для решения различных задач форматирования.
Листинг 3. Начало кода класса NewsFormatter
- package com.XmlEcomBook.Chap08;
-
- import com.XmlEcomBook.DOMlibrary ;
- import java.io.*;
- import java.util.* ;
- import javax.servlet.*;
- import javax.servlet.http.*;
- import org.w3c.dom.* ;
-
- public class NewsFormatter
- {
- static String handler ; // сервлет для отображения одного
- //сообщения
- public static void setHandler(String s){handler=s; }
-
- // переменные экземпляра
- File newsFile ;
- String newsFileName ;
- String newsFilePath ;
- String headStr, footStr ;
-
- Node[] itemNodes ;
- Element docRoot ;
- Hashtable nodeHash ; // хэш-таблица элементов <Newsitem,
- //ключами которых служат имена тегов
-
- int maxNitems, skipNitems;
- int itemsCount = 0 ;
-
- public NewsFormatter( File f ) throws IOException {
- newsFile = f ;
- newsFileName = f.getAbsolutePath() ;
- int p = newsFileName.lastIndexOf( File.separatorChar );
- if( p > 0 ){ newsFilePath = newsFileName.substring(0,p);
- }
- else { System.out.println("NewsFormatter path problem");
- }
- DOMlibrary library = DOMlibrary.getLibrary();
- Document doc = library.getDOM( newsFileName );
- if( doc == null ){
- throw new FileNotFoundException( newsFileName );
- }
- docRoot = doc.getDocumentElement();
- NodeList newsItemNodes = doc.getElementsByTagName("Newsitem");
- int ct = newsItemNodes.getLength();
- itemNodes = new Node[ ct ];
- for( int i = 0 ; i < ct ; i++ ){
- itemNodes[i] = newsItemNodes.item( i );
- }
- }
Вы, должно быть, помните из главы 7, что класс DOMlibrary перезагружал файл XML, если время его последней модификации изменялось. Поскольку в нашем случае объект Document не меняется в результате действия класса NewsFormatter, он может использоваться совместно любым количеством сервлетов и доступ к нему нужно синхронизировать.
У нас имеются две версии метода doNews. Версия, приведенная в листинге 8.4, используется для вывода нескольких сообщений в виде заголовков новостей, краткого и полного форматов изложения. Эта версия метода обеспечивает следующие возможности: выбор сообщений по их тематике и времени появления, пропуск указанного количества сообщений и ограничение общего количества отображаемых сообщений. Строки hs и fs - необязательные параметры, которые обеспечивают некоторые небольшие дополнительные возможности форматирования.
Метод doNews проверяет наличие параметров типа Srting, которые ограничивают выбор сообщений определенными тематическими или временными рамками. Если параметр topstr отличен от null и не пуст, вызывается метод selectNodes, который ограничивает полный список сообщений набором новостей, соответствующим заданной тематике. Аналогично, если указана строка age, вызывается метод limitAge. Если какой-либо из этих методов сокращает список сообщений до нуля, метод doNews сразу же прекращает свое выполнение. Другие параметры контролируют максимальное количество новостей на странице и относительный номер сообщения, с которого начинается их просмотр.
Листинг 4. Метод doNews выбирает способ представления сообщений (NewsFormatter.java)
- // hs и fs - это верхние и нижние колонтитулы,
- // используемые в краткой и полной версиях сообщения
- // в элементе <Newsfile также можно задать шаблоны
- // PrintWriter, hs, fs, topics, H,S или L, age, mx#
- // skpN используется для того, чтобы пропустить первые N
- // сообщений (предполагается, что они печатаются где-то в
- // другом месте страницы). Для того чтобы напечатать все
- // сообщения, задайте параметр skpN равным 0
- // возвращает количество напечатанных сообщений
- public int doNews( PrintWriter out, String hs, String fs, String topstr, String sz, String age, int skpN, int mxN ){
- headStr = hs ; footStr = fs ;
- skipNitems = skpN ; maxNitems = mxN ;
- itemsCount = 0 ;
- if( topstr != null && topstr.length() > 0 ){
- if( selectNodes(topstr, out )== 0 ) return 0 ;
- }
- if( age != null && age.length() > 0 ){
- if( limitAge( age, out ) == 0 ) return 0 ;
- }
- char szch ;
- if( sz == null || sz.length() == 0 ) szch = 'L' ;
- // по умолчанию полная версия
- else szch = sz.toUpperCase().charAt(0);
- switch( szch ) {
- case 'H' :
- doHeadlineNews( out ); break ;
- case 'S' :
- doShortNews( out ); break ;
- case 'L' :
- default :
- doLongNews(out );
- }
- return itemsCount ;
- }
Метод doNews, показанный в листинге 8.5, отыскивает сообщение по указанному атрибуту id и форматирует полную версию сообщения. Оставшийся метод класса NewsFormatter предназначен для поддержки двух методов doNews.
Листинг 5. Версия doNews для одного выбранного сообщения (NewsFormatter.java)
- // по указанному идентификатору находит сообщение и
- // печатает его - всегда в полной версии
- public int doNews( PrintWriter out, String hs, String fs, String id ){
- headStr = hs ; footStr = fs ;
- itemsCount = 0 ;
- Node n = null ; //
- for( int i = 0 ; i < itemNodes.length ; i++ ){
- n = itemNodes[i]; // узлы <Newsitem
- String nid = ((Element)n).getAttribute("id");
- if( id.equals( nid )){
- break ;
- }
- } /* если не нашлось сообщения с указанным идентификатором id, то будет напечатано самое старое из имеющихся*/
- findNodes((Element) n ); // отыскивает дочерние узлы
- // элемента <Newsitem
- doNewsItemLong( out ); // для элемента <Newsitem с
- // указанным идентификатором
- return itemsCount ;
- }
Мы решили, что заголовки сообщений всегда будут форматироваться как маркированные списки (unordered lists) HTML. Это очень упрощает метод doHeadlineNews, показанный в листинге 8.6.
Листинг 6. Метод, форматирующий список заголовков новостей (NewsFormatter.java)
- // Заголовок всегда форматируется как <UL> со ссылкой
- public void doHeadlineNews(PrintWriter out){
- out.println( "<ul>" );
- for( int i = skipNitems ; i < itemNodes.length ; i++ ){
- if( i >= maxNitems ) break ;
- Node n = itemNodes[i]; // узлы <Newsitem
- String id = ((Element)n).getAttribute("id");
- findNodes((Element) n ); // отыскивает дочерние узлы
- // элемента <Newsitem
- out.print("<li><a href=" + handler + "?id=" + id + "&size=L >" );
- out.print( nodeHash.get("head") );
- out.println("</a></li>");
- }
- out.println("</ul>");
- }
Метод doShort, показанный в листинге 8.7, проверяет наличие заданного по умолчанию шаблона форматирования короткой версии сообщения, а затем выводит эту версию на страницу. Обратите внимание на то, что из каждого элемента (сообщения) извлекается его атрибут id, прежде чем будет вызван метод doNewsItemShort. Этот идентификатор впоследствии присоединяется к каждому элементу, представляющему собой краткую версию, в качестве ссылки на полный текст сообщения.
Листинг 7. Метод doShortNews (NewsFormatter.java)
- public void doShortNews(PrintWriter out){
- NamedNodeMap attrib = docRoot.getAttributes();
- Node n = attrib.getNamedItem( "shorttemplate") ;
- String template = null ;
- if( n != null ) template = n.getNodeValue();
- if( headStr == null && template != null && template.length() > 2 ){
- try {
- setFromTemplate( template );
- }catch(IOException ie ){
- System.out.println("Unable to read " + template );
- }
- }
- out.println( headStr );
- for( int i = skipNitems ; i < itemNodes.length ; i++ ){
- if( i >= maxNitems ) break ;
- n = itemNodes[i]; // узлы <Newsitem
- String id = ((Element)n).getAttribute("id");
- findNodes((Element) n ); // отыскивает дочерние узлы
- // элемента <Newsitem
- doNewsItemShort( out, id );
- }
- out.println( footStr );
- }
Как показано в листинге 8.8, метод doLongNews проверяет наличие заданного по умолчанию шаблона форматирования полной версии сообщения, после чего выполняет цикл по всем сообщениям в массиве itemNodes.
Листинг 8. Метод doLongNews выводит полный текст сообщения (NewsFormatter.java)
- public void doLongNews(PrintWriter out){
- NamedNodeMap attrib = docRoot.getAttributes();
- Node n = attrib.getNamedItem( "longtemplate");
- String template = null ;
- if( n != null ) template = n.getNodeValue();
- if( headStr == null &&
- template != null &&
- template.length() > 2 ){
- try {
- setFromTemplate( template );
- System.out.println("Template set ok " + headStr + footStr );
- }catch(IOException ie ){
- System.out.println("Unable to read " + template );
- }
- }
- out.println( headStr );
- for( int i = skipNitems ; i < itemNodes.length ; i++ ){
- if( i >= maxNitems ) break ;
- n = itemNodes[i];
- findNodes((Element) n );
- doNewsItemLong( out );
- }
- out.println( footStr );
- }
В листинге 8.9 показан метод limitAge, который вызывается всегда, когда в методе doNews присутствует строка, задающая максимально допустимый "возраст" новостей. После проверки корректности целочисленного значения, содержащегося в строке age, этот метод заново компонует массив itemNodes, помещая туда только выбранные сообщения.
Листинг 9. Метод, выбирающий сообщения по дате их создания (NewsFormatter.java)
- // накладывает ограничение на "возраст" сообщений -
- // допускаются только наиболее свежие новости. Возвращает
- // количество допущенных сообщений, это может быть ноль.
- private int limitAge(String age, PrintWriter out ){
- int days = 100 ;
- try {
- days = Integer.parseInt( age );
- if( days <= 0 ) days = 1 ;
- }catch(NumberFormatException nfe){
- return itemNodes.length ; // без изменений
- }
- int today =(int)( System.currentTimeMillis() / ( 24 * 60 * 60 * 1000));
- int oldest = today - days ;
- Vector v = new Vector( itemNodes.length );
- int nidate = today ; // на случай проблем при анализе
- int i ;
- for( i = 0 ; i < itemNodes.length ; i++ ){
- Node n = itemNodes[i]; // узлы <Newsitem
- String t = ((Element)n).getAttribute("timestamp");
- try { nidate = Integer.parseInt( t );
- }catch(Exception nfe){ // неверный формат числа или
- // пустой указатель
- System.out.println( "NewsFormatter.limitAge " + nfe );
- }
- if( nidate >= oldest ){
- v.addElement( n );
- }
- }
- itemNodes = new Node[ v.size() ]; // может быть ноль
- for( i = 0 ; i < v.size(); i++ ){
- itemNodes[i] = (Node) v.elementAt(i);
- }
- return itemNodes.length ;
- }
Причина сложности метода selectNodes заключается в том, что и параметр topics этого метода, задающий выбор тем сообщений, и атрибут topic каждого сообщения могут содержать как одну, так и несколько тем, разделенных запятыми. Как показано в листинге 8.10, мы строим хэш-таблицу recognize для ускорения распознавания тем.
Листинг 10. Метод, который выбирает сообщения по указанным темам (NewsFormatter.java)
- // в строке topics перечислены темы, разделенные запятыми
- // как в атрибуте topics="general,books,java",
- // выходные данные используются только для отладки
- private int selectNodes(String topics, PrintWriter out ){
- Hashtable recognize = new Hashtable();
- StringTokenizer st = new StringTokenizer( topics.toUpperCase(), ",");
- while( st.hasMoreTokens()){
- String tmp = st.nextToken().trim();
- recognize.put( tmp,tmp );
- }
- // теперь можно использовать хэш-таблицу для
- // распознавания выбранных тем сообщений
- Vector v = new Vector( itemNodes.length );
- int i ;
- for( i = 0 ; i < itemNodes.length ; i++ ){
- Node n = itemNodes[i]; // узлы <Newsitem
- String t = ((Element)n).getAttribute("topic");
- st = new StringTokenizer(t.toUpperCase(),",");
- while( st.hasMoreElements()){
- // Мы просто просматриваем хэш-таблицу, чтобы
- // узнать, присутствует ли там данная тема
- if( recognize.get( st.nextToken().trim() ) != null ){
- v.addElement(n);
- break;
- }
- } // конец цикла while по списку тем
- } // конец цикла по всем узлам
- // строим новый массив из выбранных узлов
- itemNodes = new Node[ v.size() ];
- for( i = 0 ; i < v.size(); i++ ){
- itemNodes[i] = (Node) v.elementAt(i);
- }
- return itemNodes.length ;
- }
Метод findNodes, показанный в листинге 8.11, вызывается для каждого сообщения, которое должно быть помещено на страницу. Входной элемент Element - это узел NewsItem документа XML. Метод findNodes создает переменную nodeHash, которая позволяет другим методам извлекать дочерние элементы NewsItem, например <short>, из коллекции nodeHash. Ключами элементов в этой хэш-таблице являются имена узлов.
Листинг 11. Метод findNodes класса NewsFormatter (NewsFormatter.java)
- // поиск узлов, которые содержат текстовые данные
- private void findNodes( Element ne ){
- NodeList nl = ne.getChildNodes(); // все узлы
- int ct = nl.getLength();
- nodeHash = new Hashtable( 2 * ct );
- for( int i = 0 ; i < ct ; i++ ){
- Node n = nl.item(i);
- if( n instanceof Element ){
- nodeHash.put( n.getNodeName(), n );
- }
- }
- }
Заголовки и краткая версия сообщения всегда снабжаются ссылкой на полную версию. Эта ссылка встраивается в HTML-страницу с помощью методов doNewsItemHead и doNewsItemShort, как показано в листинге 8.12.
Листинг 12. Методы doNewsItemHead и doNewsItemShort (NewsFormatter.java)
- // для <Newsitem > уже создана хэш-таблица
- // id - это атрибут
- private void doNewsItemHead( PrintWriter out, String id ){
- out.print("<a href=" + handler + "?id=" + id + "&size=L >" );
- out.print("<h3>"); out.print( nodeHash.get("head") );
- out.println("</h3></a>");
- out.println();
- }
-
- // для <Newsitem > уже создана хэш-таблица
- // id - это атрибут
- // для форматирования вывода используется <p>..</p>
- private void doNewsItemShort( PrintWriter out, String id ){
- // отметьте наличие ссылки на полную версию сообщения
- out.print("<a href=" + handler + "?id=" + id + "&size=L >" );
- out.print("<h3>"); out.print( nodeHash.get("head") );
- out.println("</h3></a>");
- Element de = (Element)nodeHash.get("date");
- out.print( de.getFirstChild() );
- out.println("</p>");
- Element ne = (Element)nodeHash.get("short");
- String wrk = ne.getFirstChild().getNodeValue().trim() ;
- if( !(wrk.startsWith("<P") || wrk.startsWith("<p")) ){
- out.print("<p>");
- }
- out.print( wrk );
- if( !(wrk.endsWith("/p>") || wrk.endsWith("/P>"))){
- out.print("</p>");
- }
- itemsCount++ ;
- out.println();
- }
Как показано в листинге 8.13, метод doNewsItemLong форматирует текст заголовка с помощью тега <h3>. Было бы неплохо усовершенствовать этот метод так, чтобы он допускал возможность изменять указанный формат по мере надобности. Основной текст сообщения форматируется как абзац с помощью тега <p>. Внутри самого текста могут содержаться любые форматирующие теги HTML, но теги <p> всегда будут использоваться для полного текста сообщения.
Листинг 13. Метод doNewsItemLong выводит полную версию сообщения (NewsFormatter.java)
- // для элементов <Newsitem > уже созданы хэш-таблицы
- // для форматирования вывода полной версии сообщения
- // используется <p>..</p>
- private void doNewsItemLong( PrintWriter out ){
- out.print("<h3>"); out.print( nodeHash.get("head") );
- out.println("</h3>");
- Element de = (Element)nodeHash.get("date");
- out.print( de.getFirstChild() );
- out.println("</p>");
- Element ne = (Element)nodeHash.get("long");
- String wrk = ne.getFirstChild().getNodeValue().trim() ;
- if( !(wrk.startsWith("<P") || wrk.startsWith("<p")) ){
- out.print("<p>");
- }
- out.print( wrk );
- if( !(wrk.endsWith("/p>") || wrk.endsWith("/P>"))){
- out.print("</p>");
- }
- itemsCount++ ;
- out.println();
- }
Наконец, в листинге 8.14 представлены два служебных метода. Метод setFormatTemplate отыскивает файл и считывает его строка за строкой. Предполагается, что в файле имеется строка, начинающаяся с текста "<!-INSERT". Она разделяет разметку HTML на два раздела, которые становятся переменными headStr и footStr. Метод toString предназначен для помощи в отладке.
Листинг 14. Конец исходного кода класса NewsFormatter (NewsFormatter.java)
- private void setFromTemplate(String template)
- throws IOException {
- File f = new File( newsFilePath, template );
- FileReader fr = new FileReader( f );
- BufferedReader br = new BufferedReader( fr );
- StringBuffer hsb = new StringBuffer( 100 );
- StringBuffer fsb = new StringBuffer( 100 );
- String tmp = br.readLine(); // убирает символы конца
- //строки
- while( !tmp.startsWith("<!--INSERT" )){
- hsb.append( tmp ); fsb.append("\r\n");
- tmp = br.readLine();
- }
- tmp = br.readLine();
- while( tmp != null ){
- fsb.append( tmp ); fsb.append("\r\n");
- tmp = br.readLine();
- }
- headStr = hsb.toString();
- footStr = fsb.toString();
- }
-
- public String toString(){
- StringBuffer sb = new StringBuffer("NewsFormatter item ct= ");
- sb.append( Integer.toString( itemNodes.length ));
- return sb.toString() ;
- }
- }
Использование класса NewsFormatter
В этом разделе рассматриваются два способа использования класса NewsFormatter: с сервлетом общего назначения TheNewsServ и с JSP-страницами.
Код для сервлета TheNewsServ
Сервлет TheNewsServ можно использовать для отображения одного сообщения с указанным параметром id или для отображения нескольких сообщений с заданными параметрами topic и age. В листинге 8.15 показаны инструкции импорта и статические переменные. Мы установили значения статических переменных равными заданным по умолчанию, но, разумеется, вам нужно будет заменить их на значения, отражающие ваши фактические настройки.
Листинг 15. Начало исходного кода сервлета TheNewsServ (TheNewsServ)
- package com.XmlEcomBook.Chap08 ;
-
- import java.io.*;
- import java.util.* ;
- import javax.servlet.*;
- import javax.servlet.http.*;
-
- public class TheNewsServ extends HttpServlet
- {
- static String workDir = "E:\\scripts\\CompanyNews" ;
- static String newsFile = "thenews.xml" ;
- static String handler = "http://localhost/servlet/thenews" ;
- static String propfile = "conewserv.properties";
- static String version = "v1.0";
- static String pversion = "" ;
- static Properties cnProp ;
- static String brcrlf = "<br />\r\n" ;
- static String defaultHead = "<html>\r\n" +
- "<head><title>Company News Servlet</title></head>\r\n" +
- "<body>\r\n" +
- "<h2>Here is the news</h2>\r\n" ;
- static String defaultFoot = "</body></html>\r\n";
Метод init, показанный в листинге 8.16, считывает файл свойств, значения которых могут быть использованы для замены установленных по умолчанию значений статических переменных.
Листинг 16. Метод init класса TheNewsServ (TheNewsServ.java)
- public void init(ServletConfig config) throws ServletException
- super.init(config);
- String tmp = config.getInitParameter("workdir");
- if( tmp != null ) workDir = tmp ;
- tmp = config.getInitParameter("propfile");
- if( tmp != null ) propfile = tmp;
- System.out.println("Start TheNewsServ using " + workDir );
- File f = new File( workDir, propfile );
- try { cnProp = new Properties();
- cnProp.load( new FileInputStream(f) );
- tmp = cnProp.getProperty("thenewshandler");
- if( tmp != null ) handler = tmp ;
- pversion = cnProp.getProperty("version");
- if( pversion != null ){
- defaultFoot = "<hr><br>News Servlet " + version
- + " properties: " + pversion + "<br>\r\n" +
- "</body>\r\n</html>\r\n" ;
- }
- NewsFormatter.setHandler( handler );
- System.out.println( new Date().toString() +
- " Loaded properties for TheNewsServ: " + handler );
- }catch(IOException e){
- System.out.println("Error loading " + e );
- }
- }
Функциональность сервлета сконцентрирована в методе doGet, как видно из листинга 8.17. В запросе можно передать значения параметров, определяющих тему сообщений, максимальный "возраст" сообщений, требуемый способ представления и идентификатор сообщения. Заметим, что создается объект File, соответствующий файлу XML с сообщениями, и передается конструктору NewsFormatter. Использование объекта File гарантирует, что соблюдаются соглашения относительно разделителей для компонентов пути; NewsFormatter не открывает этот файл, но использует его имя при получении объектной модели документа для этого файла из DOMlibrary.
Листинг 17. Метод doGet (TheNewsServ.java)
- public void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- resp.setContentType("text/html");
- PrintWriter out = new PrintWriter(resp.getOutputStream());
- String topics = req.getParameter("topic");
- String ageStr = req.getParameter("days");
- String len = req.getParameter("size" ); // "S","H" или "L"
- String id = req.getParameter("id"); // запрашивается одно
- // сообщение
- try {
- File f = new File( workDir, newsFile );
- NewsFormatter nf = new NewsFormatter( f );
- if( id != null ){
- nf.doNews( out, defaultHead,defaultFoot, id );
- }
- else {
- // PrintWriter, head, foot, topics, H,S или L, age,
- // skip#, mx#
- nf.doNews( out, defaultHead, defaultFoot, topics, len, ageStr,0, 10 );
- }
- out.close();
- }catch(Exception e){
- System.err.println("TheNewsServ.doGet " + e );
- errorMsg( out, "TheNewsServ.doGet", e );
- }
- }
Обратите внимание, что конструкция try-catch в методе doGet направляет все исключения методу errorMsg, показанному в листинге 8.18. Разумеется, вам следует вставить свой адрес электронной почты в текст сообщения либо текст этого сообщения может состоять из специальной строки, которая задается в файле свойств. Методы header и footer просто выписывают стандартные теги HTML.
Листинг 18. Методы errorMsg, Header и Footer (TheNewsServ.java)
- // предполагается, что ответ задан как text/html
- private void errorMsg( PrintWriter out, String msg, Exception ex ){
- header( out );
- out.println("<h2>Error: " ); out.println( msg );
- out.println("</h2><br>");
- if( ex != null ){
- ex.printStackTrace( out );
- }
- out.println("<br>");
- out.println("<a href=\"mailto:wbrogden@bga.com\">Please
- mail me" + " the error message.</a><br>");
- footer( out );
- }
-
- private void header(PrintWriter out ){
- out.println("<html>");
- out.println("<head><title>Company News Servlet</title></head>");
- out.println("<body>");
- }
-
- private void footer(PrintWriter out ){
- out.println("<hr><br>Company News Servlet " + version + " properties: <br>" );
- out.println("</body>");
- out.println("</html>");
- out.close();
- }
- }
Пример JSP-страницы
Для рассматриваемого нами примера JSP-страницы новостей ее основной формой является таблица с тремя столбцами. Чтобы уменьшить размер листинга, мы предельно сократили эту страницу; на реальной странице, разумеется, содержится гораздо больше сообщений, связанных с фирмой.
Привлекательность JSP-страниц объясняется как раз простотой включения выходных данных Java в разметку HTML. В листинге 8.19 показано начало JSP-страницы, где создается первая строка таблицы.
Листинг 19. Первая часть упрощенной JSP-страницы для отображения новостей (mockup.jsp)
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
- <html>
- <head>
- <title>The XMLGifts News </title>
- </head>
-
- <body bgcolor="#FFFFFF">
- <%@ page language="java"
- import="com.XmlEcomBook.Chap08.NewsFormatter,java.io.*" %>
- <%!
- String newsFilePath = "e:\\scripts\\CompanyNews" ;
- String newsFileName = "thenews.xml" ;
- String newsHandler =
- "http://localhost:8080/XMLbook/Chap08/thenews.jsp" ;
- File newsFile = new File( newsFilePath, newsFileName );
- public void jspInit(){
- super.jspInit();
- NewsFormatter.setHandler( newsHandler );
- }
- %>
- <table width="89%" border="0" align="left" cellpadding="8">
- <tr align="center" bgcolor="cyan">
- <td colspan="3"><font size="4">
- Various Corporate Navigation Links Go Here</font>
- </td>
- </tr>
Чтобы не усложнять пример, мы жестко запрограммировали тему сообщений - музыкальные компакт-диски (листинг 8.20). Первый раз объект NewsFormatter используется для создания левого столбца таблицы, где расположены заголовки сообщений. Это делается в первую очередь, так как, когда тематика сообщений задана, объект NewsFormatter будет содержать только данные по сообщениям, соответствующим выбранной тематике.
Листинг 20. Продолжение JSP-страницы с выходными данными NewsFormatter (mockup.java)
- <!-- объекты nf и pw будут использоваться для всех трех td -->
- <tr valign="TOP" ><font size="3">
- <td><b>News Headlines</b><br>
- <%
- // для выбора темы можно использовать специальную форму
- String topic = "CDs" ;
- NewsFormatter nf = new NewsFormatter( newsFile );
- PrintWriter pw = new PrintWriter( out );
-
- /* Напоминаем сигнатуру метода doNews
- doNews( PrintWriter out, String hs, String fs,
- String topstr, String sz, String age,
- int skpN, int mxN ) */
- // заголовки - все темы
- nf.doNews( pw, "","", "", "H", null, 0, 8 );
- %>
- </td>
- <td width="50%">
- <%
- nf.doNews( pw, "","", topic, "L", null, 0, 1 );
- %>
- </td>
- <!- столбец с краткими версиями сообщений -->
- <td width="23%">
- <%= "<b>Recent news items about " + topic + "</b><br>" %>
- <%
- /* Напоминаем сигнатуру метода doNews
- doNews( PrintWriter out, String hs, String fs,
- String topstr, String sz, String age,
- int skpN, int mxN ) */
-
- nf.doNews( pw, "","", topic, "S", null, 1, 8 );
- %>
- </td>
- </font>
- </tr>
- <tr align="center" bgcolor="cyan">
- <td colspan="3"><font size="4" >
- Repeat the Navigation links here for convenience<br></font>
- </td>
- </tr>
- <tr>
- <td colspan="3" align='center'><font face='arial, helvetica' size='3'>
- ©2000 XMLGifts.com<sup>SM</sup>
- <br /></font>
- </td>
- </tr>
- </table>
- </body>
- </html>
Добавление свежих новостей
Важной особенностью этого приложения является возможность добавления новых сообщений без нарушения нормальной работы web-сайта. Эту функцию иллюстрирует верхний правый угол рис. 8.1. Вместо того чтобы модифицировать DOM в памяти сервера, сервлет CompanyNewsServ записывает модифицированную версию исходного файла XML на диск. Этот обновленный файл сообщений будет автоматически загружен в очередной раз при вызове DOM из библиотеки DOMlibrary.
Сервлет CompanyNewsServ
Форма HTML для обновления страницы новостей создается и управляется сервлетом CompanyNewsServlet. Начальный вход в сервлет осуществляется с помощью HTML-страницы, в которой имеется обычная форма HTML для ввода имени автора и пароля. Пример такой страницы представлен в файле CoNewsUpdate.html, который находится на прилагаемом к книге компакт-диске. Сервлет отыскивает имя автора в файле свойств, проверяя таким образом, что этот человек имеет право на добавление новых сообщений.
В листинге 8.21 показан файл свойств для работы на сервере localhost. Заметим, что имя автора является именем свойства, а пароль - его значением.
Листинг 21. Файл свойств, используемый сервлетом CompanyNewsServ (conewserv.properties)
- # properties for CompanyNewsServ
- handler=http://localhost/servlet/conewserv
- thenewshandler=http://localhost/servlet/thenews
- newsfile=thenews.xml
- version=June 15, 2000
- wbrogden=xmlrules
В листинге 8.22 показаны инструкции импорта, статические переменные и метод init для сервлета CompanyNewsServ.
Листинг 22. Начало кода метода CompanyNewsServ (ComanyNewsServ.java)
- package com.XmlEcomBook.Chap08 ;
-
- import com.XmlEcomBook.DOMlibrary ;
- import java.io.*;
- import java.util.* ;
- import javax.servlet.*;
- import javax.servlet.http.*;
- import org.w3c.dom.* ;
-
- public class CompanyNewsServ extends HttpServlet
- {
- static String workDir = "E:\\scripts\\CompanyNews" ;
- static String propfile = "conewserv.properties" ;
- static String newsFile = "thenews.xml" ;
- static String handler = "http://localhost/servlet/conewserv" ;
- static String version = "v0.12";
- static String pversion = "" ;
- static Properties cnProp ;
- static String brcrlf = "<br />\r\n" ;
-
- public void init(ServletConfig config) throws ServletException
- {
- super.init(config);
- String tmp = config.getInitParameter("workdir");
- if( tmp != null ) workDir = tmp ;
- tmp = config.getInitParameter("propfile");
- if( tmp != null ) propfile = tmp ;
- System.out.println("Start CompanyNewsServ using " + workDir );
- File f = new File( workDir, propfile );
- try { cnProp = new Properties();
- cnProp.load( new FileInputStream(f) );
- tmp = cnProp.getProperty("handler");
- if( tmp != null ) handler = tmp ;
- tmp = cnProp.getProperty("newsfile");
- if( tmp != null ) newsFile = tmp ;
- pversion = cnProp.getProperty("version");
- System.out.println("Loaded properties for
- CompanyNewsServ: " + handler + " file:" + newsFile );
- }catch(IOException e){
- System.out.println("Error loading " + e );
- }
- }
Метод doGet, как показано в листинге 8.23, проверяет введенные пользователем имя и пароль, сравнивая их с данными в файле свойств, загруженном при инициализации сервлета. Если обнаруживается, что имя соответствует паролю, вызывается метод generateForm, создающий форму HTML для ввода текста нового сообщения.
Листинг 23. Метод doGet создает форму для ввода нового сообщения (CompanyNewsServ.java)
- public void doGet(HttpServletRequest req,
- HttpServletResponse resp)
- throws ServletException, IOException
- {
- resp.setContentType("text/html");
- PrintWriter out = new PrintWriter(resp.getOutputStream());
- String username = req.getParameter("username");
- String password = req.getParameter("password");
- String action = req.getParameter("action");
- String tmp = cnProp.getProperty(username);
- boolean userok = false ;
- if( tmp != null ){
- userok = tmp.equals( password );
- }
- header( out );
- if( userok ){
- generateForm( out, username, password );
- }
- else {
- out.println("<p>User: " + username + " password: " + password + " not found.</p>" );
- }
- footer( out );
Заполненная форма посылается методу doPost. Как показано в листинге 8.24, различные текстовые элементы извлекаются и передаются объекту NewsUpKeep с помощью метода addItem.
Листинг 24. Метод doPost собирает данные из формы (CompanyNewsServ.java)
- public void doPost(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- resp.setContentType("text/html");
- PrintWriter out = new PrintWriter(resp.getOutputStream());
- String username = req.getParameter("username");
- String password = req.getParameter("password");
- String action = req.getParameter("action");
- String head = req.getParameter("head");
- String date = req.getParameter("date");
- String topics = req.getParameter("topics");
- String shrtStr = req.getParameter("short").trim();
- String longStr = req.getParameter("long").trim();
- DOMlibrary library = DOMlibrary.getLibrary();
- File f = new File( workDir, newsFile );
- try {
- NewsUpKeep nup = new NewsUpKeep( f );
- nup.addItem( head, date, topics, username, shrtStr, longStr );
- header( out );
- out.println("NewsUpKeep is " + nup + "<br />");
- footer( out );
- }catch( Exception e){
- errorMsg( out, "CompanyNewsServ.doPost ", e );
- }
- }
Форма HTML для ввода новых сообщений создается методом generateForm, как показано в листинге 8.25. Заметим, что имя пользователя и пароль вставлены в форму в виде скрытых значений.
Листинг 25. Метод generateForm создает форму для ввода (CompanyNewsServ.java)
- private void generateForm( PrintWriter out, String name, String pw ){
- out.println("<h2>Enter Company News Item Data</h2>");
- out.println("<form method=\"POST\" action= \"" + handler + "\" >");
- out.println("Headline - 80 char max<br />");
- out.println("<input type=\"text\" maxlength=\"80\" size=
- \"60\"" + " name=\"head\" ><br />" );
- out.println("Dated <br />");
- out.println("<input type=\"text\" maxlength=\"50\" size=
- \"40\"" + " name=\"date\" value=
- \"" + new Date().toString() + "\" ><br />" );
- out.println(
- "Topics separated by commas - please stick to the official list.<br />");
- out.println("<input type=\"text\" maxlength=\"80\" size=
- \"60\"" + " name=\"topics\" ><br />" );
- out.println("Short version <br />");
- out.println("<textarea cols=\"60\" rows=\"3\" name=
- \"short\" >");
- out.println("</textarea><br />");
- out.println("Long version <br />");
- out.println("<textarea cols=\"60\" rows=\"10\" name=\"long\" >");
- out.println("</textarea><br />");
- out.println("<input type=\"hidden\" name=\"username\" value=
- \"" + name + "\"><br>" );
- out.println("<input type=\"hidden\" name=\"password\" value=
- \"" + pw + "\" ><br>");
- out.println(
- "<input type=\"submit\" name=\"action\" value=
- \"Submit\" ><br />" );
- out.println("</form></center>");
- }
Наконец, в сервлете имеются обычные вспомогательные методы, показанные в листинге 8.26. Естественно, вам нужно будет заменить адрес в тексте сообщения на свой собственный либо предоставить переменную типа String, которая инициализируется в файле свойств.
Листинг 26. Служебные методы в сервлете CompanyNewsServ (CompanyNewsServ.java)
- // предполагается, что ответ был задан как text/html
- private void errorMsg( PrintWriter out, String msg, Exception ex ){
- header( out );
- out.println("<h2>Error: " ); out.println( msg );
- out.println("</h2><br>");
- if( ex != null ){
- ex.printStackTrace( out );
- }
- out.println("<br>");
- out.println("<a href=\"mailto:wbrogden@bga.com\">" +
- "Please mail me the error message.</a><br>");
- footer( out );
- }
-
- private void header(PrintWriter out ){
- out.println("<html>");
- out.println("<head><title>Company News
- Servlet</title></head>");
- out.println("<body>");
- }
-
- private void footer(PrintWriter out ){
- out.println("<hr><br>Company News Servlet " + version +
- " properties: <br>" );
- out.println("</body>");
- out.println("</html>");
- out.close();
- }
- }
Класс NewsUpKeep
Классу NewsUpKeep передаются существующий объект DOM <Newsfile> и различные текстовые строки, которые составляют новый элемент <Newsitem>; он переписывает файл XML с сообщениями. Применить этот метод проще, чем создать элемент <Newsitem> и вставить его модель DOM, которая постоянно находится в памяти; кроме того, указанный метод гарантирует, что файл новостей будет обновлен правильным образом. Также при использовании этого метода исключается возможность того, что один из отображающих методов получит частично измененную модель DOM.
В листинге 8.27 показано начало кода класса NewsUpKeep. Конструктор класса использует полное имя файла для получения DOM из DOMlibrary. Поскольку эта модель DOM не модифицируется при переписывании файла XML, вам не нужно беспокоиться по поводу возможности одновременного доступа к этому объекту класса NewsFormatter.
Заметим, что в классе NewsUpKeep создается переменная rootNNM типа NamedNodeMap, которая содержит имена и значения атрибутов корневого элемента документа XML, <Newsfile>. Также конструктор помещает все узлы <Newsitem> в массив itemNodes.
Листинг 27. Начало класса NewsUpKeep (NewsUpKeep.java)
- package com.XmlEcomBook.Chap08;
-
- import com.XmlEcomBook.DOMlibrary ;
- import java.io.*;
- import java.util.* ;
- import javax.servlet.*;
- import javax.servlet.http.*;
- import org.w3c.dom.* ;
-
- public class NewsUpKeep
- {
- File newsFile ;
- String newsFileName ;
- Node[] itemNodes ;
- NamedNodeMap rootNNM ; // для атрибутов корневого элемента
-
- public NewsUpKeep( File f) throws IOException {
- newsFile = f ;
- newsFileName = f.getAbsolutePath() ;
- DOMlibrary library = DOMlibrary.getLibrary();
- Document doc = library.getDOM( newsFileName );
- if( doc == null ){
- throw new FileNotFoundException( newsFileName );
- }
- Element re = doc.getDocumentElement();
- rootNNM = re.getAttributes();
- System.out.println("Root has " + rootNNM.getLength() + " attributes");
- NodeList newsItemNodes = doc.getElementsByTagName("Newsitem");
- int ct = newsItemNodes.getLength();
- itemNodes = new Node[ ct ];
- for( int i = 0 ; i < ct ; i++ ){
- itemNodes[i] = newsItemNodes.item( i );
- }
- }
В листинге 8.28 показаны некоторые вспомогательные методы, необходимые в классе NewsUpKeep. Метод formatTopics гарантирует, что строка, которая будет записана в качестве значения атрибута topic, имеет правильный формат.
Листинг 28. Различные вспомогательные функции класса NewsUpKeep (NewsUpKeep.java)
- // проверяем отсутствие пробелов перед названиями
- // тематических категорий и после них, а также наличие запятых
- // между названиями, например: general, food, и т. д.
- private String formatTopics(String s ){
- if( s.indexOf(',') < 0 ) return s.trim();
- // единственным разделителем является запятая
- StringTokenizer st = new StringTokenizer( s, "," );
- StringBuffer sb = new StringBuffer( s.length() );
- while( st.hasMoreTokens() ){
- sb.append( st.nextToken().trim() );
- if( st.hasMoreTokens() ) sb.append(',');
- }
- return sb.toString();
- }
- // преобразует миллисекунды в дни
- private String timeInDays(){
- long t = System.currentTimeMillis() ;
- int tid = (int)(t / ( 1000 * 60 * 60 * 24 ));
- return Integer.toString( tid );
- }
-
- // предполагается, что s - это десятичное число,
- // используемое в <Newsitem id=
- private String incrementID(String s ){
- try{
- int n = Integer.parseInt( s );
- return Integer.toString( n + 2 );
- }catch(NumberFormatException e){
- return s + "a" ;
- }
- }
-
- public String toString(){
- StringBuffer sb = new StringBuffer("NewsUpKeep ");
- sb.append(" Newsitem count: " );
- sb.append( Integer.toString( itemNodes.length ));
- return sb.toString();
- }
Теперь мы подходим к основному рабочему методу, addItem. Сначала этот метод создает новый файл с временным именем и записывает туда стандартное объявление XML и комментарии. Затем создается тег <Newsfile>, куда записываются имена атрибутов и их значения из коллекции rootNNM.
Как видно из листинга 8.29, атрибут nextid обрабатывается специальным образом. Сохраняется текущее значение, которое становится значением атрибута id нового элемента <Newsitem>, а увеличенное значение записывается в тег <Newsfile>.
Листинг 29. Начало метода addItem (NewsUpKeep.java)
- // новые сообщения всегда добавляются в начало файла,
- // поэтому нужно переделать начало корневого элемента
- public void addItem(
- String head, String date, String topics, String author,
- String shrtStr,
- String longStr ) throws IOException {
- String idVal = "" ;
- String tmpfile = newsFileName + "$$" ;
- File f = new File( tmpfile );
- FileWriter fw = new FileWriter(f);
- PrintWriter out = new PrintWriter( new BufferedWriter( fw ) );
- out.println("<?xml version=\"1.0\" standalone=\"yes\" ?>");
- out.println("<!-- output by NewsUpKeep -->");
- int ct = rootNNM.getLength();
- if( ct == 0 ){
- out.println("<Newsfile>");
- }
- else {
- out.print("<Newsfile ");
- for( int i = 0 ; i < ct ; i++ ){
- Node an = rootNNM.item(i);
- String name = an.getNodeName();
- String val = an.getNodeValue();
- out.print( name + "=\"" );
- if( name.equals("nextid") ){
- idVal = val ;
- val = incrementID( val );
- }
- out.print( val + "\" ");
- }
- out.println(" >");
- }
Затем, как показано в листинге 8.30, пишется новый тег <NewsItem>, за которым следует заголовок сообщения, указывается дата и приводятся краткая и полная версии текста сообщения. Для того чтобы записать старые элементы <NewsItem>, вызывается метод writeNewsNode. После закрытия временного файла старый файл XML удаляется, а временный файл получает имя. Следующий раз, когда этот файл будет запрошен, класс DOMlibrary по изменившейся метке даты модификации файла (timestamp) определит, что нужно считывать новый файл.
Листинг 30. Метод addItem, продолжение (NewsUpKeep.java)
- out.print("<Newsitem timestamp=\"");
- out.print( timeInDays() + "\" topic=\"");
- out.print( formatTopics( topics ) );
- out.println( "\" author=\"" + author + "\" id= \"" + idVal + "\" >");
- // конец <Newsitem .. >
- out.println("<head>" + head.trim() + "</head>" );
- out.println("<date>" + date.trim() + "</date>" );
- out.println("<short><![CDATA[");
- out.println( shrtStr.trim() );
- out.println("]]></short>");
- out.println("<long><![CDATA[");
- out.println( longStr ); out.println("]]></long>");
- out.println("</Newsitem>");
- for( int i = 0 ; i < itemNodes.length ; i++ ){
- writeNewsNode(out, (Element)itemNodes[i] );
- }
- out.println("</Newsfile>");
- out.flush(); out.close();
- File forig = new File( newsFileName );
- DOMlibrary library = DOMlibrary.getLibrary();
- // чтобы предотвратить перекрывание операций XML
- synchronized( library ){
- forig.delete();
- if( !f.renameTo( forig )){
- System.out.println("NewsUpKeep.addItem rename failed") ;
- }
- }
- }
Метод writeNewsNode, который записывает отдельный элемент <Newsitem>, показан в листинге 8.31.
Листинг 31. Метод, который записывает отдельный элемент из DOM (NewsUpKeep.java)
- // записывает элемент <Newsitem>, воспроизводя все атрибуты
- public void writeNewsNode(PrintWriter out, Element e) {
- NamedNodeMap nnm = e.getAttributes();
- out.print("<Newsitem " ) ; //timestamp=\"");
- int i ;
- for( i = 0 ; i < nnm.getLength() ; i++ ){
- Attr na = (Attr) nnm.item(i); // Attr - это
- // узел под номером i
- String atr = na.getName();
- String val = na.getValue();
- out.print( atr ); out.print("=\"");
- out.print( val ); out.print("\" ");
- }
- out.println(">");
- NodeList nl = e.getChildNodes();
- int ct = nl.getLength();
- for( i = 0 ; i < ct ; i++ ){
- Node nde = nl.item( i );
- if( nde instanceof Element ){
- Element ce = (Element)nde;
- String name = ce.getTagName();
- out.print("<" + name + ">");
- NodeList chnl = ce.getChildNodes() ;
- if( chnl.getLength() == 0 ) continue ;
- Node chn = chnl.item(0);
- if( name.equals("long") || name.equals("short") ){
- out.print("<![CDATA[");
- out.println( chn.getNodeValue().trim() );
- out.print("]]>");
- }
- else { out.print( chn.getNodeValue() );
- }
- out.println("</" + name + ">");
- }
- } // цикл по дочерним узлам <Newsitem>
- out.println("</Newsitem>");
- }
-
- }
На основе нашего опыта добавления новых сообщений с помощью сервлета CompanyNewsServ вы можете сначала создать полный текст сообщения в текстовом редакторе, а затем при работе с формой HTML для ввода текста просто вставить его в форму с помощью команд вырезания и вставки.
Авторы: Брогден Б., Минник К.
Цель этой книги — познакомить разработчиков с технологиями использования XML в программах на Java для электронной коммерции. Для того чтобы построить эффективный коммерческий сайт, мало пройти долгий путь обучения программированию на Java — недостающим звеном останется XML, и эта книга содержит подробное описание методики объединения этих тесно связанных технологий. В ней рассматриваются последние версии интерфейсов API сервлетов и JSP и текущий стандарт XML, и подробно описываются все этапы, необходимые для построения хорошо организованного, динамичного и успешного сайта.
Авторы предполагают, что читатель на базовом уровне знаком с HTML, Java и web-серверами, и у него имеется доступ к компьютеру, на котором можно установить небольшой web-сервер, стандартный компилятор Java и другие служебные программы. Исходный код всех программ, в большом количестве иллюстрирующих материал книги, можно найти на прилагаемом компакт-диске.
Заказать книгу вы сможете в Издательском доме "Питер" - http://shop.piter.com/to_order.phtml?postid=978531800400&refer=10000
Содержание:
Благодарности
Введение
- XML и Java
- Сервлеты Java и JSP-страницы
- Кому стоит купить эту книгу
- Что вам потребуется
- Что содержится в этой книге
- Что содержится на компакт-диске
- Соглашения, используемые в этой книге
- Об авторах
- От издательства
Глава 1. Описание данных с помощью XML
- Введение в XML
- XML и электронная коммерция
- Области применения XML
- XML на стороне клиента
- XML на стороне сервера
- Использование XML для хранения данных
- Правила XML
- Определение правильно оформленного документа XML
- DTD и допустимость документа XML
- Объявления элементов
- Спецификация содержимого
- Объявления атрибутов
- Объявления сущностей
- Непроверенные символьные данные
- Разделы символьных данных
- Комментарии в XML
- Инструкции по обработке
- Схема XML
- Создание таблиц стилей с использованием XSL
- Использование XML в приложениях
- Модели программирования
- Программирование на основе DOM
- Программирование на основе SAX
- Краткий справочник по правилам XML
- Требования к правильно оформленному документу XML
- Элементы
- Применение
- Объявление
- Атрибуты
- Сущности
- Применение
- Объявления
Глава 2. Каталог товаров на XML
- Назначение web-сайта
- Требования
- Ограничения
- Покупка готового пакета - это быстрее и дешевле
- Сборка приложения из стандартных частей - это лучше и дешевле
- Разработка сервера web-приложений - это быстрее и лучше
- Достоинства XML
- Доступность инструментальных средств
- SOAP
- Гибкость инструментальных средств
- Совместимость инструментальных средств
- Поддержка Unicode
- Каталог товаров и DTD
- Организация данных
- Написание DTD
- Доработка DTD
- Создание первого чернового варианта DTD
- Уточнение чернового варианта
- Элементы catalog, product_line и product
- Элементы description, paragraph и general
- Элементы price, quantity_in_stock и image
- Элементы onsale_date, time, clip и title
- Элемент shipping
- Финальная версия
- Принципы разработки DTD
- Репрезентативный образец данных
- Избегайте субтрактивного уточнения
- Упрощайте DTD
- Элементы или атрибуты?
- Рассуждайте в терминах реальных процессов
- Шаблоны XML
- Краткое описание
- Задача
- Контекст
- Причины
- Решение
- Пример
- Обсуждение
- Близкие по назначению шаблоны
- Известные примеры применения
- Стандартизация DTD
Глава 3. Представление XML-каталога в сети
- Технологии представления
- Взаимодействие по протоколу HTTP
- Запрос браузера
- Ответ web-сервера
- API для сервлетов Java
- Классы и интерфейсы для сервлетов Java
- Обработка запросов сервлетами
- Простой пример сервлета
- Инициализация сервлета
- Генерирование ответа сервлетом
- Роль JavaBeans
- API для JSP-страниц
- Язык тегов JSP
- Пользовательские библиотеки тегов
- Обработка запросов JSP
- Встроенные переменные в JSP-страницах
- Организация каталога
- API для объектной модели документа
- Создание объектной модели документа для каталога товаров
- Интерфейс Node
- Интерфейс NodeList
- Интерфейс Document
- Индексация товаров
- Информация для представления каталога в сети
- Организация поиска по ключевым словам
- Форматирование описаний товаров
- Гибкость стилей
- Гибкость содержимого
Глава 4. Заполнение корзины покупателя
- Проблема корзины покупателя
- Использование объектов класса HttpSession
- API для класса HttpSession
- Интерфейс HttpSessionBindingListener
- Корзина покупателя на языке Java
- Классы CartItem и ShoppingCart
- Класс CatalogServ
- Инициализация сервлета
- Методы doGet и doPost
- Метод doPageEnd
- Метод doPageMid
- Отображение полной информации о товаре
- Использование класса cartListener
- Класс CatalogBean
- Использование объектов CartItem и ShoppingCart
- Класс ProductFormatter
- Методы, вызываемые методом doFullItem
- Метод doListOutput
- Метод addText
Глава 5. Оплата и подтверждение заказа
- Процесс оплаты
- Безопасность
- Доверие клиента
- Сбор информации о заказе
- Класс CustomerInfo
- Класс CreditInfo
- Класс Fullfilment
- Класс Authorization
- Класс Order
- Класс TestPaymentAuthorizer
- Класс ShippingCalculator
- Класс Emailer
- Страница CustomerInfo
- JSP-страница ShippingInfo
- JSP-страница CreditInfo
- JSP-страница ConfirmInfo
- Сервлет SubmitOrder
- JSP-страница Approved
- JSP-страница Declined
- Обновление информации о доставке
- JSP-страница OrderDateSelector
- JSP-страница SelectOrder
- JSP-страница ShowOrder
- JSP-страница UpdateFullfilment
Глава 6. Обслуживание виртуального каталога
- Общие принципы редактирования каталога
- Объекты данных
- Класс Catalog
- Класс XMLWriter
- Класс ProductLine
- Класс Product
- Класс Image
- Класс Clip
- Класс DateTime
- Класс Util
- Код для представления информации пользователю
- Главная HTML-страница
- Класс Main сервлета
- JSP-страница Delete
- JSP-страница Edit
- Сервлет UpdateProduct
Глава 7. Поиск своего покупателя с помощью опросов
- Обеспечение конфиденциальности
- Промышленные стандарты
- Общественные организации
- Создание системы опросов с помощью XML-сценария
- Определение последовательности вопросов
- Пример анкеты
- Сервлет управления опросом
- Код сервлета управления опросом
- Класс Interpreter
- Отображение вопросов
- Отображение введения
- Управление ветвлением опроса
- Обработка элемента Terminal
- Вспомогательные методы класса Interpreter
- Класс Recorder
- Варианты анализа анкеты
- Класс для создания файлов снимков
- Класс для создания таблиц
- Пример сервлета для просмотра результатов отчета
- Служебная библиотека документа XML
Глава 8. Новости на сайте
- Разработка системы показа новостей
- Гибкость отображения
- Элементы текстов сообщений
- Расположение сообщений в зависимости от их новизны
- Информация для управления сообщениями
- Корневой элемент документа
- Простота ввода данных
- Минимальная нагрузка на сервер
- Система показа новостей
- Внешний вид web-страницы
- Класс NewsFormatter
- Использование класса NewsFormatter
- Код для сервлета TheNewsServ
- Пример JSP-страницы
- Добавление свежих новостей
- Сервлет CompanyNewsServ
- Класс NewsUpKeep
Глава 9. Привлечение постоянных посетителей
- Источники новостей и стандарты
- Формат RSS
- NewsML и планы стандартизации
- Формат сообщений Moreover.com
- Получение файла XML
- Класс NewsModel
- Создание DOM
- Выбор заголовков
- Главный управляющий класс
- Классы для отображения заголовков
- Сервлет NetNewsServ
- Класс NetNewsBean
- Возможные усовершенствования
Глава 10. Web-приложения на Java
- Спецификации в интерфейсе API сервлетов Java
- Развертывание web-приложения
- Определение web-приложения
- Структура папок
- Содержимое папки WEB-INF
- Дескриптор развертывания web-приложения
- Параметры конфигурации
- Архивные файлы web-приложения
- Следующее поколение
- Следующее поколение XML
- Протокол SOAP
- Проблемы масштабирования
- Сохранность информации о сеансе
- J2EE и Enterprise JavaBean
- Альтернативное решение - Spaces
Приложение А. Интерфейсы API для сервлетов и JSP-страниц
- Параметры приложений
- Создание сервлета
- Методы класса HttpServlet
- Интерфейс ServletContext
- Получение информации о запросе
- Методы, добавляемые интерфейсом HttpServletRequest
- Методы, связанные с безопасностью
- Методы, связанные с сеансами и cookie
- Другие методы интерфейса ServletRequest
- Класс HttpSession и классы, связанные с cookie
- Методы класса HttpSession
- Методы интерфейса HttpSessionListener
- Методы класса Cookie
- Специальные объекты, связанные с запросом
- Формирование ответа пользователю
- Методы интерфейса ServletResponse
- Методы, добавляемые интерфейсом HttpServletResponse
- Выходные данные JSP-страниц
- Ошибки и исключения
- Ошибки и исключения JSP-страниц
- Коды состояний и ошибок HTTP
- Интерфейс API для JSP-страниц
- Класс PageContext
- Доступ к стандартным переменным
- Класс JspWriter
- Пакет javax.servlrt.jsp.tagext
- Класс BodyContent
Приложение Б. Словарь терминов
Алфавитный указатель