IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  Web development | Java technology | XML  >

Hamlet を実装する

この Web 開発フレームワークをさらに洗練したものにする

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

サンプルコード

原文はこちら

原文はこちら


レベル: 中級

René Pawlitzek (rpa@zurich.ibm.com), Research and Development Engineer, IBM 

2007年 2月 07日
2007年 7月 03日 更新

Hamlet フレームワークは、Java™ サーブレットを拡張してコンテンツと表示を強制的に分離するために開発されました。この記事では、著者の René Pawlitzek が、このフレームワークをさらに進化させ、テンプレート・エンジンの洗練した使い方をすることで、動的コンテンツを提供するための、新しい方法を紹介します。

Hamlet は、Web ベースのアプリケーションを開発するための、使いやすく理解しやすいフレームワークを提供します。このフレームワークは、コンテンツと表示の完全な分離をサポートするだけではなく、強制的に両者の分離を行います。しかも単純でスマートに設計されているため、おなじみの、基礎となるサーブレットのインフラが隠されることはありません。

このフレームワークは、Hamlet と呼ばれるサーブレット拡張機能を提供します。Hamlet は、表示を含むテンプレート・ファイルを SAX (Simple API for XML) を使用して読み取ります。(これらのテンプレート・ファイルは、厳密な HTML や XHTML、あるいは XML など、SAX で構文解析可能なコンテンツを含んでいる必要があります。) テンプレート・ファイルが読み取られる間、Hamlet は特別なタグや ID でマーキングされたテンプレート内の場所に、いくつかのコールバック関数からなるセットを使って動的にコンテンツを追加します。

この記事で使用するプロジェクト名 Hamlet は、Web ベースのアプリケーションでのコンテンツと表示を分離するための、サーブレット・ベースのフレームワーク開発の内部プロジェクトを指します。この記事を先に読み進める前に、私が以前 IBM developerWorks に書いた記事、「Introducing Hamlets」と、できれば私が developerWorks に書いたチュートリアル「Programming Hamlets」も読むとよいでしょう。(どちらも「参考文献」にリンクがあります。)

この記事は、Hamlet を実装するためのさまざまな側面について説明します。基本的な Hamlet の実装 (バージョン 1.0) は、2 つのパブリック Java クラスのみを使った 500 行に満たない Java コードで作られており、この実装についてはチュートリアル「Programming Hamlets」に説明してあります。今回の記事に含まれるコード (バージョン 1.1) は、2 つの Java インターフェースと 4 つのパブリック Java クラス、そして合計 695 行の Java コードを含んでいます。この実装はバージョン 1.0 よりも進化しており、より適切に構成されています (図 1 の UML 図を見てください)。この記事では、この実装について詳細に説明し、さらに HamletHandler を使って Hamlet をプログラムする新しい方法について学びます。この記事の完全なサンプルコードを含んだアーカイブ wa-hamlets3-code.zip は、「ダウンロード」セクションから入手することができます。


図 1. Hamlets 1.1 の UML 図

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 リーダーのプールから 1 つの SAX リーダーを取得し、入力パラメーター in InputSource でラップし、SAX エラーとコンテンツ・ハンドラーを設定し、そして入力ソース (テンプレート) の構文解析を開始します。構文解析が終了すると、その 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() メソッドは、テンプレートが実際に構文解析される前に呼び出されます。このメソッドは、記録用に使われる 2 つのインスタンス変数 (root と curNode) を初期化します。構文解析が終了すると、endDocument() メソッドが両方のインスタンス変数を null に設定します。

SAX パーサーは、開始タグと終了タグを認識するごとに startElement() メソッドと endElement() メソッドを呼び出します。SAX パーサーが次のいずれかのタグを見つけると、特別なアクションが行われます。

  • <REPEAT>
  • </REPEAT>
  • <REPLACE>
  • </REPLACE>
  • <INCLUDE>
  • </INCLUDE>

これらのタグはテンプレート・ファイルの中に埋め込まれ、構文解析の際に動的コンテンツを追加する場所をマーキングします。

SAX リーダーが <REPEAT> タグを見つけると、startElement() メソッドは record フラグを true に設定します (リスト 5 )。従ってテンプレート・ファイルの <REPEAT> タグの後のすべてのコンテンツは、ツリー構造と 4 つの内部クラス (TreeNode StartElement EndElement CharElement ) を利用して記録されます。ツリー構造が必要な理由は、<REPEAT> タグはネストが可能なためです。


リスト 5. startElement() での <REPEAT> タグの処理によってコンテンツの記録が開始される
                
                
      if (REPEAT_TAG.equals (localName)) {
        record = true;
        ...
      }

endElement() メソッドは、対応する </REPEAT> タグを見つけると record フラグを false に設定し、記録を停止します (リスト 6 )。


