Shale Clay Pluginを使う

最近はHTMLをテンプレートとするFrameworkが増えてきました.
(最近ではPOHP・・・Plain Old HTML Pageと呼ばれているようです)
例えば,S2JSF,Facelets,TapestryWicket...

S2JSFとFaceletsを一度使用してみると,やはりHTMLテンプレートはいいなと.
特に,JSFを使用していると正直タグを書く気にはなれなくなります.

ShaleもViewにHTMLテンプレートを使用することができます.
では,HTML Viewを使ってみます.

HTML View

HTML View を使用するための準備をします.

Jar
  • commons-beanutils.jar
  • commons-chain.jar
  • commons-codec.jar
  • commons-digester.jar
  • commons-logging.jar
  • commons-validator.jar
  • myfaces-api.jar
  • myfaces-impl.jar
  • shale-clay.jar
  • shale-core.jar


とりあえず,今回はshale-usecase-YYYYMMDD.warを展開して,
libに入っているjarを使用することにします.
上記Jarは,今回作成するサンプルに必要最小限のjarです.

設定

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
                        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <!-- Select JSF State Saving Mode -->
    <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>client</param-value>
    </context-param>

    <!-- Commons Chain Configuration Resources -->
    <context-param>
        <param-name>
            org.apache.commons.chain.CONFIG_WEB_RESOURCE
        </param-name>
        <param-value>/WEB-INF/chain-config.xml</param-value>
    </context-param>

    <!-- Clay Common Configuration Resources -->
    <context-param>
        <param-name>
            org.apache.shale.clay.COMMON_CONFIG_FILES
        </param-name>
        <param-value>/WEB-INF/clay-config.xml</param-value>
    </context-param>

    <!-- Shale Application Controller Filter -->
    <filter>
        <filter-name>shale</filter-name>
        <filter-class>
            org.apache.shale.faces.ShaleApplicationFilter
        </filter-class>
    </filter>

    <!-- Shale Application Controller Filter Mapping -->
    <filter-mapping>
        <filter-name>shale</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- Commons Chain Configuration Listener -->
    <listener>
        <listener-class>
            org.apache.commons.chain.web.ChainListener
        </listener-class>
    </listener>

    <!-- Clay Configuration Listener -->
    <listener>
        <listener-class>
            org.apache.shale.clay.config.ClayConfigureListener
        </listener-class>
    </listener>

    <!-- JavaServer Faces Servlet Configuration -->
    <servlet>
        <servlet-name>faces</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- JavaServer Faces Servlet Mapping for Clay HTML Full View -->
    <servlet-mapping>
        <servlet-name>faces</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

    <!-- Welcome File List -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>

faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE faces-config PUBLIC
  "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
  "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">

<faces-config>

    <application>
        <locale-config>
            <default-locale>en</default-locale>
            <supported-locale>jp</supported-locale>
            <supported-locale>en</supported-locale>
        </locale-config>
    </application>

    <!-- ViewController Beans -->
    <managed-bean>
        <managed-bean-name>hello</managed-bean-name>
        <managed-bean-class>sample.HelloBean</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
    </managed-bean>

</faces-config>

chain-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<catalogs>

    <!-- Define preprocessing command chain for Shale to execute -->
    <catalog name="shale">
        <chain name="preprocess">

            <!-- Perform remote request processing for matching requests -->
            <!-- Successful match will terminate the processing chain -->
            <command
                className="org.apache.commons.chain.generic.LookupCommand"
                catalogName="shale" name="remote" optional="true" />

            <!-- This command is only needed for full clay html views with myfaces  -->
            <command
                className="org.apache.shale.clay.faces.ClayViewHandlerCommand" />

            <!-- This filter command wakes up the watchdog monitoring the Clay configuration files for change. -->
            <command
                className="org.apache.shale.clay.config.beans.ConfigDefinitionsWatchdogFilter"
                includes="\S*\.faces,\S*\.html,/index\.jsp,\S*\.xml" />

            <!-- Disallow direct access to JSP and JSFP resources -->
            <command
                className="org.apache.shale.application.ContextRelativePathFilter"
                includes="\S*\.xml,\S*\.faces,\S*\.html,\S*\.gif,\S*\.jpg,/index\.jsp"
                excludes="\S*\.jsp,\S*\.jspf" />
        </chain>
    </catalog>

