今日のアプリケーションは、サイズが大きくて実行速度が遅く、あまりに複雑になることがよくあります。リスト 1 のスタック・トレースは、筆者が JSF (JavaServer Faces) を使って初めて開発したときのもので、最近のコードがいかに複雑になっているかを示しています。
リスト 1. JSF スタック・トレース
java.lang.Exception: Authorization server name not found
at com.ibm.zurich.gsal.billyboard.libs.IntranetAuthProcessor.configure(Unknown Source)
at com.ibm.zurich.gsal.billyboard.apps.board.LoginController.<init>(Unknown Source)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance
(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance
(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:274)
at java.lang.Class.newInstance0(Class.java:308)
at java.lang.Class.newInstance(Class.java:261)
at java.beans.Beans.instantiate(Beans.java:204)
at java.beans.Beans.instantiate(Beans.java:48)
at com.sun.faces.config.ManagedBeanFactory.newInstance(ManagedBeanFactory.java:203)
at com.sun.faces.application.ApplicationAssociate.createAndMaybeStoreManagedBeans
(ApplicationAssociate.java:256)
at com.sun.faces.el.VariableResolverImpl.resolveVariable(VariableResolverImpl.java:78)
at com.sun.faces.el.impl.NamedValue.evaluate(NamedValue.java:125)
at com.sun.faces.el.impl.ComplexValue.evaluate(ComplexValue.java:146)
at com.sun.faces.el.impl.ExpressionEvaluatorImpl.evaluate
(ExpressionEvaluatorImpl.java:243)
at com.sun.faces.el.ValueBindingImpl.getValue(ValueBindingImpl.java:173)
at com.sun.faces.el.ValueBindingImpl.getValue(ValueBindingImpl.java:154)
at javax.faces.component.UIOutput.getValue(UIOutput.java:147)
at com.sun.faces.renderkit.html_basic.HtmlBasicInputRenderer.getValue
(HtmlBasicInputRenderer.java:82)
at com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.getCurrentValue
(HtmlBasicRenderer.java:191)
at com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeEnd
(HtmlBasicRenderer.java:169)
at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:720)
at com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeRecursive
(HtmlBasicRenderer.java:443)
at com.sun.faces.renderkit.html_basic.GridRenderer.encodeChildren(GridRenderer.java:233)
at javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:701)
at javax.faces.webapp.UIComponentTag.encodeChildren(UIComponentTag.java:607)
at javax.faces.webapp.UIComponentTag.doEndTag(UIComponentTag.java:544)
at com.sun.faces.taglib.html_basic.PanelGridTag.doEndTag(PanelGridTag.java:460)
at org.apache.jsp.login_jsp._jspx_meth_h_panelGrid_0(login_jsp.java:200)
at org.apache.jsp.login_jsp._jspx_meth_h_form_0(login_jsp.java:150)
at org.apache.jsp.login_jsp._jspx_meth_f_view_0(login_jsp.java:120)
at org.apache.jsp.login_jsp._jspService(login_jsp.java:85)
at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:94)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:324)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:292)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:236)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter
(ApplicationFilterChain.java:237)
at org.apache.catalina.core.ApplicationFilterChain.doFilter
(ApplicationFilterChain.java:157)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:704)
at org.apache.catalina.core.ApplicationDispatcher.processRequest
(ApplicationDispatcher.java:474)
at org.apache.catalina.core.ApplicationDispatcher.doForward
(ApplicationDispatcher.java:409)
at org.apache.catalina.core.ApplicationDispatcher.forward
(ApplicationDispatcher.java:312)
at com.sun.faces.context.ExternalContextImpl.dispatch(ExternalContextImpl.java:322)
at com.sun.faces.application.ViewHandlerImpl.renderView(ViewHandlerImpl.java:130)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:87)
at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:200)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:117)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:198)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter
(ApplicationFilterChain.java:237)
at org.apache.catalina.core.ApplicationFilterChain.doFilter
(ApplicationFilterChain.java:157)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:214)
at org.apache.catalina.core.StandardValveContext.invokeNext
(StandardValveContext.java:104)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
at org.apache.catalina.core.StandardContextValve.invokeInternal
(StandardContextValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:152)
at org.apache.catalina.core.StandardValveContext.invokeNext
(StandardValveContext.java:104)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:137)
at org.apache.catalina.core.StandardValveContext.invokeNext
(StandardValveContext.java:104)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:118)
at org.apache.catalina.core.StandardValveContext.invokeNext
(StandardValveContext.java:102)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.core.StandardValveContext.invokeNext
(StandardValveContext.java:104)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:929)
at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:160)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:799)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.processConnection
(Http11Protocol.java:705)
at org.apache.tomcat.util.net.TcpWorkerThread.runIt(PoolTcpEndpoint.java:577)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:683)
at java.lang.Thread.run(Thread.java:534)
|
「Introducing Hamlets」で始まるこのシリーズの以前の記事で、Web ベース・アプリケーション開発用の Hamlet と呼ばれる、使いやすくて理解しやすいフレームワークを提案しました。このフレームワークは、徹底してソフトウェアを単純にした成果が反映されています。 (これらの古い記事をまだお読みでなければ、「参考文献」にリンクがあります。)
Hamlet は、テンプレート・ファイルを読めるように、SAX (Simple API for XML) を使って Java サーブレットを拡張したものです。テンプレート・ファイルが読み込まれている一方で、Hamlet は、小さいコールバック関数のセット (HamletHandler によって実装されたもの) を使って、テンプレート内で特別ななタグと ID でマークされた場所に動的コンテンツを追加します。図 1 は、そのプロセスを示したものです。
図 1. Hamlet はテンプレート・ファイルからコンテンツを読み取るために SAX を使用し、動的コンテンツを追加するために HamletHandler を呼び出します
この資料で使われているプロジェクト名 Hamlet は、Web ベース・アプリケーションにおいてコンテンツとプレゼンテーションを分離するためのサーブレット・ベースのフレームワークを開発する内部プロジェクトのことを指しています。この記事の最後の方にある「ダウンロード」セクションから、この記事のすべてのサンプル・コードを含むアーカイブ・ファイル hamlet-compiler.zip をダウンロードできます。このコードは、IBM alphaWorks サイトで Hamlet v1.2 としてもリリースされています (リンクについては、「参考文献」を参照してください)。
この記事では、Hamlet フレームワークへの小さな追加機能、すなわち Hamlet を高速にするためのテンプレート・コンパイラーについて説明します。そのテンプレート・コンパイラーの実装には、600 行以下の Java ソース・コードが必要です。テンプレート・コンパイラーは、SAX を使ってテンプレート・ファイルを読み込み、コンテンツを Java ソース・コードに変換します。そして、標準の JDK Java コンパイラーを呼び出して Java バイトコードを生成します。実行時に Hamlet はこのコードを実行し、動的コンテンツを追加するために HamletHandler をコールします。図 2 では、そのプロセスを説明しています。
図 2. テンプレート・コンパイラーはテンプレート・ファイルのコンテンツを Java バイトコードに変換し、Hamlet はこのコードを実行して、動的コンテンツを追加するために HamletHandler をコールします
コンパイル済み Hamlet (つまり、コンパイル済みのテンプレートを使った Hamlet) により、たくさんのユーザーが同時に利用できる Web ベース・アプリケーションを実装できるようになります。またコンパイル済み Hamlet は、必要とするリソースが少ないため、組み込みデバイスにも同様に適しています。
テンプレート・コンパイラーは、2 つのステージで操作されます。最初は、テンプレート・ファイルの HTML コンテンツが読み込まれ、Java ソース・コードに変換されます。例えば、リスト 2 を見てください。HTML コンテンツが BasicTestTemplate.html. に組み込まれています。
リスト 2. 基本 HTML コード
<HTML>
<HEAD>
<TITLE>Hamlet Test Suite</TITLE>
</HEAD>
<BODY>
<H1>Hamlet Test Suite</H1>
<DIV>
The following test cases test the functionality of the <I>hamlet.jar</I> library.
It is in working condition if the output in each section is exactly repeated.
</DIV>
<H2>Replace Test</H2>
<DIV>Hello Mr. <REPLACE ID="Name">X</REPLACE>.</DIV>
<DIV>Hello Mr. Pawlitzek.</DIV>
<H2>Repeat Test</H2>
<REPEAT ID="Rows">
<DIV>
<REPEAT ID="Cols">
<REPLACE ID="Value">0</REPLACE>
</REPEAT>
</DIV>
</REPEAT>
<DIV>?</DIV>
<DIV>11 12 13</DIV>
<DIV>21 22 23</DIV>
<DIV>31 32 33</DIV>
<H2>Attribute Test</H2>
<DIV><A>www.pawlitzek.com</A></DIV>
<DIV><A ID="Ref" HREF="www.pawlitzek.com">www.pawlitzek.com</A></DIV>
<H2>Include Test</H2>
<DIV>c Copyright IBM Corp. 2006</DIV>
<DIV><INCLUDE SRC="BasicTestInclude.html" /></DIV>
<DIV><INCLUDE ID="Copyright" SRC="BasicTestInclude.html" /></DIV>
</BODY>
</HTML>
|
このコードは、リスト 3 の Java ソース・コード (BasicTestTemplate.java) に変換されます。
リスト 3. Java コードに変換されたリスト 2 の HTML
import java.io.*;
import com.ibm.hamlet.*;
import org.xml.sax.helpers.*;
public class BasicTestTemplate implements Template {
public void serveDoc (PrintWriter writer, ContentHandler handler) throws Exception {
writer.print (
"<HTML>\n" +
" <HEAD>\n" +
" <TITLE>Hamlet Test Suite</TITLE>\n" +
" </HEAD>\n" +
" <BODY>\n" +
"\n" +
" <H1>Hamlet Test Suite</H1>\n" +
" <DIV>\n" +
" The following test cases test the functionality" +
" of the <I>hamlet.jar</I> library.\n" +
" It is in working condition if the output in each section" +
" is exactly repeated.\n" +
" </DIV>\n" +
"\n" +
" <H2>Replace Test</H2>\n" +
" <DIV>Hello Mr. "
);
AttributesImpl atts0 = new AttributesImpl ();
atts0.addAttribute ("", "ID", "ID", "CDATA", "Name");
writer.print (handler.getElementReplacement ("Name", "REPLACE", atts0));
writer.print (
".</DIV>\n" +
" <DIV>Hello Mr. Pawlitzek.</DIV>\n" +
"\n" +
" <H2>Repeat Test</H2>\n" +
" "
);
AttributesImpl atts1 = new AttributesImpl ();
atts1.addAttribute ("", "ID", "ID", "CDATA", "Rows");
int count0 = handler.getElementRepeatCount ("Rows", "REPEAT", atts1);
for (int loop0 = 0; loop0 < count0; loop0++) {
writer.print (
"\n" +
" <DIV>\n" +
" "
);
AttributesImpl atts2 = new AttributesImpl ();
atts2.addAttribute ("", "ID", "ID", "CDATA", "Cols");
int count1 = handler.getElementRepeatCount ("Cols", "REPEAT", atts2);
for (int loop1 = 0; loop1 < count1; loop1++) {
writer.print (
"\n" +
" "
);
AttributesImpl atts3 = new AttributesImpl ();
atts3.addAttribute ("", "ID", "ID", "CDATA", "Value");
writer.print (handler.getElementReplacement ("Value", "REPLACE", atts3));
writer.print (
"\n" +
" "
);
if (handler.getElementRepeatCount ("Cols", "REPEAT", atts2) == 0)
break;
} // for
writer.print (
"\n" +
" </DIV>\n" +
" "
);
if (handler.getElementRepeatCount ("Rows", "REPEAT", atts1) == 0)
break;
} // for
writer.print (
"\n" +
" <DIV> </DIV>\n" +
" <DIV>11 12 13</DIV>\n" +
" <DIV>21 22 23</DIV>\n" +
" <DIV>31 32 33</DIV>\n" +
"\n" +
" <H2>Attribute Test</H2>\n" +
" <DIV><A>www.pawlitzek.com</A></DIV>\n" +
" <DIV>"
);
AttributesImpl atts4 = new AttributesImpl ();
atts4.addAttribute ("", "ID", "ID", "CDATA", "Ref");
atts4.addAttribute ("", "HREF", "HREF", "CDATA", "www.pawlitzek.com");
RuntimeUtilities.printTag
(writer, "A", handler.getElementAttributes ("Ref", "A", atts4));
writer.print (
"www.pawlitzek.com</A></DIV>\n" +
"\n" +
" <H2>Include Test</H2>\n" +
" <DIV>(c) Copyright IBM Corp. 2006</DIV>\n" +
" <DIV>"
);
AttributesImpl atts5 = new AttributesImpl ();
atts5.addAttribute ("", "SRC", "SRC", "CDATA", "BasicTestInclude.html");
RuntimeUtilities.printInclude
(writer, handler.getElementIncludeSource (null, "INCLUDE", atts5));
writer.print (
"</DIV>\n" +
" <DIV>"
);
AttributesImpl atts6 = new AttributesImpl ();
atts6.addAttribute ("", "ID", "ID", "CDATA", "Copyright");
atts6.addAttribute ("", "SRC", "SRC", "CDATA", "BasicTestInclude.html");
RuntimeUtilities.printInclude (writer,
handler.getElementIncludeSource ("Copyright", "INCLUDE",
handler.getElementAttributes ("Copyright", "INCLUDE", atts6)));
writer.print (
"</DIV>\n" +
"\n" +
" </BODY>\n" +
"</HTML>"
);
} // serveDoc
} // BasicTestTemplate
|
Java ソース・コードへの変換は直接行われ、以下の内容が実行されます。
- 静的コンテンツの箇所が集められ、各箇所ごとに writer.print() ステートメントが生成されます。例えば、リスト 4 のコードが、リスト 5 のコードに変換されます。
リスト 4. 静的コンテンツの箇所<HTML> <HEAD> <TITLE>Hamlet Test Suite</TITLE> </HEAD> <BODY> <H1>Hamlet Test Suite</H1> <DIV> The following test cases test the functionality of the <I>hamlet.jar</I> library. It is in working condition if the output in each section is exactly repeated. </DIV> <H2>Replace Test</H2> <DIV>Hello Mr.
リスト 5. Java コードに変換されたリスト 4 からの HTMLwriter.print ( "<HTML>\n" + " <HEAD>\n" + " <TITLE>Hamlet Test Suite</TITLE>\n" + " </HEAD>\n" + " <BODY>\n" + "\n" + " <H1>Hamlet Test Suite</H1>\n" + " <DIV>\n" + " The following test cases test the functionality" + " of the <I>hamlet.jar</I> library.\n" + " It is in working condition if the output in each section" + " is exactly repeated.\n" + " </DIV>\n" + "\n" + " <H2>Replace Test</H2>\n" + " <DIV>Hello Mr. " );
- 各 <REPLACE>...</REPLACE> 部ごとに、getElementReplacement() および writer.print() のコールが生成されます。実行時に、Hamlet は、<REPLACE> タグの属性を持つ getElementReplacement() をコールし、writer.print() による出力に組み込まれる動的コンテンツを受け取ります。例えば、リスト 6 のコードは、リスト 7 のコードに変換されます。
リスト 6. <REPLACE> 部<REPLACE ID="Name">X</REPLACE>
リスト 7. Java コードに変換されたリスト 6 の HTMLAttributesImpl atts0 = new AttributesImpl (); atts0.addAttribute ("", "ID", "ID", "CDATA", "Name"); writer.print ( handler.getElementReplacement ("Name", "REPLACE", atts0));
- 各 <REPEAT>...</REPEAT> 部ごとに、1 つの for ループ文と 2 つの getElementRepeatCount() コールが生成されます。実行時に、Hamlet は、<REPEAT> タグの属性を持つ getElementRepeatCount() をコールします (繰り返し回数を取得するためにループの実行前に 1 回コールし、0 を返してループを終了するまでループ内で繰り返しコールします)。例えば、リスト 8 のコードは、リスト 9 のコードに変換されます。
リスト 8. <REPEAT> 部<REPEAT ID="Cols"> ... </REPEAT>
リスト 9. Java コードに変換されたリスト 8 の HTMLAttributesImpl atts2 = new AttributesImpl (); atts2.addAttribute ("", "ID", "ID", "CDATA", "Cols"); int count1 = handler.getElementRepeatCount ("Cols", "REPEAT", atts2); for (int loop1 = 0; loop1 < count1; loop1++) { ... if (handler.getElementRepeatCount ("Cols", "REPEAT", atts2) == 0) break; } // for
- 各 <INCLUDE/> 部ごとに、getElementIncludeSource() および RuntimeUtilities.printInclude() のコールが生成されます。実行時に、Hamlet は、<INCLUDE> タグの属性を使って getElementIncludeSource() をコールし、InputStream を取得します。次に、RuntimeUtilities.printInclude() は、この InputStream を使って、<INCLUDE> タグの SRC 属性で指定されるリソースのコンテンツを組み込みます。例えば、リスト 10 のコードは、リスト 11 のコードに変換されます。
リスト 10. <INCLUDE/> 部<INCLUDE SRC="BasicTestInclude.html" />
リスト 11. Java コードに変換されたリスト 10 の HTMLAttributesImpl atts5 = new AttributesImpl (); atts5.addAttribute ("", "SRC", "SRC", "CDATA", "BasicTestInclude.html"); RuntimeUtilities.printInclude (writer, handler.getElementIncludeSource (null, "INCLUDE", atts5));
- 各 <INCLUDE ID="..."/> 部ごとに、getElementAttributes()、getElementIncludeSource()、および RuntimeUtilities.printInclude() のコールが生成されます。実行時に、Hamlet は、属性を変更できるようにするために、最初に <INCLUDE> タグの属性を使って getElementAttributes() をコールします。次に、InputStream を取得するために、変更される可能性のある属性を使って getElementIncludeSource() がコールされます。最後に、RuntimeUtilities.printInclude() はこの InputStream を使用して、リソースのコンテンツを組み込みます。 例えば、リスト 12 のコードは、リスト 13 のコードに変換されます。
リスト 12. <INCLUDE ID="..."/> 部AttributesImpl atts5 = new AttributesImpl (); atts5.addAttribute ("", "SRC", "SRC", "CDATA", "BasicTestInclude.html"); RuntimeUtilities.printInclude (writer, handler.getElementIncludeSource (null, "INCLUDE", atts5));
リスト 13. Java コードに変換されたリスト 12 の HTMLAttributesImpl atts6 = new AttributesImpl (); atts6.addAttribute ("", "ID", "ID", "CDATA", "Copyright"); atts6.addAttribute ("", "SRC", "SRC", "CDATA", "BasicTestInclude.html"); RuntimeUtilities.printInclude (writer, handler.getElementIncludeSource ("Copyright", "INCLUDE", handler.getElementAttributes ("Copyright", "INCLUDE", atts6)));
- ID 属性を持つ各タグごとに、getElementAttributes() および RuntimeUtilities.printTag() のコールが生成されます。実行時に、Hamlet は、属性を変更できるようにするために、タグの属性を使って getElementAttributes() を呼び出します。 続いて RuntimeUtilities.printTag() がコールされ、 変更される可能性のある属性を使ってタグを出力に組み込みます。 例えば、リスト 14 のコードは、リスト 15 のコードに変換されます。
リスト 14. ID 属性を持つタグ<A ID="Ref" HREF="www.pawlitzek.com">
リスト 15. Java コードに変換されたリスト 14 の HTMLAttributesImpl atts4 = new AttributesImpl (); atts4.addAttribute ("", "ID", "ID", "CDATA", "Ref"); atts4.addAttribute ("", "HREF", "HREF", "CDATA", "www.pawlitzek.com"); RuntimeUtilities.printTag (writer, "A", handler.getElementAttributes ("Ref", "A", atts4));
ここまでが、テンプレート・コンパイラーのステージ 1 で、HTML テンプレートを Java ソース・コードに変換します。ステージ 2 では、標準の JDK Java コンパイラーが呼び出され、ステージ 1 で生成されたソースをコンパイルします。その結果生成された Java バイトコードは、実行時に Hamlet によって実行され、レスポンスに動的コンテンツを追加するために HamletHandler によって実装された、4 つのコールバック関数 (getElementReplacement()、getElementRepeatCount()、getElementAttributes()、および getElementIncludeSource()) をコールします。
リスト 16 に示すように、コマンドラインからテンプレート・コンパイラーを起動すると、コマンドラインの引数を使って、その static main() 関数がコールされます。
リスト 16. static main() 関数は TemplateCompiler クラスのインスタンスを作成し、その perform() メソッドをコールします
public class TemplateCompiler {
...
public static void main (String args[]) {
try {
if (args.length == 0) {
System.out.println (
"Usage: java -cp <classpath> com.ibm.hamlet.compiler.TemplateCompiler " +
"[-debug] [-verbose] [-dstDir <directory>] <Template.html>");
Runtime.getRuntime().exit (1);
} // if
TemplateCompiler tc = new TemplateCompiler ();
for (int n = 0; n < args.length; n++) {
if ("-debug".equals (args[n]))
tc.setDebug (true);
else if ("-verbose".equals (args[n]))
tc.setVerbose (true);
else if ("-dstDir".equals (args[n]))
tc.setDstDir (args[++n]);
else
tc.setFileName (args[n]);
} // for
tc.perform ();
} catch (Exception e) {
e.printStackTrace ();
Runtime.getRuntime().exit (1);
} // try
} // main
} // TemplateCompiler
|
最初に、コンパイラーは引数の数をチェックします。引数がない場合、テンプレート・コンパイラーの使い方を示す短いメッセージが出力され、アプリケーションは終了します。 それ以外の場合は、TemplateCompiler クラスのインスタンス (tc) が作成され、コマンドラインの引数が解析されます。次に、実際の作業を行うために、テンプレート・コンパイラーの perform() メソッドがコールされます。このメソッドは、リスト 17 に示すように、上記の 2 つのステージ (HTML テンプレートの Java ソース・コードへの変換、およびそのソース・コードのコンパイル) を実装します。
リスト 17. perform() は HTML から Java バイトコードへ変換する 2 つのステージを実装します
public class TemplateCompiler {
...
public void perform () throws Exception {
// stage one: create template.java
File fin = new File (fileName);
String templateName = fin.getName ();
String className = RuntimeUtilities.getClassName (templateName);
String sourceName = dstDir + File.separator + className + ".java";
File fout = new File (sourceName);
InputStream in = new FileInputStream (fin);
OutputStream out = new FileOutputStream (fout);
SourceGenerator generator = getSourceGenerator ();
if (verbose) System.out.println ("Creating " + sourceName + " ...");
generator.perform (in, out, className);
out.close ();
in.close ();
// stage two: compile template.java
String[] args = { sourceName };
Main compiler = new Main ();
if (verbose) System.out.println ("Compiling " + sourceName + " ...");
compiler.compile (args);
// delete template.java
if (!debug) {
if (verbose) System.out.println ("Deleting " + sourceName + " ...");
fout.delete ();
} // if
if (verbose) System.out.println ("Finished.");
} // perform
...
} // TemplateCompiler
|
ステージ 1 では、getSourceGenerator() が SourceGenerator インターフェースを実装するオブジェクト (ジェネレーター) を取得する前に、FileInputStream および FileOutputStream が作成されます。リスト 18 は、このインターフェースを示したものです。
リスト 18. SourceGenerator インターフェース
public interface SourceGenerator {
public void perform (InputStream in, OutputStream out, String name)
throws Exception;
} // SourceGenerator
|
generator.perform (in, out, className) をコールすることによって、入力ストリームから HTML テンプレートが読み取られ、Java ソース・コードに変換されてから、出力ストリームに書き込まれます。
ステージ 2 では、標準の JDK Java コンパイラーが、compiler.compile (args) の実行時にステージ 1 で生成された Java ソース・コードから Java バイトコードを生成します。その後テンプレート・コンパイラーは、デバッグ・モードがアクティブでなければ、使われなくなった Java ソースを削除します。
リスト 19 に示すように、DefaultGenerator クラスは HTML テンプレートを Java コードに変換します。このクラスは SourceGenerator インターフェースを実装します。
リスト 19. DefaultGenerator の perform() メソッドは、HTML から Java ソース・コードへの 変換を開始します
public class DefaultGenerator extends DefaultHandler implements SourceGenerator {
...
public void perform (InputStream in, OutputStream out, String source)
throws Exception {
XMLReader reader = null;
try {
this.source = source;
this.out = new PrintStream (out);
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
...
} // DefaultGenerator
|
DefaultGenerator の perform() メソッドは、リーダーのプールから SAX リーダーを取得し、SAX のエラー・ハンドラーとコンテンツ・ハンドラーを設定して、入力ソース (テンプレート) の解析を開始します。解析が終了すると、SAX リーはーがリーダーのプールに戻されます。
テンプレートの解析時に、ジェネレーターの SAX コンテンツ・ハンドラー・メソッド (startDocument()、endDocument()、startElement()、endElement()、および characters()) がコールされます。リスト 20 は、これを実装したものです。
リスト 20. DefaultGenerator クラスは、HTML を Java ソース・コードに変換する SAX コンテンツ・ハンドラー・メソッドを実装します
public class DefaultGenerator extends DefaultHandler implements SourceGenerator {
...
public void startDocument () throws SAXException {
stack = new Stack ();
buf = new StringBuffer ();
generateHeader (out, source);
} // startDocument
public void endDocument () throws SAXException {
generateText (out, buf.toString ());
generateFooter (out, source);
} // endDocument
public void startElement (String namespaceURI, String localName,
String qName, Attributes atts) throws SAXException {
// category.debug ("local name: " + localName + ", qname: " + qName);
if (REPEAT_TAG.equals (localName)) {
generateText (out, buf.toString ());
generateElementRepeatHeader (out, atts);
buf = new StringBuffer ();
} else if (REPLACE_TAG.equals (localName)) {
generateText (out, buf.toString ());
String id = atts.getValue (ID_ATTRIBUTE);
if (id != null)
generateElementReplacement (out, atts);
buf = new StringBuffer ();
ignore = true;
} else if (INCLUDE_TAG.equals (localName)) {
generateText (out, buf.toString ());
generateElementInclude (out, localName, atts);
buf = new StringBuffer ();
ignore = true;
} else {
String id = atts.getValue (ID_ATTRIBUTE);
if (id != null) {
generateText (out, buf.toString ());
generateElementAttributes (out, localName, atts);
buf = new StringBuffer ();
} else {
buf.append ("<");
buf.append (localName);
for (int i = 0; i < atts.getLength (); i++) {
buf.append (" ");
buf.append (atts.getLocalName (i));
buf.append ("=\"");
buf.append (atts.getValue (i));
buf.append ("\"");
} // for
buf.append (">");
} // if
} // if
} // startElement
public void endElement (String namespaceURI, String localName, String qName)
throws SAXException {
if (REPEAT_TAG.equals (localName)) {
generateText (out, buf.toString ());
generateElementRepeatFooter (out);
buf = new StringBuffer ();
} else if (REPLACE_TAG.equals (localName) || INCLUDE_TAG.equals (localName)) {
ignore = false;
} else {
buf.append ("</");
buf.append (localName);
buf.append (">");
} // if
} // endElement
public void characters (char[] ch, int start, int length) throws SAXException {
chars = new String (ch, start, length);
if (!ignore)
buf.append (chars);
} // characters
...
} // DefaultGenerator
|
テンプレートが実際に解析される前に、startDocument() メソッドがコールされます。このメソッドは、静的テンプレート・コンテンツを収集するための StringBuffer と、ネストされた <REPEAT> タグを処理するためのスタックを作成します。さらに generateHeader(out, source) がコールされ、import 文と serveDoc() メソッド定義が含まれているソース・コード・ヘッダーを生成します。
SAX パーサーは、開始タグおよび終了タグを認識すると、startElement() および endElement() をコールします。これらのメソッドは、上記の「動作」のセクションで説明した概要に従って、テンプレート・コンテンツを変換します。
例えば、SAX リーダーが <REPEAT> タグを見つけると、startElement() は現在の StringBuffer を消去します。つまり、generateText(out, buf.toString ()) は、Java ソース・コードの中に writer.print() 文を生成します。writer.print() 文の引数は、<REPEAT> タグが見つかるまでに読み込まれた、すべての静的 HTML コンテンツが含まれている文字列になります。次に、generateElementRepeatHeader(out, atts) が、for ループの先頭のソースを生成します。最後に、新しい StringBuffer インスタンスが作成され、解析が続行します。リスト 21 は、このすべてを示したものです。
public void startElement (String namespaceURI, String localName,
String qName, Attributes atts) throws SAXException {
if (REPEAT_TAG.equals (localName)) {
generateText (out, buf.toString ());
generateElementRepeatHeader (out, atts);
buf = new StringBuffer ();
} ...
} // startElement
|
SAX リーダーが、対応する </REPEAT> タグを見つけると、endElement() は、現行の StringBuffer を消去して generateElementRepeatFooter(out) をコールし、未完了の for ループの Java ソース・コードを完了させます。解析を継続する前に、再び新しい StringBuffer が作成されます。 リスト 22
リスト 22. endElement() で処理する <REPEAT> タグは、未完了の for ループを完了させる Java ソース・コードを生成します
public void endElement (String namespaceURI, String localName, String qName)
throws SAXException {
if (REPEAT_TAG.equals (localName)) {
generateText (out, buf.toString ());
generateElementRepeatFooter (out);
buf = new StringBuffer ();
} ...
} // endElement
|
<REPLACE> タグと <INCLUDE> タグは、ほとんど同じようにコードを生成します。 ID 属性を含む他のすべてのタグでもこれと同じことが言えます。
解析が終了すると endDocument() メソッドがコールされます。このメソッドは、StringBuffer を消去し、generateFooter(out, source) を使ってソース・コード・フッターを生成します。
これが、開発時に HTML テンプレートが Java クラスにコンパイルされる方法です。
実行時に、アプリケーション・サーバーまたはサーブレット・コンテナーがユーザー要求を受け取ると、Hamlet の doGet() メソッドがコールされます。doGet() 内で Hamlet はハンドラーを作成します。このハンドラーは、HamletHandler を拡張し、デフォルトで 4 つのコールバック・メソッド (getElementReplacement()、getElementRepeatCount()、getElementAttributes()、および getElementIncludeSource()) を実装するクラスのインスタンスです。 HamletHandler を拡張したクラス (例えば、BasicTestHandler) は、この 4 つのコールバック・メソッドを上書きして、リスト 23 に示すような、特定の Hamlet 用に特別な動的コンテンツを提供します。
リスト 23. BasicTestHandler クラスの 1 つのインスタンスは、BasicTestTemplate.html 用の動的コンテンツを提供します
public class BasicTest extends Hamlet {
...
private static class BasicTestHandler extends HamletHandler {
public String getElementReplacement (String id, String name, Attributes atts)
throws Exception {
...
} // getElementReplacement
public int getElementRepeatCount (String id, String name, Attributes atts)
throws Exception {
...
} // getElementRepeatCout
public Attributes getElementAttributes (String id, String name, Attributes atts)
throws Exception {
...
} // getElementAttributes
public InputStream getElementIncludeSource (String id, String name, Attributes atts)
throws Exception {
...
} // getElementIncludeSource
} // BasicTestHandler
public void doGet (HttpServletRequest req, HttpServletResponse res)
throws ServletException {
try {
HamletHandler handler = new BasicTestHandler (this);
serveDoc (req, res, "BasicTestTemplate.html", handler);
} catch (Exception e) {
category.error ("", e);
throw new ServletException (e);
} // try
} // doGet
} // BasicTest
|
次に Hamlet の serveDoc() メソッドがコールされ、テンプレートの名前とハンドラーが引数として (サーブレット・リクエスト (req) およびサーブレット・レスポンス (res) と一緒に) 渡されます。serveDoc() は、リスト 24 のように private serveDoc() メソッドをコールします。
リスト 24. Hamlet の private serveDoc() メソッドは、コンパイル済みテンプレートの Java バイトコードを見つけるために findTemplateClass() をコールします
public abstract class Hamlet extends HttpServlet implements ContentHandler {
...
private void serveDoc (PrintWriter out, String template, ContentHandler handler)
throws Exception {
findTemplateClass (template);
if (templateClass != null) {
...
} else {
...
} // if
} // serveDoc
} // Hamlet
|
このメソッドは、コンパイル済みテンプレートの Java バイトコードを見つけるために findTemplateClass(template) をコールします。 リスト 25 は、findTemplateClass() を実装したものです。
リスト 25. findTemplateClass() メソッドはテンプレートの Java バイトコードをロードします
private void findTemplateClass (String template) throws Exception {
if (!template.equals (oldTemplate)) {
String className = RuntimeUtilities.getClassName (template);
try {
category.debug ("Loading class '" + className + "' ...");
Class c = Class.forName (className);
templateClass = (Template) c.newInstance ();
category.debug ("Class '" + className + "' loaded");
} catch (ClassNotFoundException e) {
category.debug ("Cannot load class '" + className + "'");
} // try
oldTemplate = template;
} // if
} // findTemplateClass
|
findTemplateClass() は、テンプレートの Java バイトコードをロードする必要があるかどうかをチェックします。その必要がある場合は、RuntimeUtilities.getClassName(template) は、テンプレートのコンパイル済み Java ソース・コードを含むクラス (className) の名前を返します。Class.forName(className) を使えば、インスタンス (templateClass) が作成される前にクラスがロードされます。 リスト 26 は、Template 型のインスタンスを示しています。
リスト 26. テンプレート・インターフェースは、テンプレート・クラスのインターフェースを定義します
public interface Template {
public void serveDoc (PrintWriter writer, ContentHandler handler) throws Exception;
} // Template
|
テンプレート・コンパイラーは、各 HTML テンプレートごとに別々の Java クラスを生成します。これらの各クラスは、Template インターフェースを実装し、そのためリスト 27 に示すように serveDoc() メソッドを提供します。
テンプレートのコンパイル済み Java コードを含むクラスが見つかると、そのクラスは、templateClass.serveDoc(out, handler) をコールすることによって実行されます。そのクラスが見つからないと、Hamlet は非コンパイル・モードに戻ります。つまり、SAX を使ってテンプレートを解析するテンプレート・エンジンを取得します (これは、Hamlet のコンパイルが考えられるまでは、常に行われていたことです)。
リスト 27. Hamlet の serveDoc() メソッドは、テンプレートの Java バイトコードが存在する場合はそのバイトコードを実行し、存在しなけい場合は、テンプレート・エンジンがすぐにテンプレートを解析します。
private void serveDoc (PrintWriter out, String template, ContentHandler handler)
throws Exception {
findTemplateClass (template);
if (templateClass != null) {
templateClass.serveDoc (out, handler);
} else {
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.");
} // if
} // serveDoc
|
コンパイルすべきでしょうか、それともコンパイルすべきでないのでしょうか。どちらのケースでも、要求は満たしてくれます。コンパイル済みテンプレートを使えば、パフォーマンスは目立って向上することがわかっています。どの程度向上するかは、主にテンプレート内の静的コンテンツの量に依存します。タグが 50 個程度の非常に小さいテンプレート (BasicTestTemplate.html のように) では、コンパイル済み Hamlet の方が 1.2 倍高速になります。タグが 1,000 個程度の大きなテンプレートでは、コンパイルすると、1.75 倍高速に実行されます。
テンプレートのコンパイルは開発時に行われます。そのため、ビルド・スクリプトの中にリスト 28 に示すような Ant タスクをもち、テンプレート・コンパイラーをコールすると便利です。
リスト 28. Ant スクリプトからテンプレート・コンパイラーをコールする
<project name="build" default="jar" basedir=".">
...
<target name="template" depends="compile">
<taskdef name="hamletc" classname="com.ibm.hamlet.ant.CompileTask"/>
<hamletc destdir="${dst}">
<fileset dir="${src}">
<include name="*Template.html" />
</fileset>
</hamletc>
</target>
</project>
|
リスト 29 は、基本的な Ant タスクを実装する方法を示しています。
リスト 29. CompileTask クラスは、テンプレート・コンパイラーをコールするために Ant タスクを実装します。
public class CompileTask extends Task {
...
private String dstDir;
private ArrayList fileSets = new ArrayList ();
public void setDestdir (String dir) {
dstDir = dir;
System.out.println ("Destination directory: " + dstDir);
} // setDestdir
public void addFileset (FileSet set) {
fileSets.add (set);
} // addFileset
public void execute () {
// setup compiler
TemplateCompiler compiler = new TemplateCompiler ();
compiler.setDebug (debug);
compiler.setVerbose (verbose);
compiler.setDstDir (dstDir);
// invoke compiler for all files
Project project = getProject ();
Iterator iter = fileSets.iterator ();
while (iter.hasNext ()) {
Object elem = iter.next ();
if (elem instanceof FileSet) {
FileSet set = (FileSet) elem;
DirectoryScanner scanner = set.getDirectoryScanner (project);
String srcDir = scanner.getBasedir().getPath ();
System.out.println ("Source directory: " + srcDir);
String fileNames[] = scanner.getIncludedFiles ();
System.out.println ("Compiling " + fileNames.length +
" template file(s) to " + dstDir);
for (int i = 0; i < fileNames.length; i++) {
String fileName = srcDir + File.separator + fileNames[i];
// System.out.println (" scanning " + fileName);
compiler.setFileName (fileName);
try {
compiler.perform ();
} catch (Exception e) {
throw new BuildException (e);
} // try
} // for
} // if
} // while
} // execute
} // CompileTask
|
setDestdir() と addFileset() を使ってコンパイルできるように、宛先ディレクトリーをセットし、ファイル・セットを指定した後、リスト 28 に示す Ant タスクを実行すると、com.ibm.hamlet.ant.CompileTask クラスのインスタンスが作成され、その execute() メソッドがコールされます。execute() 内では、ファイル・セットのすべてのテンプレート・ファイルを宛先ディレクトリーにコンパイルするために、テンプレート・コンパイラーはインスタンス化されます。
この記事では、テンプレート・ファイルを Java バイトコードに変換するためのテンプレート・コンパイラーという、Hamlet フレームワークに対する小さな追加機能について紹介しました。その結果生成されたコードは Hamlet によって実行され、実行時のユーザー要求を満たしてくれます。このようなコンパイル済みテンプレートを使った Hamlet (いわゆる、コンパイル済み Hamlet) は、パフォーマンスを目立って向上させ、たくさんのユーザーが同時に利用できる Web アプリケーションの開発を可能にします。
テンプレート・コンパイラーの完全な Java ソース・コード (600 行未満) は、「ダウンロード」セクションで入手可能です。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| Complete Java source codefor template compiler | wa-hamlets4/hamlet-compiler.zip | 6KB | HTTP |
学ぶために
- 「Introducing Hamlets,」 Rene Pawlitzek (developerWorks, 2005年3月): Hamlet プログラミングの基礎を習得し、少量のコードを使って、コンテンツとプレゼンテーションを分離する方法について学びます。
- 「Programming Hamlets,」 Rene Pawlitzek (developerWorks, 2005年3 月): このチュートリアルでは、Hamlet v1.0 プログラミングのさまざまな面を学び、いくつかの実用的な Hamlet の例を検討します。
- 「Implementing Hamlets,」 Rene Pawlitzek (developerWorks, 2006年2月): Hamlet v1.1 の実装および動的コンテンツを提供する別の方法である HamletHandler についてお読みください。
-
The Java Servlet API White Paper: Java サーブレットの概要を入手してください。
-
developerWorks の Web アーキテクチャー・ゾーン: Web テクノロジーに特化された記事およびチュートリアルを使って、サイト開発のスキルを高めてください。
-
developerWorks technical events and webcasts: 短期間で習得できるたくさんの情報が詰まったテクニカル・セッションで、最新の情報を入手し、難しいソフトウエア・プロジェクトの品質および結果の向上に役立ててください。
製品や技術を入手するために
-
SAX (Simple API for XML): 広く使われているこの API をチェックしてください。
-
Ant: Java テクノロジー・ベースのビルド・ツールについてたくさんの情報を入手してください。
-
Hamlet フレームワーク: IBM alphaWorks から入手できます。
-
IBM trial software: 次期開発プロジェクトの構築に、developerWorks から直接ダウンロードできる IBM の試用版ソフトウェアをご利用ください。
議論するために
-
ディスカッション・フォーラムに参加してください。
-
developerWorks blogs から developerWorks のコミュニティーに加わってください。
-
developerWorks discussion forums: 興味のあるディスカッション・スレッドに参加してください。

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 で働いていました。