リスト 6. endElement() での </REPEAT> タグ処理によってコンテンツの記録が停止され、コンテンツの再生が呼び出される
                
                
      if (REPEAT_TAG.equals (localName)) {
        ...
        record = false;
        playback (root);
        ...
      } 

endElement() メソッドは playback() 関数も呼び出します (リスト 7)。playback() 関数は ContentHandlergetElementRepeatCount() コールバックを呼び出し、繰り返し回数 N を取得します。(getElementRepeatCount() メソッドは再生中にも繰り返し呼び出され、0 を返すことで終了します。) 次に、記録されたコンテンツ (要素のツリー) が N 回再生されます。記録された要素を再生するには、startElement() メソッドあるいは endElement() メソッド、あるいは characters() メソッドを呼び出します。これらのメソッドは記録されたコンテンツを、記録されていないコンテンツとまったく同じように扱います。<REPEAT> タグはネストが可能なため、playback() は再帰的であることに注意してください。


リスト 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> タグを見つけ、記録がアクティブでなくなると、startElement() メソッドは、もし ID 属性が存在すれば ContentHandler getElementReplacement() メソッドを呼び出します。getElementReplacement() コールバックは、print() メソッドによる出力に含まれる、動的に作成されたコンテンツを返します。ignore フラグは、テンプレート・ファイルのプレースホルダー値 (<REPLACE> タグと </REPLACE> タグの間のコンテンツ) が無視され、characters() メソッドによる出力に含まれないように、事前に true に設定されています。


リスト 8. startElement() での <REPLACE> タグの処理
                
                
      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() メソッドを呼び出し、その時に出力に含まれているコンテンツを取得します。この前に、もし <INCLUDE> タグが ID 属性を含んでいる場合は Hamlet から属性を変更できるように、getElementAttributes() コールバックが呼び出されます。さらに、テンプレート・ファイルのプレースホルダー値 (<INCLUDE> タグと </INCLUDE> タグの間のコンテンツ) を無視するために、ignore フラグが true に設定されます。


リスト 9. startElement() での <INCLUDE> タグの処理
                
                
      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> タグを見つけると、ignore フラグは endElement() メソッドの中で false に設定されます (リスト 10 )。


リスト 10. endElement() での </REPLACE> タグと </INCLUDE> タグの処理
                
                
      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. Hamlet クラスが ContentHandler インターフェースを実装する
                
                
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 クラスは 3 つの serveDoc() メソッドを実装しています。serveDoc(PrintWriter out, String template, ContentHandler handler) はプライベート・メソッドであり、3 つのメソッドの中で最も興味深いものです。このメソッドは getTemplateEngine() を呼び出して、デフォルトのテンプレート・エンジンを取得し、getResourceAsStream() を呼び出すことでテンプレートを取得し、そしてテンプレート・エンジンの perform() メソッドを呼び出します。他の 2 つの serveDoc() メソッドはパブリック・メソッドであり、直接的に、あるいは間接的に serveDoc(PrintWriter out, String template, ContentHandler handler) を呼び出します。

さらに、抽象クラス Hamlet は Hamlet の ContentHandler インターフェースを実装し、コールバック・メソッド (getElementReplacement() getElementRepeatCount()、getElementAttributes() 、そして getElementIncludeSource() ) のデフォルト実装を提供します。Hamlet クラスからクラスを派生することで、これらのコールバック関数の具体的な実装を提供し、ある特定のテンプレートに追加することができます。

リスト 14 Logout1 クラスは Hamlet の拡張です。このクラスは getElementReplacement() メソッドを上書きし、テンプレート LogoutTemplate.html にユーザー ID を入力します。


リスト 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 Hamlet クラスから serveDoc() を継承します。getElementReplacement() がテンプレート・エンジンに提供するコンテンツ (ユーザー ID) は doGet() メソッド内で取得され、Hamlet の userID インスタンス変数の中に保存されます。