</catalogs>

clay-config.xml (2006/06/04 修正)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE view PUBLIC
      "-//Apache Software Foundation//DTD Shale Clay View Configuration 1.0//EN"
      "http://struts.apache.org/dtds/shale-clay-config_1_0.dtd">

<view>
    <!-- Hello -->
    <component jsfid="name" extends="outputText" allowBody="false">
        <attributes>
            <set name="value" value="#{@managed-bean-name.name}" />
        </attributes>
    </component>
</view>

hello.html (2006/06/04 修正)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Hello</title>
</head>
<body>

<form id="helloForm">
name:<input type="text" value="#{@managed-bean-name.name}" id="name" />
<input type="submit" action="#{@managed-bean-name.doSetName}" value="go" id="go" />
</form>
Hello. <span jsfid="name">sample</span>
</body>
</html>

sample.HelloBean

package sample;

public class HelloBean {

    private String name_;
    public String getName() {
        return name_;
    }

    public void setName(String name) {
        name_ = name;
    }

    public String doSetName() {
        return null;
    }
}
アプリケーションを動かしてみる
http://localhost:8080/sample1/hello.html

画面が表示されましたか?
では,nameに「hoge」とでも入れてみますか.

Hello. hoge

と,表示されればOK.

では,日本語を入力してみます.
nameに「てすと」と入力してみます.

Hello. a?|a??a?¨

文字化けしました.

こんなときは,Encodeing Filterを設定します.

インストールしたTomcatからFilterを持ってきましょう.

$TOMCAT_HOME/webapps/servlets-examples/WEB-INF/classes/filters

にソースがあります.コピーしてください.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
                        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <!-- Select JSF State Saving Mode -->
    <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>client</param-value>
    </context-param>

    <!-- Commons Chain Configuration Resources -->
    <context-param>
        <param-name>
            org.apache.commons.chain.CONFIG_WEB_RESOURCE
        </param-name>
        <param-value>/WEB-INF/chain-config.xml</param-value>
    </context-param>

    <!-- Clay Common Configuration Resources -->
    <context-param>
        <param-name>
            org.apache.shale.clay.COMMON_CONFIG_FILES
        </param-name>
        <param-value>/WEB-INF/clay-config.xml</param-value>
    </context-param>

    <!-- Set character encoding on each request -->
    <filter>
        <filter-name>Set Character Encoding</filter-name>
        <filter-class>filters.SetCharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>

    <!-- Shale Application Controller Filter -->
    <filter>
        <filter-name>shale</filter-name>
        <filter-class>
            org.apache.shale.faces.ShaleApplicationFilter
        </filter-class>
    </filter>

    <!-- Mapping to apply the "Set Character Encoding" filter
        to *all* requests processed by this web application -->
    <filter-mapping>
        <filter-name>Set Character Encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- Shale Application Controller Filter Mapping -->
    <filter-mapping>
        <filter-name>shale</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- Commons Chain Configuration Listener -->
    <listener>
        <listener-class>
            org.apache.commons.chain.web.ChainListener
        </listener-class>
    </listener>

    <!-- Clay Configuration Listener -->
    <listener>
        <listener-class>
            org.apache.shale.clay.config.ClayConfigureListener
        </listener-class>
    </listener>

    <!-- JavaServer Faces Servlet Configuration -->
    <servlet>
        <servlet-name>faces</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- JavaServer Faces Servlet Mapping for Clay HTML Full View -->
    <servlet-mapping>
        <servlet-name>faces</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

    <!-- Welcome File List -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>

では,もう一度日本語を入力してみます.
nameに「てすと」と入力してみます.

Hello. てすと

表示できました.

HTMLテンプレートに日本語を使ってみる

では,次にテンプレートに日本語を使ってみます.

hello.html (2006/06/04 修正)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>こんにちは</title>
</head>
<body>

