IBM®
Перейти к тексту
    в России и странах СНГ [изменить]    Условия использования
 
 
   
    Главная страница    Продукты    Услуги и решения    Поддержка и загрузка    Мой профиль    
Перейти к тексту

developerWorks Россия  >  Технология Java | XML  >

Реализация Hamlets

Развиваем возможности интегрированной среды Web-разработки

developerWorks
Опции документа

Опции документа, требующие включения JavaScript, не отображаются

Обсудить

Исходные тексты примера


Выскажите мнение об этой странице

Помогите нам улучшить содержание


Уровень сложности: средний

Рене Павлитцек, инженер-исследователь, 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
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.zip6KBHTTP
Информация о методах загрузки


Ресурсы

Научиться

Получить продукты и технологии

Обсудить


Об авторе

Рене Павлитцек (René Pawlitzek) – гражданин Лихтенштейна, имеет степень инженера в области информатики, полученную в Швейцарском Федеральном Технологическом Институте (ETH Zurich). Рене работает исследователем и разработчиком решений по управлению информацией для обеспечения безопасности в группе Advanced Operating Environment (ранее - Global Security Analysis Lab (GSAL)) исследовательской лаборатории IBM в Цюрихе (Швейцария). До перехода в IBM он работал в Калифорнии в компаниях Hewlett-Packard, WindRiver Systems и Borland International.




Выскажите мнение об этой странице


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



ДаНетНе знаю
 


 


12345
 


В начало


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

    IBM в России Конфиденциальность Контакты