サーブレット・コンテナーはデフォルトでサーブレット/Hamlet のインスタンスを 1 つしか提供しないため、複数のリクエストに対して doGet() メソッドを実行する複数のスレッド間でインスタンス変数 userID が共有されます。従って、適切な実行を保証するためには doGet() は同期している必要があります。あるいは、Logout1 Hamlet が空の SingleThreadModel インターフェースを実装する方法もあります (リスト 15 )。この場合は、doGet() メソッドの中で 2 つのスレッドが並列に実行されないよう、サーブレット・コンテナーが保証します。SingleThreadModel インターフェースを実装するすべてのサーブレット/Hamlet はスレッド・セーフと見なすことができ、そのインスタンス変数 (この場合は 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 を導入する

一時的な保存や doGet() メソッドの同期のため、あるいは SingleThreadModel インターフェースを実装するために、Hamlet インスタンス変数を使う方法は、単純な 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 を拡張しますが、HamletHandler を拡張する LogoutHandler というプライベート・クラスも含みます。HamletHandler (リスト 18 ) は、Hamlet ContentHandler インターフェースを実装し、そして 4 つのコールバック関数すべてに対するデフォルト実装 (例えば 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 

Logout クラスの doGet() メソッドは、(handler という名前の) LogoutHandler のインスタンスを作成し、それを serveDoc() メソッドに渡します。テンプレート・エンジンは、LogoutTemplate.html テンプレートに動的コンテンツを追加しなければならない時に LogoutHandler を呼び出します。LogoutHandler コードはテンプレート・エンジンにユーザー ID を返します。このユーザー ID は doGet() メソッドの中で取得されますが、Hamlet クラスのインスタンス変数の中には保存されず、コンストラクターによって LogoutHandler クラスの userID インスタンス変数の中に保存されます。リクエストごとに LogoutHandler が作成されるため、複数のリクエストが Hamlet クラスの 1 つのインスタンス変数を共有する必要はありません。プライベート・クラス LogoutHandler によって複雑さが増しますが、その一方で同期の問題を回避することができ、複数のリクエストを並列に実行できるようになります。




上に戻る


Hamlet を拡張する

Hamlet フレームワークは拡張性を考慮して設計されています。TemplateEngine インターフェースを実装することで、皆さんが独自のテンプレート・エンジンを作成することができます。例えば、名前空間や他の要件をサポートするエンジンを作成しなければならないことがあるかもしれません。リスト 19 は、そうした例を示しています。


リスト 19. XMLEngine クラスが別の TemplateEngine インターフェース実装を提供する
                
                
public class XMLEngine implements TemplateEngine {

  ...

  public void perform (InputStream in, ContentHandler handler, PrintWriter out)
    throws Exception {

    ...

  } // perform

  ...

} // XMLEngine

またリスト 20 に示すように、カスタムのテンプレート・エンジンを使うように Hamlet に指示することもできます。


リスト 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




上に戻る


まとめ

以前に「Programming Hamlets」チュートリアルで示した実装 (2 つのパブリック Java クラスのみを使った 500 行に満たないコードで作成された、バージョン 1.0) と比較して、新しい実装 (2 つの Java インターフェースと 4 つのパブリック Java クラス、そして 695 行の Java コードを含む、バージョン 1.1) はさらに進化しており、テンプレート・エンジンを Hamlet クラスから分離しています。コールバック関数は、今度は ContentHandler インターフェースによって定義されます。さらに新しい実装では、テンプレート・エンジンに動的コンテンツを提供するための別の方法が用意されています。また、リクエストごとに HamletHandler のインスタンスが別であるため、doGet() メソッドを同期する必要がなくなりました。




上に戻る


謝辞

Hamlet の高度な実装に助言や貢献をしてくださった Yann Duponchel 氏に感謝いたします。





上に戻る


ダウンロード

内容ファイル名サイズダウンロード形式
Complete code sample for this articlewa-hamlets3-code.zip6KBHTTP
ダウンロード形式について


参考文献

学ぶために
  • 記事「Introducing Hamlets」(developerWorks、2005年3月) は Hamlet プログラミングの基本を紹介し、ごくわずかなコードによってコンテンツと表示とを分離するための方法を解説しています。

  • チュートリアル「Programming Hamlets」(developerWorks、2005年5月) は実際的な Hamlet の例をいくつか示しながら、Hamlet v1.0 プログラミングのさまざまな側面を解説しています。

  • The Java Servlet API White Paper で Java サーブレットの概要を学んでください。

  • developerWorks の Web Architecture ゾーンで Web 作成のスキルを磨いてください。


製品や技術を入手するために
  • 広く使われている API である、SAX, the Simple API for XML を調べてみてください。

  • Enhydra XMLC Project を訪れてください。このプロジェクトでは、動的表示の実際のオブジェクト・ビューでマークアップとロジックを厳密に分離する表示技術が開発されています。

  • SourceForge で Hamlet を入手してください。今や Hamlet プロジェクトはオープン・ソースとなったので、皆さんがどう貢献できるかを考えてください。

  • Rational® development tools を今すぐにダウンロードしてください。


議論するために


著者について

Photo of Rene Pawlitzek

Rene Pawlitzek は、リヒテンシュタインの市民であり、Swiss Federal Institute of Technology (ETH Zurich) のコンピューター・サイエンスの工学学位を取得しています。スイスにある IBM Zurich Research Laboratory の Advanced Operating Environment グループ (旧 GSAL (Global Security Analysis Lab)) で、セキュリティー情報管理ソリューションにフォーカスした研究・開発のエンジニアとして働いています。IBM に入社する前は、カリフォルニアの Hewlett-Packard、WindRiver Systems、および Borland International で働いていました。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


    日本IBMについて プライバシー お問い合わせ