<form id="helloForm">
名前:<input type="text" value="#{@managed-bean-name.name}" id="name" />
<input type="submit" action="#{@managed-bean-name.doSetName}" value="go" id="go" />
</form>
こんにちは. <span jsfid="name">sample</span>
</body>
</html>

もう一度,表示してみます.

http://localhost:8080/sample1/hello.html

...文字化けしました...orz

Clayを修正します.

日本語が文字化けするようでは使い物にならない為,修正します.
前回,ShaleのBuildを参考にしてjarを作成します.

修正するファイルは,

$Shaleのルート/clay-plugin/src/java/org/apache/shale/clay/config/ClayTemplateParser.java

org.apache.shale.clay.config.ClayTemplateParser.java

/*
 * Copyright 2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Id: ClayTemplateParser.java 409894 2006-05-27 22:57:44Z gvanmatre $
 */
package org.apache.shale.clay.config;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Iterator;
import java.util.List;

import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shale.clay.config.beans.ComponentBean;
import org.apache.shale.clay.config.beans.ComponentConfigBean;
import org.apache.shale.clay.config.beans.ConfigBean;
import org.apache.shale.clay.config.beans.ElementBean;
import org.apache.shale.clay.config.beans.TemplateConfigBean;
import org.apache.shale.clay.parser.Node;
import org.apache.shale.clay.parser.Parser;
import org.apache.shale.clay.parser.builder.Builder;
import org.apache.shale.util.Messages;
import org.xml.sax.SAXException;

/**
 * <p>This class is responsible for loading an HTML template into a graph 
 * of {@link ComponentBean}'s that represents a JSF component tree.  It 
 * is used by {@link org.apache.shale.clay.config.beans.TemplateConfigBean},
 * a subclass of {@link org.apache.shale.clay.config.beans.ComponentConfigBean}.
 * </p>
 */
public class ClayTemplateParser implements ClayConfigParser {

    /**
     * <p>Commons logging utility object static instance.</p>
     */
    private static Log log;
    static {
        log = LogFactory.getLog(org.apache.shale.clay.config.ClayXmlParser.class);
    }

    /**
     * <p>Message resources for this class.</p>
     */
    private static Messages messages = new Messages("org.apache.shale.clay.Bundle",
            ClayConfigureListener.class.getClassLoader());

    
    /**
     * <p>Object pool for HTML template configuration files.</p>
     */
    private ConfigBean config = null;

    /**
     * <p>Sets an object pool for HTML template configuration files.</p>
     */
    public void setConfig(ConfigBean config) {
        this.config = config;
    }

    /**
     * <p>Returns an object pool for HTML template configuration files.</p>
     */
    public ConfigBean getConfig() {
        return config;
    }

    /**
     * <p>Loads the <code>templateURL</code> identified by the <code>templateName</code>
     * into a graph of {@link ComponentBean}'s.</p> 
     */
    public void loadConfigFile(URL templateURL, String templateName) throws IOException,
            SAXException {

        ((ComponentConfigBean) config).addChild(generateElement(templateURL, templateName));
    }
    
    
    /**
     * <p>Loads the template file and parses it into a composition
     * of metadata that's used by the {@link org.apache.shale.clay.component.Clay} 
     * component.  This metadata is used to construct a JSF subtree 
     * within target view.
     * </p> 
     */
    protected ComponentBean generateElement(URL templateURL, String templateName) throws IOException {
        
        if (log.isInfoEnabled())
            log.info(messages.getMessage("loading.template",  new Object[] {templateName}));
            
            ComponentBean root = new ComponentBean();   
            root.setJsfid(templateName);
            root.setComponentType("javax.faces.NamingContainer");
            
            // generate the document
            
            InputStreamReader inputStreamReader = null;
            StringBuffer buffer = null;
            
            try {
               InputStream inputStream = templateURL.openStream();
               FacesContext context = FacesContext.getCurrentInstance();
               HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
               inputStreamReader = new InputStreamReader(inputStream,request.getCharacterEncoding());
               buffer = loadTemplate(inputStreamReader, templateName);
            } finally {
               if (inputStreamReader != null)
                   inputStreamReader.close();
            }
            
            List roots = new Parser().parse(buffer);
            Iterator ri = roots.iterator();
            while (ri.hasNext()) {
                Node node = (Node) ri.next();
                Builder renderer = node.getBuilder();
                ElementBean child = renderer.createElement(node);
                
                root.addChild(child);
                if (renderer.isChildrenAllowed()) {
                    renderer.encode(node, child, child);
                } else {
                    renderer.encode(node, child, root);
                }
            }
            
            roots.clear();
            roots = null;
            buffer.setLength(0);
            buffer = null;
            ri = null;
            
            //verify there is not a duplicate component id within a naming 
            //container.
            config.checkTree(root);
            
            //compress the tree merging adjacent verbatim nodes
            if (config instanceof TemplateConfigBean)
              ((TemplateConfigBean) config).optimizeTree(root);
            
            
            return root;
    }

    
    /**
     * <p>Loads the template into a <code>StringBuffer</code> using
     * the <code>inputStream</code> and <code>templateName<code> parameters.
     * </p>
     */
    protected StringBuffer loadTemplate(InputStreamReader inputStreamReader, String templateName) throws IOException {
        
        
        StringBuffer buff = new StringBuffer();
        
        try {
            int c = 0;
            done: while (true) {
                c = inputStreamReader.read();
                if (c > -1)
                    buff.append((char) c);
                else
                    break done;
                
            }
        } catch (IOException e) {
            log.error(messages.getMessage("loading.template.exception", new Object[] {templateName}), e);
            throw e;
        }
        
        return buff;
        
    }


}

