 | Уровень сложности: средний Рене Павлитцек, инженер-исследователь, IBM
10.09.2007 Интегрированная среда Hamlet была разработана для расширения возможностей сервлетов Java™ и обеспечения разделения контента и его представления. Из этой статьи вы узнаете о новых способах предоставления динамического контента. Рене Павлитцек (Rene Pawlitzek) совершенствует эту среду и развивает возможности механизма определения шаблонов.
Hamlets – это удобная в использовании и простая в понимании среда разработки Web-приложений. Она не только поддерживает, но и обеспечивает полное отделение контента от его представления. Ее простая и элегантная архитектура не скрывает лежащую в ее основе знакомую инфраструктуру сервлетов.
Основой среды является расширение сервлета, получившее название Hamlet. Hamlet с помощью интерфейса Simple API for XML (SAX) считывает файлы шаблонов, задающих представления. (Формат этих файлов должен быть таким, чтобы его мог разобрать SAX – строгий HTML, XHTML или XML). Считав файл шаблона, Hamlet вызывает несколько внешних функций, которые позволяют динамически вставить контент в места, отмеченные специальными тегами и идентификаторами.
Название Hamlets, используемое в этой статье, обозначает внутренний проект разработки интегрированной среды на базе сервлетов, обеспечивающей разделение контента и представления в Web-приложениях. Прежде чем приступить к чтению статьи, вам следует ознакомится с предыдущими статьями этой серии, опубликованными на сайте IBM developerWorks: Введение в Hamlets (EN), и, при желании, с моим руководством developerWorks Программирование Hamlets(EN). Ссылки на оба материала можно найти в разделе Ресурсы.
В этой статье описываются различные вопросы применения Hamlets. Базовая реализация Hamlet (версия 1.0), в которой используется всего два публичных класса Java (менее 500 строк кода Java), была описана в руководстве "Программирование Hamlets". В коде, рассматриваемом здесь (версия 1.1), содержится два интерфейса Java и четыре публичных Java-класса общим объемом 695 строк Java. Эта более развитая и лучше структурированная реализация (см. UML-диаграмму на Рисунке 1) подробно рассматривается в этой статье. Кроме того, из этой статьи вы также узнаете о новом способе программирования Hamlets с помощью HamletHandlers. Вы можете загрузить файл wa-hamlets3-code.zip – архив, в котором содержится код всех примеров, использованных в этой статье.
Рисунок 1. UML-диаграмма для Hamlets 1.1
Интерфейсы TemplateEngine и ContentHandler и класс DefaultEngine
В основе среды Hamlet лежит механизм шаблонов – класс, реализующий интерфейс TemplateEngine, который показан в Листинге 1.
Листинг 1. Интерфейс TemplateEngine определяет функциональные возможности механизма шаблонов
public interface TemplateEngine {
public void perform (InputStream in, ContentHandler handler, PrintWriter out)
throws Exception;
} // TemplateEngine
|
В механизме шаблонов реализован публичный метод perform(), который берет шаблон и разбирает его. В это время вызывается несколько внешних функций, формирующих динамическое содержание, которым механизм заполнит шаблон. Входные параметры (шаблон) передаются через InputStream, а результат (заполненный шаблон) получается посредством PrintWriter. Обработчик реализует функции внешнего вызова и предоставляет интерфейс Hamlet ContentHandler, показанный в Листинге 2.
Листинг 2. Интерфейс ContentHandler определяет сигнатуры функций внешнего вызова
public interface ContentHandler {
public int getElementRepeatCount (String id, String name, Attributes atts)
throws Exception;
public String getElementReplacement (String id, String name, Attributes atts)
throws Exception;
public Attributes getElementAttributes (String id, String name, Attributes atts)
throws Exception;
public InputStream getElementIncludeSource (String id, String name, Attributes atts)
throws Exception;
} // ContentHandler
|
В классе DefaultEngine представлена стандартная реализация интерфейса TemplateEngine, а для парсинга шаблона используется SAX. Его метод perform() показан в Листинге 3.
Листинг 3. Класс DefaultEngine предоставляет стандартную реализацию интерфейса TemplateEngine
public class DefaultEngine extends DefaultHandler implements TemplateEngine {
...
public void perform (InputStream in, ContentHandler handler, PrintWriter out)
throws Exception {
XMLReader reader = null;
try {
this.out = out;
this.handler = handler;
reader = readerPool.getReader ();
InputSource inputSource = new InputSource (in);
reader.setErrorHandler (this);
reader.setContentHandler (this);
reader.parse (inputSource);
} finally {
if (reader != null)
readerPool.returnReader (reader);
} // try
} // perform
...
} // DefaultEngine
|
Код, приведенный в Листинге 3, выбирает из пула считывающих модулей модуль SAX, упаковывает входной параметр in в InputSource, устанавливает обработчики контента и ошибок SAX и начинает разбирать входящие параметры (шаблон). По завершении обработки модуль SAX возвращается в пул модулей. Для повышения производительности выполняется буферизация SAX, поскольку создание нового модуля для каждого вызова будет занимать слишком много времени.
Во время обработки шаблона вызываются методы обработчиков контента SAX (startDocument(), endDocument(), startElement(), endElement() и characters()). Их реализация представлена в Листинге 4.
Листинг 4. В классе DefaultEngine реализованы методы обработчиков контента SAX
public class DefaultEngine extends DefaultHandler implements TemplateEngine {
...
public void startDocument () throws SAXException {
root = new TreeNode ();
curNode = root;
} // startDocument
public void endDocument () throws SAXException {
curNode = null;
root = null;
} // endDocument
public void startElement (String namespaceURI, String localName, String qName,
Attributes atts) throws SAXException {
try {
// category.debug ("local name: " + localName + ", qname: " + qName);
if (REPEAT_TAG.equals (localName)) {
record = true;
StartElement elem = new StartElement (namespaceURI, localName, qName, atts);
curNode.add (elem);
TreeNode newNode = new TreeNode ();
curNode.insert (newNode, 0);
curNode = newNode;
} else if (REPLACE_TAG.equals (localName)) {
if (record) {
StartElement elem = new StartElement (namespaceURI, localName, qName, atts);
curNode.add (elem);
} else {
ignore = true;
String id = atts.getValue (ID_ATTRIBUTE);
if (id != null)
out.print (handler.getElementReplacement (id, localName, atts));
} // if
} else if (INCLUDE_TAG.equals (localName)) {
if (record) {
StartElement elem = new StartElement (namespaceURI, localName, qName, atts);
curNode.add (elem);
} else {
ignore = true;
String id = atts.getValue (ID_ATTRIBUTE);
if (id != null)
atts = handler.getElementAttributes (id, localName, atts);
InputStream in = handler.getElementIncludeSource (id, localName, atts);
if (in != null) {
String line;
BufferedReader r = new BufferedReader (new InputStreamReader (in));
while ((line = r.readLine ()) != null)
out.println (line);
r.close ();
} // if
} // if
} else {
if (record) {
StartElement elem = new StartElement (namespaceURI, localName, qName, atts);
curNode.add (elem);
} else {
String id = atts.getValue (ID_ATTRIBUTE);
if (id != null)
atts = handler.getElementAttributes (id, localName, atts);
out.print ("<" + localName);
for (int i = 0; i < atts.getLength (); i++) {
out.print (" ");
out.print (atts.getLocalName (i));
out.print ("=\"");
out.print (atts.getValue (i));
out.print ("\"");
} // for
out.print (">");
} // if
} // if
} catch (Exception e) {
category.debug (e.getMessage (), e);
throw new SAXException (e);
} // try
} // startElement
public void endElement (String namespaceURI, String localName, String qName)
throws SAXException {
try {
if (REPEAT_TAG.equals (localName)) {
curNode = (TreeNode) curNode.getParent ();
EndElement elem = new EndElement (namespaceURI, localName, qName);
curNode.add (elem);
if (curNode.equals (root)) {
record = false;
playback (root);
root = new TreeNode ();
curNode = root;
} // if
} else if (REPLACE_TAG.equals (localName) || INCLUDE_TAG.equals (localName)) {
if (record) {
EndElement elem = new EndElement (namespaceURI, localName, qName);
curNode.add (elem);
} else {
ignore = false;
} // if
} else {
if (record) {
EndElement elem = new EndElement (namespaceURI, localName, qName);
curNode.add (elem);
} else {
out.print ("</" + localName + ">");
} // if
} // if
} catch (Exception e) {
category.debug (e.getMessage (), e);
throw new SAXException (e);
} // try
} // endElement
public void characters (char[] ch, int start, int length) throws SAXException {
String str = new String (ch, start, length);
if (record) {
CharElement elem = new CharElement (str);
curNode.add (elem);
} else {
if (!ignore)
out.print (str);
} // if
} // characters
...
} // DefaultEngine
|
Перед началом фактической обработки шаблона вызывается метод startDocument(). Он инициализирует два экземпляра переменных (root и curNode), используемых для записи. После того, как парсер закончит свою работу, метод endDocument() присваивает обеим переменным значение null.
Когда парсер SAX обнаруживает открывающий и закрывающий теги, он вызывает методы startElement() и endElement() соответственно. Если встречаются перечисленные ниже теги, выполняются особые действия:
-
<REPEAT>
-
</REPEAT>
-
<REPLACE>
-
</REPLACE>
-
<INCLUDE>
-
</INCLUDE>
Эти теги включаются в файлы шаблона для отметки мест, где во время обработки добавляется динамический контент.
Когда парсер SAX доходит до тега <REPEAT>, метод startElement() устанавливает флаг record в значение true, что можно увидеть в Листинге 5, поэтому все последующее содержимое файла шаблона записывается в древовидной структуре с помощью четырех внутренних классов TreeNode, StartElement, EndElement и CharElement. Древовидная структура необходима для обеспечения возможности использования вложенных тегов <REPEAT>.
Листинг 5. Обработка тега <REPEAT> в методе startElement() начинает запись контента
if (REPEAT_TAG.equals (localName)) {
record = true;
...
}
|
Чтобы остановить запись, когда встречается соответствующий тег </REPEAT>, метод endElement() устанавливает флаг record в значение false, как видно из Листинга 6.
Листинг 6. Обработка тега </REPEAT> в методе endElement() останавливает запись контента и вызывает воспроизведение контента
if (REPEAT_TAG.equals (localName)) {
...
record = false;
playback (root);
...
}
|
Метод endElement() также вызывает функцию playback(), приведенную в Листинге 7, которая вызывает внешнюю функцию getElementRepeatCount() метода ContentHandler для получения числа повторений N. (Метод getElementRepeatCount() также вызывается в ходе воспроизведения записи, останавливая его возвращением значения 0). После этого производится воспроизведение записанного контента (дерева элементов) N раз. Чтобы воспроизвести записанные элементы, вызываются методы startElement(), endElement() и characters(); эти методы обрабатывают записанный контент точно так же, как если бы это был обычный (не записанный) контент. Обратите внимание, что функция playback() рекурсивная, поскольку теги <REPEAT> могут быть вложенными.
Листинг 7. Функция playback() повторяет записанный контент N раз
private void playback (TreeNode node) throws Exception {
int childIndex = node.getChildCount () - 1;
for (int i = 0; i < node.getCount (); i++) {
Object obj = node.elementAt (i);
if (obj instanceof StartElement) {
StartElement elem = (StartElement) obj;
if (REPEAT_TAG.equals (elem.localName)) {
int count = 0;
String id = elem.atts.getValue (ID_ATTRIBUTE);
if (id != null)
count = handler.getElementRepeatCount (id, elem.localName, elem.atts);
for (int j = 0; j < count; j++) {
playback ((TreeNode) node.getChildAt (childIndex));
if (handler.getElementRepeatCount (id, elem.localName, elem.atts) == 0)
break;
} // if
childIndex--;
} else
startElement (elem.namespaceURI, elem.localName, elem.qName, elem.atts);
} else if (obj instanceof EndElement) {
EndElement elem = (EndElement) obj;
if (!REPEAT_TAG.equals (elem.localName))
endElement (elem.namespaceURI, elem.localName, elem.qName);
} else if (obj instanceof CharElement) {
CharElement elem = (CharElement) obj;
characters (elem.str.toCharArray (), 0, elem.str.length ());
} // if
} // for
} // playback
|
Когда парсер SAX встречает тег <REPLACE> при отключенной записи, если определен атрибут ID, метод startElement() вызывает метод getElementReplacement() класса ContentHandler. Метод getElementReplacement() возвращает динамически созданный контент, который включается в результат с помощью метода print(). Ранее флагу ignore было присвоено значение true, что означает, что значение для подстановки в файле шаблона (контент между тегами <REPLACE> и </REPLACE>) игнорируется и не включается в результат, возвращаемый методом characters().
Листинг 8. Обработка тега <REPLACE> в методе startElement()
if (REPLACE_TAG.equals (localName)) {
if (record) {
...
} else {
ignore = true;
String id = atts.getValue (ID_ATTRIBUTE);
if (id != null)
out.print (handler.getElementReplacement (id, localName, atts));
} // if
} ...
|
Таким же образом парсер SAX обрабатывает тег <INCLUDE> и, при выключенной записи, метод startElement() вызывает метод ContentHandler класса getElementIncludeSource() для извлечения контента, который впоследствии включается в результат. Перед этим вызывается метод getElementAttributes(), который позволяет Hamlet изменять атрибуты в случаях, если в теге <INCLUDE> содержится атрибут ID. Кроме того, флагу ignore присваивается значение true, чтобы игнорировать значение для подстановки в файле шаблона (содержимого между тегами <INCLUDE> и </INCLUDE>).
Листинг 9. Обработка тега <INCLUDE> в методе startElement()
if (INCLUDE_TAG.equals (localName)) {
if (record) {
...
} else {
ignore = true;
String id = atts.getValue (ID_ATTRIBUTE);
if (id != null)
atts = handler.getElementAttributes (id, localName, atts);
InputStream in = handler.getElementIncludeSource (id, localName, atts);
if (in != null) {
String line;
BufferedReader r = new BufferedReader (new InputStreamReader (in));
while ((line = r.readLine ()) != null)
out.println (line);
r.close ();
} // if
} // if
} ...
|
Когда парсер SAX находит теги </REPLACE> или </INCLUDE>, метод endElement() присваивает флагу ignore значение false, как видно из Листинга 10.
Листинг 10. Обработка тегов </REPEAT> и </INCLUDE> в методе endElement()
if (REPLACE_TAG.equals (localName) || INCLUDE_TAG.equals (localName)) {
if (record) {
...
} else {
ignore = false;
} // if
} ...
|
Для всех иных тегов, содержащих атрибут ID, в режиме отключенной записи метод startElement() вызывает метод getElementAttributes(). Вызов функции getElementAttributes() позволяет добавлять, удалять и изменять атрибуты тега. После этого тег с измененными атрибутами записывается в результирующий поток. Этот процесс иллюстрируется в Листинге 11.
Листинг 11. Обработка обычных открывающих тегов в методе startElement()
} else {
if (record) {
...
} else {
String id = atts.getValue (ID_ATTRIBUTE);
if (id != null)
atts = handler.getElementAttributes (id, localName, atts);
out.print ("<" + localName);
for (int i = 0; i < atts.getLength (); i++) {
out.print (" ");
out.print (atts.getLocalName (i));
out.print ("=\"");
out.print (atts.getValue (i));
out.print ("\"");
} // for
out.print (">");
} // if
} ...
|
Соответствующий закрывающий тег формируется в методе endElement() командой print(), как видно из Листинга 12.
Листинг 12. Обработка обычных закрывающих тегов в методе endElement()
} else {
if (record) {
...
} else {
out.print ("</" + localName + ">");
} // if
} ...
|
Класс Hamlet
Абстрактный класс Hamlet, представленный в Листинге 13, представляет собой расширение HttpServlet, реализующее интерфейс Hamlet ContentHandler:
Листинг 13. Реализация интерфейса ContentHandler в классе Hamlet
public abstract class Hamlet extends HttpServlet implements ContentHandler {
...
private String includePath;
public int getElementRepeatCount (String id, String name, Attributes atts)
throws Exception {
return 0;
} // getElementRepeatCount
public String getElementReplacement (String id, String name, Attributes atts)
throws Exception {
return "";
} // getElementReplacement
public Attributes getElementAttributes (String id, String name, Attributes atts)
throws Exception {
return atts;
} // getElementAttributes
public InputStream getElementIncludeSource (String id, String name, Attributes atts)
throws Exception {
URL url = new URL (getIncludeURL (atts.getValue ("SRC")));
return url.openStream ();
} // getElementIncludeSource
public String getDocumentType () {
return "text/html";
} // getDocumentType
public String getIncludeURL (String fileName) {
return includePath + fileName;
} // getIncludeURL
public TemplateEngine getTemplateEngine () {
return new DefaultEngine ();
} // getTemplateEngine
private void serveDoc (PrintWriter out, String template, ContentHandler handler)
throws Exception {
TemplateEngine engine = getTemplateEngine ();
InputStream in = getServletContext().getResourceAsStream (template);
category.debug ("Parsing '" + template + "' ...");
long t1 = System.currentTimeMillis ();
engine.perform (in, handler, out);
long t2 = System.currentTimeMillis ();
category.debug ("Parsed '" + template + "' in " + (t2 - t1) + " ms.");
} // serveDoc
public void serveDoc (HttpServletRequest req, HttpServletResponse res,
String template, ContentHandler handler) throws Exception {
includePath = "http://localhost:" + req.getServerPort () + "/" +
req.getContextPath () + "/";
PrintWriter out = res.getWriter ();
res.setContentType (getDocumentType ());
serveDoc (out, template, handler);
} // serveDoc
public void serveDoc (HttpServletRequest req, HttpServletResponse res,
String template) throws Exception {
serveDoc (req, res, template, this);
} // serveDoc
} // Hamlet
|
В классе Hamlet представлены три метода serveDoc(). Наиболее интересным является private-метод serveDoc(PrintWriter out, String template, ContentHandler handler). Он вызывает getTemplateEngine() для получения механизма шаблонов по умолчанию, получает шаблон, вызывая getResourceAsStream(), а также вызывает метод perform() механизма шаблонов. Два других метода serveDoc() публичные и вызывают serveDoc(PrintWriter out, String template, ContentHandler handler) прямо или косвенно.
Кроме того, в абстрактном классе Hamlet представлен интерфейс Hamlet ContentHandler, в котором реализованы вызываемые по умолчанию методы getElementReplacement(), getElementRepeatCount(), getElementAttributes() и getElementIncludeSource(). Создав класс, наследующий классу Hamlet, можно создать специальную реализацию функций обратного вызова для заполнения конкретного шаблона.
Класс Logout1, представленный в Листинге 14, является расширением Hamlet. В нем переопределяется метод getElementReplacement() для заполнения шаблона LogoutTemplate.html идентификатором пользователя.
Листинг 14. Класс Logout1 предоставляет динамическое содержимое для LogoutTemplate.html
public class Logout1 extends Hamlet {
// log4j
private static Category category = Category.getInstance (Logout1.class.getName ());
private String userID = null;
public void init () throws ServletException {
// get properties
ContextProperties props = ContextProperties.getProperties (this);
// configure logging
Utilities.configLog (props);
category.debug ("init");
} // init
public synchronized void doGet (HttpServletRequest req, HttpServletResponse res)
throws ServletException {
try {
category.debug ("doGet");
HttpSession session = req.getSession (false);
if (session != null) {
WebApp webApp = (WebApp) session.getAttribute (WebApp.ID);
userID = webApp.getUserID ();
// invalidate session
session.invalidate ();
// serve document
serveDoc (req, res, "LogoutTemplate.html");
} else {
// redirect
res.sendRedirect (res.encodeURL (req.getContextPath () +
"/StaticPage.html?Page=LoginHere.html"));
} // if
} catch (Exception e) {
category.debug ("", e);
throw new ServletException (e);
} // try
} // doGet
public String getElementReplacement (String id, String name, Attributes atts)
throws Exception {
if (id.equals ("UserID")) {
return (userID == null) ? "" : userID;
} // if
return "?";
} // getElementReplacement
} // Logout1
|
Функция обратного вызова getElementReplacement() вызывается во время обработки LogoutTemplate.html механизмом шаблонов. Собственно обработка инициируется методом serveDoc(req, res, "LogoutTemplate.html"). Класс Logout1 наследует метод serveDoc() из класса Hamlet. Контент (идентификатор пользователя), который метод getElementReplacement() предоставляет механизму шаблонов, извлекается методом doGet() и сохраняется в переменной userID экземпляра Hamlet.
По умолчанию контейнер сервлета предоставляет только один экземпляр сервлета/Hamlet, и, таким образом, переменная userID совместно используется несколькими потоками, вызывающими метод doGet() для нескольких запросов. Следовательно, для обеспечения безошибочной работы методы doGet() необходимо синхронизировать. В качестве альтернативного варианта Hamlet Logout1 может создать пустой интерфейс SingleThreadModel, как показано в Листинге 15. В этом случае контейнер сервлета будет гарантировать, что одновременно с методом doGet() не будет выполняться никаких других потоков. Любой сервлет/Hamlet, создающий интерфейс SingleThreadModel, может быть рассмотрен как безопасный поток, и необходимости в синхронизации переменных экземпляра (в данном случае, userID) нет. Другими словами, в этом случае синхронизацию метода doGet() выполнять не нужно.
Листинг 15. Класс Logout1, создающий безопасный интерфейс SingleThreadModel
public class Logout1 extends Hamlet implements SingleThreadModel {
// log4j
private static Category category = Category.getInstance (Logout1.class.getName ());
private String userID = null;
public void init () throws ServletException {
// get properties
ContextProperties props = ContextProperties.getProperties (this);
// configure logging
Utilities.configLog (props);
category.debug ("init");
} // init
public void doGet (HttpServletRequest req, HttpServletResponse res)
throws ServletException {
try {
category.debug ("doGet");
HttpSession session = req.getSession (false);
if (session != null) {
WebApp webApp = (WebApp) session.getAttribute (WebApp.ID);
userID = webApp.getUserID ();
// invalidate session
session.invalidate ();
// serve document
serveDoc (req, res, "LogoutTemplate.html");
} else {
// redirect
res.sendRedirect (res.encodeURL (req.getContextPath () +
"/StaticPage.html?Page=LoginHere.html"));
} // if
} catch (Exception e) {
category.debug ("", e);
throw new ServletException (e);
} // try
} // doGet
public String getElementReplacement (String id, String name, Attributes atts)
throws Exception {
if (id.equals ("UserID")) {
return (userID == null) ? "" : userID;
} // if
return "?";
} // getElementReplacement
} // Logout1
|
Синхронизация doGet(), показанная в Листинге 14, отключает одновременное выполнение нескольких запросов. В большинстве случаев, если данные для заполнения шаблона можно получить достаточно быстро, это не является проблемой. Если же извлечение данных занимает слишком много времени, можно выполнять частичную синхронизацию метода doGet() (как в Листинге 16). Обычно парсинг шаблона (вызов serveDoc()) выполняется очень быстро.
Листинг 16. Более тонкая синхронизация doGet() допускает одновременное выполнение
public void doGet (HttpServletRequest req, HttpServletResponse res)
throws ServletException {
try {
category.debug ("doGet");
HttpSession session = req.getSession (false);
if (session != null) {
WebApp webApp = (WebApp) session.getAttribute (WebApp.ID);
// obtain user ID, takes some time
String aUserID = webApp.getUserID ();
// invalidate session
session.invalidate ();
// serve document
synchronized (this) {
userID = aUserID;
serveDoc (req, res, "LogoutTemplate.html");
} // synchronized
} else {
// redirect
res.sendRedirect (res.encodeURL (req.getContextPath () +
"/StaticPage.html?Page=LoginHere.html"));
} // if
} catch (Exception e) {
category.debug ("", e);
throw new ServletException (e);
} // try
} // doGet
|
Описание HamletHandler
Использование переменных экземпляра Hamlet для временного хранения и синхронизации метода doGet() или создания интерфейса SingleThreadModel – это очень простой способ снижения сложности при написании простых Web-приложений. В Hamlets v1.1 реализован альтернативный способ написания Hamlet, приведенный в Листинге 17.
Листинг 17. Класс Logout, использующий расширение HamletHandler для предоставления динамического контента
public class Logout extends Hamlet {
// log4j
private static Category category = Category.getInstance (Logout.class.getName ());
private class LogoutHandler extends HamletHandler {
private String userID = null;
public LogoutHandler (Hamlet hamlet, String userID) {
super (hamlet);
this.userID = userID;
} // LogoutHandler
public String getElementReplacement (String id, String name, Attributes atts)
throws Exception {
if (id.equals ("UserID")) {
return (userID == null) ? "" : userID;
} // if
return "?";
} // getElementReplacement
} // LogoutHandler
public void init () throws ServletException {
// get properties
ContextProperties props = ContextProperties.getProperties (this);
// configure logging
Utilities.configLog (props);
category.debug ("init");
} // init
public void doGet (HttpServletRequest req, HttpServletResponse res)
throws ServletException {
try {
category.debug ("doGet");
HttpSession session = req.getSession (false);
if (session != null) {
WebApp webApp = (WebApp) session.getAttribute (WebApp.ID);
String userID = webApp.getUserID ();
// invalidate session
session.invalidate ();
// serve document
HamletHandler handler = new LogoutHandler (this, userID);
serveDoc (req, res, "LogoutTemplate.html", handler);
} else {
// redirect
res.sendRedirect (res.encodeURL (req.getContextPath () +
"/StaticPage.html?Page=LoginHere.html"));
} // if
} catch (Exception e) {
category.debug ("", e);
throw new ServletException (e);
} // try
} // doGet
} // Logout
|
Класс Logout, как и класс Logout1, расширяет класс Hamlet. Однако в нем также содержится private-класс LogoutHandler, расширяющий HamletHandler. В классе HamletHandler, представленном в Листинге 18, реализован интерфейс Hamlet ContentHandler, а также все четыре функции обратного вызова по умолчанию, как и у класса Hamlet.
Листинг 18. В классе HamletHandler представлена стандартная реализация интерфейса ContentHandler
public class HamletHandler implements ContentHandler {
private Hamlet hamlet;
public HamletHandler (Hamlet hamlet) {
this.hamlet = hamlet;
} // HamletHandler
public int getElementRepeatCount (String id, String name, Attributes atts)
throws Exception {
return 0;
} // getElementRepeatCount
public String getElementReplacement (String id, String name, Attributes atts)
throws Exception {
return "";
} // getElementReplacement
public Attributes getElementAttributes (String id, String name, Attributes atts)
throws Exception {
return atts;
} // getElementAttributes
public InputStream getElementIncludeSource (String id, String name, Attributes atts)
throws Exception {
URL url = new URL (hamlet.getIncludeURL (atts.getValue ("SRC")));
return url.openStream ();
} // getElementIncludeSource
} // HamletHandler
|
Метод doGet() класса Logout создает экземпляр LogoutHandler с названием handler и передает ему метод serveDoc(). Когда будет необходимо передать данные в шаблон LogoutTemplate.html, механизм шаблонов вызовет LogoutHandler. Код LogoutHandler возвращает механизму шаблонов идентификатор пользователя. Идентификатор пользователя, полученный в методе doGet(), не сохраняется в переменной экземпляра класса Hamlet. Вместо этого конструктор сохраняет его в переменной экземпляра userID класса LogoutHandler. В случае, когда для каждого запроса создается новый LogoutHandler, запросы используют разные переменные экземпляра класса Hamlet. Хотя private-класс LogoutHandler повышает сложность, он исключает проблемы синхронизации и позволяет одновременно выполнять несколько запросов.
Расширение Hamlets
Среда Hamlets разработана с учетом возможности ее расширения. Вы можете написать собственный механизм шаблонов, реализовав интерфейс TemplateEngine. Например, вам может потребоваться создание механизма, который будет поддерживать пространства имен и другие требования. В Листинге 19 приведен пример такой реализации.
Листинг 19. В классе XMLEngine представлена еще одна реализация интерфейса TemplateEngine
public class XMLEngine implements TemplateEngine {
...
public void perform (InputStream in, ContentHandler handler, PrintWriter out)
throws Exception {
...
} // perform
...
} // XMLEngine
|
Необходимо указать Hamlet, чтобы он использовал ваш механизм шаблонов, как показано в Листинге 20.
Листинг 20. В XMLHamlet используется собственный механизм шаблонов
public class XMLHamlet extends Hamlet {
...
private class XMLHamletHandler extends HamletHandler {
public XMLHamletHandler (Hamlet hamlet) {
super (hamlet);
} // XMLHamletHandler
} // XMLHamletHandler
public String getDocumentType () {
return "text/xml";
} // getDocumentType
public TemplateEngine getTemplateEngine () {
return new XMLTemplateEngine ();
} // getTemplateEngine
public void doGet (HttpServletRequest req, HttpServletResponse res)
throws ServletException {
try {
// serve document
HamletHandler handler = new XMLHamletHandler (this);
serveDoc (req, res, "XMLTemplate.html", handler);
} catch (Exception e) {
throw new ServletException (e);
} // try
} // doGet
} // XMLHamlet
|
Резюме
В отличие от реализации, приведенной в вышедшем ранее руководстве по Программированию Hamlets (EN) (версия 1.0, в которой используется два публичных класса Java общим объемом менее 500 строк кода), более развитая редакция (версия 1.1, содержащая два интерфейса Java, четыре класса и 695 строк кода) отделяет механизм шаблонов от класса Hamlet. В ней реализованы функции обратного вызова в интерфейсе ContentHandler. Кроме того, в новой реализации предлагается дополнительный способ передачи динамического контента механизму шаблонов. Отдельный экземпляр класса HamletHandler для каждого запроса позволяет более не синхронизировать методы doGet().
Благодарности
Я хочу поблагодарить Йенн Дюпоншель (Yann Duponchel) за ее советы и вклад в развитие Hamlets.
Загрузка | Описание | Имя | Размер | Метод загрузки |
|---|
| Полный пример кода для этой статьи | wa-hamlets3-code.zip | 6KB | HTTP |
|---|
Ресурсы Научиться
Получить продукты и технологии
Обсудить
Об авторе  | |  | Рене Павлитцек (René Pawlitzek) – гражданин Лихтенштейна, имеет степень инженера в области информатики, полученную в Швейцарском Федеральном Технологическом Институте (ETH Zurich). Рене работает исследователем и разработчиком решений по управлению информацией для обеспечения безопасности в группе Advanced Operating Environment (ранее - Global Security Analysis Lab (GSAL)) исследовательской лаборатории IBM в Цюрихе (Швейцария). До перехода в IBM он работал в Калифорнии в компаниях Hewlett-Packard, WindRiver Systems и Borland International. |
Выскажите мнение об этой странице
|  |