作成した shale-clay.jar をコピーしてください.

もう一度,表示してみます.

http://localhost:8080/sample1/hello.html

うまく表示されましたね.

テンプレートを読み込む再に,InputStreamReaderを使用するように修正しました.
指定するエンコードは,リクエストで取得したエンコードを指定しました.

海外のFrameworkを使用する際に,やはり日本語が使用できるか否かは気になりますよね.
残念ながら,Clayはまだアルファリリースの為日本語に若干の問題が残っているようです.
バグフィックスされるまでは,上記の修正で暫定対応して続きをやっていこうと思います.


patch.txt

Index: /clay-plugin/src/java/org/apache/shale/clay/config/ClayTemplateParser.java
===================================================================
--- /clay-plugin/src/java/org/apache/shale/clay/config/ClayTemplateParser.java	(revision 411191)
+++ /clay-plugin/src/java/org/apache/shale/clay/config/ClayTemplateParser.java	(working copy)
@@ -19,10 +19,14 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.net.URL;
 import java.util.Iterator;
 import java.util.List;
 
+import javax.faces.context.FacesContext;
+import javax.servlet.http.HttpServletRequest;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.shale.clay.config.beans.ComponentBean;
@@ -108,15 +112,18 @@
 			
             // generate the document
             
-            InputStream inputStream = null;
+            InputStreamReader inputStreamReader = null;
             StringBuffer buffer = null;
             
             try {
-               inputStream = templateURL.openStream();
-               buffer = loadTemplate(inputStream, templateName);
+               InputStream inputStream = templateURL.openStream();
+               FacesContext context = FacesContext.getCurrentInstance();
+               HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
+               inputStreamReader = new InputStreamReader(inputStream, request.getCharacterEncoding());
+               buffer = loadTemplate(inputStreamReader, templateName);
             } finally {
-               if (inputStream != null)
-                  inputStream.close();
+               if (inputStreamReader != null)
+            	   inputStreamReader.close();
             }
             
             List roots = new Parser().parse(buffer);
@@ -158,7 +165,7 @@
      * the <code>inputStream</code> and <code>templateName<code> parameters.
      * </p>
      */
-    protected StringBuffer loadTemplate(InputStream inputStream, String templateName) throws IOException {
+    protected StringBuffer loadTemplate(InputStreamReader inputStreamReader, String templateName) throws IOException {
         
         
         StringBuffer buff = new StringBuffer();
@@ -166,7 +173,7 @@
         try {
             int c = 0;
             done: while (true) {
-                c = inputStream.read();
+                c = inputStreamReader.read();
                 if (c > -1)
                     buff.append((char) c);
                 else