2009年12月6日日曜日

コンテナ管理トランザクションであそぶ - (1/10) -

はじめに

EJBのコンテナ管理トランザクション(ContainerManagedTransactions-CMT)とは
どんなものなのか試してみようというのが今回のテーマです。
ステートレスのローカルオブジェクトのセッション Bean で検証をします。
主に@TransactionAttributeアノテーションによってTransactionAttributeTypeが
REQUIREDとREQUIRES_NEWに指定されたメソッドがコミットされる様子と
ロールバックされる様子を観察したいと思います。
インターセプターを使ってトランザクション内に例外を発生させてロールバックするようにします。
確認のためにセッションIDをアクセスログとして保存し表示する検証用Webアプリを作成します。
検証用Webアプリはシンプルにしたいのでフレームワークを使わずServletとJSPで作成します。

以下の内容を検証します。
  1. REQUIREDに指定されたメソッド同士は同じトランザクションとして処理されるのか?
    トランザクションに参加するいずれかのメソッドで例外が発生した場合、
    その トランザクションに参加するすべてのメソッドでの処理がロールバックされるか検証します。
  2. REQUIRES_NEWに指定されたメソッドは別トランザクションを開始して処理されるのか?
    別トランザクションのメソッドで例外が発生した場合でも REQUIRES_NEWに指定された
    メソッドでの処理がコミットされていれば、その処理はロールバックされないことを検証します。
    (別トランザクションのメソッドでの処理はロールバックされる)

コンテナ管理トランザクションであそぶ - (2/10) -

準備する

検証環境
アプリケーションサーバー GlassFish v2.1
データベースサーバー Apache Derby
OS Windows XP
ビルドツール Maven 2
※mvnコマンドが実行できるように設定しておいてください。

適当な場所に作業フォルダー(C:\mywork)を作り、フォルダとファイルを配置します。
一括ビルドプロジェクト(build)、EARファイルプロジェクト(build-ear)、
EJBプロジェクト(ejb-module)、Webアプリプロジェクト(webtest-client)を作成します。

  • C:\mywork
    • build
      • pom.xml
    • build-ear
      • pom.xml
    • ejb-module
      • src
        • main
          • java
            • com
              • mydomain
                • ejb
                  • Action.java
                  • ActionImpl.java
                  • Message.java
                  • MessageUtil.java
                  • Rollback.java
                  • SubAction.java
                  • SubActionImpl.java
          • resources
            • META-INF
              • ejb-jar.xml
              • persistence.xml
      • pom.xml

    • webtest-client
      • src
        • main
          • java
            • com
              • mydomain
                • ejb
                  • Client.java
                  • PageData.java
          • webapp
            • WEB-INF
              • web.xml
            • index.jsp
      • pom.xml

コンテナ管理トランザクションであそぶ - (3/10) -

DataSourceの設定をする

asadmin create-jdbc-connection-poolコマンドを使ってコネクションプールを作成します。

コマンドプロンプトを開き以下のように入力します。
asadmin create-jdbc-connection-pool ^
  --datasourceclassname org.apache.derby.jdbc.ClientDataSource ^
  --restype javax.sql.DataSource ^
  --property user=sa:password=sa:databaseName=example ^
  MyJDBCPool
コネクションプールのプロパティに接続文字列を追加します。
asadmin set ^
  domain.resources.jdbc-connection-pool.MyJDBCPool.property.url=jdbc:derby://localhost:1527/example
コネクションプールのプロパティに接続文字列属性を追加します。
asadmin set ^
  domain.resources.jdbc-connection-pool.MyJDBCPool.property.connectionAttributes=;create=true
コネクションの接続を確認します。
asadmin ping-connection-pool MyJDBCPool


asadmin create-jdbc-resourceコマンドを使ってコネクションプールをJNDIに登録します。

コマンドプロンプトを開き以下のように入力します。
asadmin create-jdbc-resource ^
  --connectionpoolid MyJDBCPool ^
  jdbc/MyJDBCResource
GlassFish管理コマンド

asadmin コマンドを使用するためには 『GLASSFISHインストールフォルダ/bin』 フォルダを
PATH変数に追加しておかないといけません。
コントロールパネルのシステムの詳細設定タブから環境変数を編集します。
それからGlassFishサーバーとJavaDBを起動します。
WebのGlassFish管理コンソールからコネクションプールをJDBCリソースに登録する場合は
『jdbc/MyJDBCResource』 でJNDIから参照できるようにします。
Persistence Unitを定義します。

persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
  xmlns="http://java.sun.com/xml/ns/persistence" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
                      http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="example-pu">
  <provider>oracle.toplink.essentials.PersistenceProvider</provider>
    <jta-data-source>jdbc/MyJDBCResource</jta-data-source>
    <properties>
      <property name="toplink.ddl-generation" value="drop-and-create-tables" />
    </properties>
  </persistence-unit>
</persistence>
Persistence UnitをJNDIのグローバルネームスペース 『example-pu』 に登録します。

コンテナ管理トランザクションであそぶ - (4/10) -

ビジネスBeanを作成する

ステートレス Session Beanを作成します。
ビジネスインターフェイスとセッションBeanを2組作成します。

Action.java
package com.mydomain.ejb;

import java.util.List;
import javax.ejb.*;

@Local
public interface Action
{
    public void regist(String message, int rollbak);
    
    public void insert(Message message);
    
    public void decorateInsert(Message message);

    public List<Message> getMessages();
    
    public void delete();
}
ActionImpl.java
package com.mydomain.ejb;

import java.util.*;
import javax.ejb.*;
import javax.persistence.*;
import static com.mydomain.ejb.MessageUtil.*;

@Stateless
public class ActionImpl implements Action
{
    @PersistenceContext(unitName="example-pu")
    private EntityManager em;
    
    @EJB
    private SubAction subAction;

    public void regist(String text, int rollback)          
    {       
        this.decorateInsert(createMessage(text, 2, rollback));
        this.insert(createMessage(text, 3, rollback));
        this.subAction.decorateInsert(createMessage(text, 5, rollback));
        this.subAction.insert(createMessage(text, 6, rollback));
        try
        {
            this.subAction.requiresNewInsert(createMessage(text, 8, rollback));
        }
        catch(Exception exp){}

        Message chkMessage = createMessage(text, 1, rollback);
        doRollback(chkMessage);
    }

    public void insert(Message message)          
    {
        setLogMessage(message, this.getClass(), "insert");
        this.em.persist(message);
        
        Message decMessage = createMessage(message);
        setNextLogMessage(decMessage);
        this.decorateInsert(decMessage);
        
        doRollback(message);
    }
    
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void decorateInsert(Message message)            
    {
        setLogMessage(message, this.getClass(), "decorateInsert");
        message.setText(message.getText() + "*");
        this.em.persist(message);

        doRollback(message);
    }

    public List<Message> getMessages()
    {
        String sql = "SELECT m FROM message AS m ORDER BY m.no";
        return this.em.createQuery(sql).getResultList();
    }

    public void delete()
    {
        this.em.createQuery("DELETE FROM message").executeUpdate();
    }
}
regist()メソッド
サーブレットから呼出されてセッションIDをアクセスログとして登録するメインの処理を開始します。
処理の最後にMessageUtil.doRollback()メソッドを呼出して例外発生の判定します。
insert()メソッド
セッションIDを登録します。
処理の最後にMessageUtil.doRollback()メソッドを呼出して例外発生の判定します。
decorateInsert()メソッド
セッションIDに『*』 アスタリスクを付けて登録します。
REQUIRES_NEW を指定して別トランザクションとして処理されるようにします。
処理の最後にMessageUtil.doRollback()メソッドを呼出して例外発生の判定します。


SubAction.java
package com.mydomain.ejb;

import javax.ejb.Local;

@Local
public interface SubAction
{
    public void insert(Message message);

    public void decorateInsert(Message message);
    
    public void requiresNewInsert(Message message);
}
SubActionImpl.java
package com.mydomain.ejb;

import javax.ejb.*;
import javax.persistence.*;
import static com.mydomain.ejb.MessageUtil.*;

@Stateless(name="subActionImpl")                 
public class SubActionImpl implements SubAction
{
    @PersistenceContext(unitName="example-pu")
    private EntityManager em;

    public void insert(Message message)          
    {
        setLogMessage(message, this.getClass(), "insert");
        this.em.persist(message);

        Message decMessage = createMessage(message);
        setNextLogMessage(decMessage);
        this.decorateInsert(decMessage);

        doRollback(message);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void decorateInsert(Message message)            
    {
        setLogMessage(message, this.getClass(), "decorateInsert");
        message.setText(message.getText() + "*");
        this.em.persist(message);

        doRollback(message);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void requiresNewInsert(Message message)         
    {
        String sessionId = message.getText();
        message.setText(sessionId + "+");
        this.em.persist(message);

        Message decMessage = createMessage(message);
        setNextLogMessage(decMessage);
        decMessage.setText(sessionId);
        this.insert(decMessage);
    }
}
このクラスのEJB名(EJBコンテナ内での名前)をsubActionImpl に設定します。
insert()メソッド
セッションIDを登録します。
処理の最後にMessageUtil.doRollback()メソッドを呼出して例外発生の判定します。
decorateInsert()メソッド
セッションIDに『*』 アスタリスクを付けて登録します。
REQUIRES_NEW を指定して別トランザクションとして処理されるようにします。
処理の最後にMessageUtil.doRollback()メソッドを呼出して例外発生の判定します。
requiresNewInsert()メソッド
セッションIDに『+』 プラスを付けて登録します。
REQUIRES_NEW を指定して別トランザクションとして処理されるようにします。
処理の最後にインターセプターによって例外発生の判定をします。

コンテナ管理トランザクションであそぶ - (5/10) -

インターセプターを作成する

インターセプターを作成します。

Rollback.java
package com.mydomain.ejb;

import javax.interceptor.*;
import static com.mydomain.ejb.MessageUtil.*;

public class Rollback
{
    @AroundInvoke
    public Object action(InvocationContext ctx) throws Exception
    {
        Object[] params = ctx.getParameters();
        Message message = (Message)params[0];
        setLogMessage(message, ctx.getTarget().getClass(), ctx.getMethod().getName());
        
        Object retObj = ctx.proceed();                     
        
        doRollback(message);
        
        return retObj;
    }
}
同じクラスの同じメソッドであっても呼出される条件によってインターセプトが適用されない場合もあったので
SubActionImpl クラスの requiresNewInsert() メソッドのみにインターセプトを設定しています。
ejb-jar.xml
<ejb-jar version="3.0"
  xmlns="http://java.sun.com/xml/ns/javaee" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
                      http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">
  <interceptors>
    <interceptor>
      <interceptor-class>com.mydomain.ejb.Rollback</interceptor-class>         
    </interceptor>
  </interceptors>
  <assembly-descriptor>
    <interceptor-binding>
      <ejb-name>subActionImpl</ejb-name>         
      <interceptor-class>com.mydomain.ejb.Rollback</interceptor-class>
      <method>
        <method-name>requiresNewInsert</method-name>
        <method-params>
          <method-param>com.mydomain.ejb.Message</method-param>
        </method-params>
      </method>
    </interceptor-binding>
  </assembly-descriptor>
</ejb-jar>
インターセプトされないメソッドの呼出し

EJBインスタンスを通して直接呼出される場合はインターセプトが適用されるようですが
メソッドの処理の中で呼出される場合にはインターセプトが 適用されないみたいでした。
今回の検証用Webアプリの例でいうとActionImplクラスのinsert()メソッドとdecorateInsert()メソッドは
regist()メソッドから呼出されるためインターセプトは適用されないようです。
(regist()メソッドはサーブレットから直接呼出されるのでインターセプトが適用できるようです。)
またSubActionImplクラスのinsert()メソッドとdecorateInsert()メソッドは
ActionImplクラスのregist()メソッドから直接呼出される場合にはインターセプトが適用されるようですが、
requiresNewInsert()メソッドの処理の中から呼出されたinsert()メソッド、
insert()メソッドの処理の中から呼出されたdecorateInsert()メソッドには インターセプトが適用されないようでした。

コンテナ管理トランザクションであそぶ - (6/10) -

エンティティBeanを作成する

エンティティBeanを作成します。

Message.java
package com.mydomain.ejb;

import java.io.Serializable;
import javax.persistence.*;

@Entity(name="message")
public class Message implements Serializable
{
    private static final long serialVersionUID = 1L;

    public Message(){}

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
    private long id;
    public long getId(){ return this.id; }
    public void setId(long id){ this.id = id; }

    private String text;
    public String getText(){ return this.text; }
    public void setText(String text){ this.text = text; }

    @Column(name="number")
    private int no;
    public int getNo(){ return this.no; }
    public void setNo(int no){ this.no = no; }

    @Column(name="rollback_point")
    private int rollback;
    public int getRollback(){ return this.rollback; }
    public void setRollback(int rollback){ this.rollback = rollback; }

    private String biko;
    public String getBiko(){ return this.biko; }
    public void setBiko(String biko){ this.biko = biko; }
}


エンティティBeanの生成と例外の生成をするユーティリティクラスを作成します。

MessageUtil.java
package com.mydomain.ejb;

public class MessageUtil
{    
    public static Message createMessage(Message message)
    {
        return createMessage(
            message.getText(), message.getNo(), message.getRollback(), message.getBiko());
    }

    public static Message createMessage(String text, int no, int rollback)
    {
        return createMessage(text, no, rollback, "");
    }
    
    public static Message createMessage(String text, int no, int rollback, String biko)
    {
        Message message = new Message();
        message.setText(text);
        message.setNo(no);
        message.setRollback(rollback);
        message.setBiko(biko);
        return message;
    }

    public static void setLogMessage(Message message, Class cls, String method)
    {
        String biko = cls.getSimpleName() + ":" + method + "( )";
        message.setBiko(message.getBiko() + biko);
    }
    
    public static void setNextLogMessage(Message message)
    {
        message.setBiko(message.getBiko() + "=>");
        message.setNo(message.getNo()+1);
    }
    
    public static boolean doRollback(Message message)      
    {
        if(message.getRollback() == message.getNo())
        {
            Object obj = null;
            obj.toString();
        }
        return false;
    }
}
doRollback()メソッド
例外発生の判定をします。
例外はjava.lang.NullPointerExceptionをthrowします。

コンテナ管理トランザクションであそぶ - (7/10) -

検証用Webアプリを作成する

サーブレットとviewページを作成します。

Client.java
package com.mydomain.ejb;

import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;
import javax.ejb.EJB;
import javax.servlet.*;
import javax.servlet.http.*;

public class Client extends HttpServlet
{
    @EJB
    private Action action;
    
    protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
    {
        HttpSession session = req.getSession();
        try
        {
            this.action.delete();
            String strRollback = req.getParameterMap().containsKey("rollback")
                ? req.getParameter("rollback").toString() : "0";
            int rollback = Pattern.matches("[^0-9]", strRollback)
                ? 0 : Integer.parseInt(strRollback);
            try
            {
                this.action.regist(session.getId(), rollback);
            }
            catch(Exception exp){}
            req.setAttribute("messages", this.action.getMessages());
            this.setMethodList(req, rollback);
        }
        catch(Exception ex)
        {
            session.invalidate();
            ex.printStackTrace();
            System.out.println("webclient servlet test failed");
            throw new ServletException(ex);
        }
    }
    
    protected void setMethodList(HttpServletRequest req, int rollback)
    {
        PageData pageData = new PageData();
        List<Map<String,String>> methodList = pageData.getMethodList();
        req.setAttribute("methodList", methodList);

        List<String> rollbacks = new ArrayList<String>();
        for(int ct=0; ct<methodList.size(); ct++)
        {
            rollbacks.add((rollback == ct) ? "checked" : "");
        }
        req.setAttribute("rollbacks", rollbacks);
    }
}
PageData.java
package com.mydomain.ejb;

import java.io.Serializable;
import java.util.*;

public class PageData implements Serializable
{
    private static final long serialVersionUID = 1L;

    public PageData(){}
    
    public List<Map<String,String>> getMethodList()
    {
        List<Map<String,String>> methods = new ArrayList<Map<String,String>>();
        String spc = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
        String pls = spc + spc + spc + "+";

        this.addMethodRow(methods, "", "");
        this.addMethodRow(methods, "+->regist( )", pls);
        this.addMethodRow(methods, spc + "+->decorateInsert( )", pls);
        this.addMethodRow(methods, spc + "+->insert( )", pls);
        this.addMethodRow(methods, spc + "&nbsp;&nbsp;&nbsp;+->decorateInsert( )", pls);
        this.addMethodRow(methods, spc + "+--------------------------", "--->decorateInsert( )");
        this.addMethodRow(methods, spc + "+--------------------------", "--->insert( )");
        this.addMethodRow(methods, "", spc + "&nbsp;&nbsp;&nbsp;+->decorateInsert( )");
        this.addMethodRow(methods, spc + "+--------------------------", "--->requiresNewInsert( )");
        this.addMethodRow(methods, "", spc + "&nbsp;&nbsp;&nbsp;+->insert( )");
        this.addMethodRow(methods, "", spc + spc + "+->decorateInsert( )");
        
        return methods;
    }

    protected void addMethodRow(List<Map<String,String>> methods, String act, String sub)
    {
        Map<String,String> row = new HashMap<String,String>();
        row.put("act", act);
        row.put("sub", sub);
        row.put("rollback", Integer.toString(methods.size()));
        methods.add(row);
    }
}
index.jsp
<%@ page language="java" pageEncoding="UTF-8" %>
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.util.*,com.mydomain.ejb.*" %>
<jsp:include page="/servlet" />
<%
List<String> rollbacks = (List<String>)request.getAttribute("rollbacks");
List<Map<String,String>> methodList 
  = (List<Map<String,String>>)request.getAttribute("methodList"); 
%>
<html>
  <head>
    <title>Access Log</title>
    <style type="text/css">
      table.list, table  {font-size: 9pt;}
      table.list th {background:#FFCC66;}
      table.list td {background:#FFFFCC}
    </style>
  </head>
  <body>
    <form method="post">
      <table>
        <tr>
          <td>
            <table style="border: solid 1px;">
              <tr>
                <th>No.</th>
                <th></th>
                <th align="left" style="text-decoration: underline;">ActionImpl</th>
                <th style="text-decoration: underline;">SubActionImpl</th>
              </tr>
              <% for(int ct=1; ct<rollbacks.size(); ct++){ %>
              <tr>
                <td><%= ct %></td>
                <td align="right">
                  Rollback
                  <input type="radio" name="rollback" value="<%= ct %>" <%= rollbacks.get(ct) %>>
                </td>
                <td><%= methodList.get(ct).get("act") %></td>
                <td><%= methodList.get(ct).get("sub") %></td>
              </tr>
              <% } %>
              <tr>
                <td></td>
                <td align="right">
                  Commit
                  <input type="radio" name="rollback" value="0"  <%= rollbacks.get(0) %>>
                </td>
                <td colspan="3"></td>
              </tr>
            </table>
            <input type="submit" value="access">
          </td>
        </tr>
        <tr>
          <td>
            <table class="list">
              <tr>
                <th>No.</th>
                <th>Session ID<br /></th>
                <th>Class:Method</th>
              </tr>
              <% for(Message row : (List<Message>)request.getAttribute("messages")){ %>
              <tr>
                <td><%= row.getNo() %></td>
                <td><%= row.getText() %></td>
                <td><%= row.getBiko() %></td>
              </tr>
              <% } %>
            </table>
          </td>
        </tr>
      </table>
    </form>
  </body>
</html>


web.xmlファイルを作成します。

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
  xmlns="http://java.sun.com/xml/ns/javaee" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
                      http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>  
  <servlet>
    <servlet-name>Servlet</servlet-name>
    <servlet-class>com.mydomain.ejb.Client</servlet-class>
    <load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Servlet</servlet-name>
    <url-pattern>/servlet</url-pattern>
  </servlet-mapping>
</web-app>
サーブレットで@EJBアノテーションを使用するためServlet2.5のスキーマを宣言します。

コンテナ管理トランザクションであそぶ - (8/10) -

ビルドする

ejb-jarファイル、warファイル、earファイル作成ビルドファイルを作成します。

ejb-module/pom.xml
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mydomain.ejb</groupId>
  <artifactId>ejb-module</artifactId>
  <packaging>ejb</packaging>
  <version>1.0</version>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-ejb-plugin</artifactId>
        <configuration>
          <ejbVersion>3.0</ejbVersion>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.apache.openejb</groupId>
      <artifactId>javaee-api</artifactId>
      <version>5.0-2</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.openejb</groupId>
      <artifactId>openejb-core</artifactId>
      <version>3.1.2</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
</project>
webtest-client/pom.xml
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mydomain.ejb</groupId>
  <artifactId>webtest-client</artifactId>
  <packaging>war</packaging>
  <version>1.0</version>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.apache.openejb</groupId>
      <artifactId>javaee-api</artifactId>
      <version>5.0-2</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.openejb</groupId>
      <artifactId>openejb-core</artifactId>
      <version>3.1.2</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>com.mydomain.ejb</groupId>
      <artifactId>ejb-module</artifactId>
      <version>1.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
</project>
build-ear/pom.xml
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mydomain.ejb</groupId>
  <artifactId>enterprise-bean-example</artifactId>
  <packaging>ear</packaging>
  <version>1.0</version>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-ear-plugin</artifactId>
        <version>2.4</version>
        <configuration>
          <modules>
            <ejbModule>
              <groupId>com.mydomain.ejb</groupId>
              <artifactId>ejb-module</artifactId>
            </ejbModule>
            <webModule>
              <groupId>com.mydomain.ejb</groupId>
              <artifactId>webtest-client</artifactId>
              <contextRoot>webtest-client</contextRoot>
            </webModule>
          </modules>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>com.mydomain.ejb</groupId>
      <artifactId>ejb-module</artifactId>
      <version>1.0</version>
      <type>ejb</type>
    </dependency>
    <dependency>
      <groupId>com.mydomain.ejb</groupId>
      <artifactId>webtest-client</artifactId>
      <version>1.0</version>
      <type>war</type>
    </dependency>
  </dependencies>
</project>


一括ビルドpomファイルを作成します。

build/pom.xml
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mydomain.ejb</groupId>
  <artifactId>build</artifactId>
  <packaging>pom</packaging>
  <version>1.0</version>
  <modules>
      <module>../ejb-module/pom.xml</module>
      <module>../webtest-client/pom.xml</module>
      <module>../build-ear/pom.xml</module>
   </modules>
</project>


コマンドプロンプトを開き一括ビルドpomファイルのあるフォルダに移動して一括ビルドpomファイルを実行します。

cd C:\mywork\build
mvn clean install
生成されたenterprise-bean-example-1.0.earを 『GLASSFISHインストールフォルダ/domains/domain1/autodeploy』フォルダーにコピーします。

コンテナ管理トランザクションであそぶ - (9/10) -

検証する

Webブラウザから検証ページ(『http://localhost:8080/webtest-client/』)にアクセスして動作を確認します。
検証用Webアプリの説明

  • ラジオボタンでロールバックするポイント、またはコミットを選択して 『access』 ボタンを押します。
    (メソッド処理の最後に例外が発生してロールバックします。)
  • セッションIDとセッションIDが登録されたクラスとメソッドの呼出しツリーが一覧に表示されます。
検証する
  1. ラジオボタンでNo.3の Rollback を選択して 『access』 ボタンを押します。

    No.2の処理(ActionImplクラスのdecorateInsert()メソッド)は
    TransactionAttributeType.REQUIRES_NEWが指定されているので
    No.3の処理(ActionImplクラスのinsert()メソッド)で例外が発生しても
    No.2の処理は登録されるはずなのですが登録されていません。
    これはNo.2の処理がActionImplクラスのregist()メソッドから呼出されているため
    regist()メソッドが呼出された段階でActionImplクラスのEntityManagerにトランザクションが設定されるようです。
    この時のトランザクションはregist()メソッドで指定されたTransactionAttributeType.REQUIRED(デフォルト)になり
    regist()メソッドの処理が終了するまでの間、このクラスのメソッドはTransactionAttributeTypeの指定に関わらず
    同一のトランザクションとして処理されるようです。

  2. ラジオボタンでNo.6の Rollback を選択して 『access』 ボタンを押します。

    No.6の処理(SubActionImplクラスのinsert()メソッド)はActionImplクラスのregist()メソッドで設定された
    トランザクションの参加することになるようで、No.6の処理で例外が発生すると
    ActionImplクラスのメソッドでの処理 (No.2~4)も一緒にロールバックされるようです。
    No.5の処理(SubActionImplクラスのdecorateInsert()メソッド)は
    TransactionAttributeType.REQUIRES_NEWが指定されているので、
    このメソッドが呼出された段階で SubActionImplクラスのEntityManagerに
    別トランザクションが設定されメソッド処理の終了時にコミットされているようです。
    そのためNo.5の処理はロールバックされず登録されています。

  3. ラジオボタンでNo.8の Rollback を選択して 『access』 ボタンを押します。

    No.8の処理(SubActionImplクラスのrequiresNewInsert()メソッド)で発生した例外は
    ActionImplクラスのregist()メソッドで catch しているので
    他のメソッドの処理は ロールバックされず登録されるようです。

  4. ラジオボタンでNo.1の Rollback を選択して 『access』 ボタンを押します。

    No.7の処理(SubActionImplクラスのdecorateInser()メソッド)は
    TransactionAttributeType.REQUIRES_NEWが 指定されているのですが
    No.6の処理(SubActionImplクラスのinsert()メソッド)から呼出されているため
    No.6の処理で設定されたトランザクション(ActionImplクラスのregist()メソッドで設定されたトランザクション)に
    参加することになり、No.1の処理で例外が発生するとActionImplクラスのメソッドでの処理 (No.2~4)と
    SunActionImplクラスのNo.6、No.7の処理はロールバックされるようです。
    No.5の処理(SubActionImplクラスのdecorateInsert()メソッド)は
    別トランザクションで処理されているので ロールバックせずに登録されるようです。
    No.8の処理(SubActionImplクラスのrequiresNewInsert()メソッド)は
    TransactionAttributeType.REQUIRES_NEWが 指定されたメソッドなので
    別トランザクションで処理されることからNo.8の処理、そしてこの処理から呼出される
    No.6、No.7の処理は ロールバックせずに登録されるようです。

  5. ラジオボタンで Commit を選択して 『access』 ボタンを押します。

    すべての登録処理が実行されます。

結果考察
  • Session Beanのフィールドに置かれたEntityManagerは Session Beanのインスタンスを通して
    最初に呼出されたメソッドの TransactionAttributeTypeに従ってトランザクションを開始するようだ。
    そしてそのメソッドの処理が終了するまでそのクラスのメソッドはTransactionAttributeTypeの指定に関わらず
    同一のトランザクションとして処理されるようだ。
  • 同じクラスの同じメソッドであっても呼出される条件によって参加するトランザクションが変わるようだ。
  • 1つのEntityManagerのインスタンスは1つのトランザクションを管理するようだ。

コンテナ管理トランザクションであそぶ - (10/10) -

おわりに

今更ながらコンテナ管理トランザクションを試してみてとても良い感じでした。
もっとはやく試していたらよかったとも思いました。
begin-commit-rollback をコーディングしなくてよくなるだけなのですが
たったこれだけのことでも一旦コーディングしなくてもよくなると
なんとかコーディングしないで済むようにソースコードを工夫しだしたりします。
ただ本格的に使うにはもうちょっと調査が必要かな?とも...
ステートレスセッションBeanとステートフルセッションBeanでは動作が少し違うような気もします。
ローカルオブジェクトとリモートオブジェクトでも違ったりするのかな?
もしかすると実行環境によっても動作に差があるかも???
このあたりは今後のテーマです。
今回は例外の生成にJavaEEのインターセプターを使っていたのですが
これがなんだかいまいちで...(あたしの理解不足でしょうか?)
AspectJ なんかを使うともうちょっと良い感じになるのではと思います。
EJBをJNDIからlookupしてViewページに渡すところも Seam なんかを使っても面白いのはないでしょうか。
参考
http://homepage3.nifty.com/rio_i/lab/ejb3/index.html
http://itpro.nikkeibp.co.jp/article/COLUMN/20060615/241006/
Sun GlassFish Enterprise Server v2.1.1 Administration Guide.pdf
ejb-3_0-fr-spec-ejbcore.pdf

2009年11月8日日曜日

JBossのテンプレートでDocBook - (1/8) -

はじめに

DocBookスタイルシートと使うと同じドキュメントソースからPDF,HTMLなど
いろいろな形式のドキュメントファイルが作れてなかなか便利なのだけれども
そこそこ見栄えの良いものにしょうとすると結構大変なのです。
cssファイルであったり、イメージファイルを自前で用意しなくてはいけないから…
そこでJBossのドキュメント作成に使われ入るテンプレートなどを拝借して
見栄えの良いドキュメントを作ろう!というのが今回のテーマです。

JBossのテンプレートでDocBook - (2/8) -

準備する

ライブラリーとスタイルシートをダウンロードします。

DocBookテンプレートはJBossのMavenリピジトリからダウンロードします。

DocBookスタイルシート docbook-xsl-1.75.2.zip
DocBookテンプレート jbossorg-docbook-xslt-1.1.0.jar
jbossorg-jdocbook-style-1.1.0.jdocbook-style
フォント jbossorg-fonts-1.0.0.jdocbook-style
(さざなみフォント)
XSLTプロセッサ saxon6-5-5.zip
ライブラリー Xerces-J-bin.2.9.1.zip
fop-0.95-bin.zip
highlight-1.0.0.jar
jhighlight-1.0.jar
ビルドツール Ant 1.7
※antコマンドが実行できるように設定しておいてください。
XalanとSaxon
JBoosのDocBookテンプレートを使ってDocBookを作成する場合、 XSLTプロセッサはSaxonを選択する方が無難かも?
highlightライブラリーを使って拡張変換処理をしているみたいで、この処理の実行時にXalanではエラーになってしまう。
xslファイルのネームスペースを利用して拡張変換処理クラスの処理を有効にするのだけれど
ネームスペースの指定方法がSaxonとXalanでは違っていてXalanので設定の仕方がよく分からなかった。
適当な場所に作業フォルダー(C:\mybook)を作り、ダウンロードしたファイルを配置します。
  • C:\mybook
    • doc
      • inc
        • HelloWorld.java
      • book.xml
      • chapter.xml
    • html
    • lib
      • fop-0.95
        • fop.bat
      • saxon6-5-5
        • saxon-jdom.jar
        • saxon-xml-apis.jar
        • saxon.jar
      • xerces-2_9_1
        • xercesImpl.jar
      • highlight-1.0.0.jar
      • jhighlight-1.0.jar
    • pdf
    • resources
      • docbook-xsl-1.75.2
        • fo
        • xhtml
      • j-docbook-fonts
        • fonts
      • j-docbook-style
        • images
          • images
            • community
            • my-image.png
      • j-docbook-xslt
        • xslt
    • build.xml
    • fop.xconf
jbossorg-fonts-1.0.0.jdocbook-style は拡張子を *.zip に変更して解凍します。
フォルダ名をj-docbook-fontsに変更して配置します。
jbossorg-jdocbook-style-1.1.0.jdocbook-style は拡張子を *.zip に変更して解凍します。
フォルダ名をj-docbook-styleに変更して配置します。
jbossorg-docbook-xslt-1.1.0.jar は拡張子を *.zip に変更して解凍します。
フォルダ名をj-docbook-xsltに変更して配置します。
fop.xconfはlib/fop/conf フォルダにあるファイルをコピーします。
docフォルダにソースドキュメントを作成します。
resources/j-docbook-style/images/imagesフォルダに ドキュメント作成に使うイメージファイルを配置します。
ビルド後、htmlフォルダにhtmlファイルが出力されます。
ビルド後、pdfフォルダにpdfファイルが出力されます。

JBossのテンプレートでDocBook - (3/8) -

ソースドキュメントを作成する

book.xml, chapter.xml, HelloWorld.java をファイルを作成します。

ファイルの文字コードはUTF-8(BOMなし)にします。

book.xml
<?xml version="1.0" encoding="UTF-8"?>
<book
  xmlns="http://docbook.org/ns/docbook"
  xmlns:xi="http://www.w3.org/2001/XInclude"
  xml:id="mybook">
  <title>Myブック</title>
  <chapter xml:id="mybook.chapter-1">
    <title>メインのファイル</title>
    <section>
      <title>テーブルを表示</title>
      <table>
        <title>成果物とPackaging</title>
        <tgroup cols="4">
          <colspec colnum="1" colwidth="100" />
          <colspec colnum="2" colwidth="200" />
          <colspec colnum="3" colwidth="50" colname="jar" 
            align="center"/>
          <colspec colnum="4" colwidth="50" colname="war" 
            align="center"/>
          <thead valign="top">
            <row>
              <entry morerows='1'>Phase</entry>
              <entry morerows='1'>Plugin:Goal</entry>
              <entry namest="jar" nameend="war">
                packaging
              </entry>
            </row>
            <row>
              <entry>jar</entry>
              <entry>war</entry>
            </row>
          </thead>
          <tbody valign="top">
            <row>
              <entry>compile</entry>
              <entry>compiler:compile</entry>
              <entry>○</entry>
              <entry>○</entry>
            </row>
            <row>
              <entry morerows='1'>package</entry>
              <entry>jar:jar</entry>
              <entry>○</entry>
              <entry>―</entry>
            </row>
            <row>
              <entry>war:war</entry>
              <entry>―</entry>
              <entry>○</entry>
            </row>
          </tbody>
        </tgroup>
      </table>
    </section>
  </chapter>
  <xi:include href="chapter.xml" parse="xml"/>
</book>

chapter.xml
<?xml version="1.0" encoding="UTF-8"?>
<chapter
  xmlns="http://docbook.org/ns/docbook"
  xmlns:xi="http://www.w3.org/2001/XInclude"
  xml:id="mybook.chapter-2">
  <title>インポートされたファイル</title>
  <section>
    <title>DocBookで遊ぼう</title>
    <caution>
      <title>注意書!</title>
      <para>ペンキ塗りたて</para>
    </caution>
    <example>
      <title>HelloWorld.java</title>
      <programlisting role="java"><xi:include 
        href="inc/HelloWorld.java" parse="text" 
        encoding="UTF-8" /></programlisting>
        <screenco>
          <areaspec />
            <screen>javac HelloWorld.java<co 
              xml:id="mybook.chapter-2.javac"/></screen>
            <calloutlist>
              <callout arearefs="mybook.chapter-2.javac">
                <para>コンパイルをします。</para>
              </callout>
            </calloutlist>
        </screenco>
    </example>
    <figure>
      <title>my-image.png表示</title>
      <imageobject>
        <imagedata format="PNG" 
          fileref="images/my-image.png" />
      </imageobject>
    </figure>
  </section>
</chapter>

HelloWorld.java
public class HelloWorld
{
  public static void main(String[] args)
  {
    System.out.println("HelloWorld");
  }
}
textdataタグについて
DocBookのtextdataタグを使っても外部ファイルを取込むことができるが
相対パスでファイルを指定した場合Saxonではうまく読込めない。
(相対パスから絶対パスの組立てでファイルパスがずれてしまう)
Xalanでは問題なく処理できる。
外部ファイルの取込みは<xi:include>タグを使う方が無難なようだ。
またインクルードするファイルは文字コードをUTF-8(BOMなし)で改行コードをLFとしておく方がよい。

JBossのテンプレートでDocBook - (4/8) -

DocBook HTMLテンプレートを編集する

ページヘッダーに表示されるイメージとリンクを変更します。

『resources/j-docbook-xslt/xslt/org/jboss』フォルダーにある『xhtml.xsl』ファイルと『xhtml-common.xsl』ファイルを編集します。

xhtml.xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  ...
  <!--<xsl:import href="http://docbook.sourceforge.net/release/xsl/1.72.0/xhtml/chunk.xsl"/>-->
  <xsl:import href="file:///C:/mybook/resources/docbook-xsl-1.75.2/xhtml/chunk.xsl"/>
  ...
DocBookスタイルシートのパスをローカルファイルに変更します。

xhtml-common.xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ...
  ...
  <!--<xsl:param name="siteHref" select="'http://www.jboss.org'"/>-->
  <!--<xsl:param name="docHref" select="'http://docs.jboss.org/'"/>-->
  <!--<xsl:param name="siteLinkText" select="'JBoss.org'"/>-->
  <!--<xsl:param name="docLinkText" select="'Community Documentation'"/>-->
  <xsl:param name="siteHref" select="'#'"/>
  <xsl:param name="docHref" select="'#'"/>
  <xsl:param name="siteLinkText" select="'MyHost'"/>
  <xsl:param name="docLinkText" select="'MyHost Documentation'"/>
  ...
  <!-- Set chunk.section.depth to 0 to just chunk chapters. -->
  <!--<xsl:param name="chunk.section.depth" select="0"/>-->
  <xsl:param name="chunk.section.depth" select="5"/>
  ...
ヘッダーリンクを変更します。
パラメータを変更してセクションごとにファイルを分割します。(5階層まで)



『resources/j-docbook-style/css/css』フォルダーにある 『documentation.css』ファイルを編集します。

documentation.css
...
#title a.site_href {
  display:block;
  height:89px;
  width:310px;
  float:left;
  /* background:url(../images/community/jbossorglogo.png) top left no-repeat; */
}

#title a.doc_href {
  display:block;
  height:89px;
  /* background:transparent url(../images/community/community_doc.png) top right no-repeat; */
}
...
ヘッダーイメージを削除します。または、差し替える場合はファイルパスを変更します。

JBossのテンプレートでDocBook - (5/8) -

DocBook PDFテンプレートを編集する

日本語フォントとイメージファイルの設定します。

『resources/j-docbook-xsl/xslt/org/jboss』フォルダーにある『common.xsl』ファイルと『pdf.xsl』ファイルを編集します。

common.xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  ...
  <!-- <xsl:param name="l10n.gentext.default.language" select="'en'"/> -->
  <xsl:param name="l10n.gentext.default.language" select="'ja'"/>
  <xsl:param name="l10n.gentext.language" select="'ja-JP'"/>
  ...
言語設定を日本語に変更します。

pdf.xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ...
  ...
  <!--<xsl:import href="http://docbook.sourceforge.net/release/xsl/1.72.0/fo/docbook.xsl"/>-->
  <xsl:import href="file:///C:/mybook/resources/docbook-xsl-1.75.2/fo/docbook.xsl"/>
  ...
  <!--<xsl:param name="fop.extensions" select="1"/>-->
  <!--<xsl:param name="fop1.extensions" select="0"/>-->
  <!--<xsl:param name="img.src.path"/>-->
  <xsl:param name="fop.extensions" select="0"/>
  <xsl:param name="fop1.extensions" select="1"/>
  <xsl:param
    name="img.src.path">file:///C:/mybook/resources/j-docbook-style/images/</xsl:param>
  <xsl:param name="keep.relative.image.uris" select="1"/>
  <xsl:param name="ignore.image.scaling" select="1"/>
  ...
  <xsl:template name="pickfont-sans">
    <xsl:variable name="font">
      <xsl:choose>
        <xsl:when test="$l10n.gentext.language = 'ja-JP'">
          <!--<xsl:text>KochiMincho,</xsl:text>-->
          <xsl:text>Sazanami-Gothic-Regular,</xsl:text>
          ...
  <xsl:template name="pickfont-serif">
    <xsl:variable name="font">
      <xsl:choose>
        <xsl:when test="$l10n.gentext.language = 'ja-JP'">
          <!--<xsl:text>KochiMincho,</xsl:text>-->
          <xsl:text>Sazanami-Gothic-Regular,</xsl:text>
          ...
  <xsl:template name="pickfont-mono">
    <xsl:variable name="font">
      <xsl:choose>
        <xsl:when test="$l10n.gentext.language = 'ja-JP'">
          <!--<xsl:text>KochiMincho,</xsl:text>-->
          <xsl:text>Sazanami-Gothic-Regular,</xsl:text>
          ...
DocBookスタイルシートのパスをローカルファイルに変更します。
prefix(file://)を付けて絶対パスで指定します。
fop.extensions,fop1.extensions,ignore.image.scalingのパラメータを変更、追加します。
イメージファイルを配置したフォルダのパスを設定します。
prefix(file://)を付けて絶対パスで指定します。
改行を入れずに一行で記述します。改行を入れるとエラーになります。
日本語フォントをKochiMinchoからSazanami-Gothic-Regularに変更します。

JBossのテンプレートでDocBook - (6/8) -

フォント設定ファイルを編集する

『fop.xconf』ファイルを編集します。

fop.xconf
<?xml version="1.0"?>
<fop version="1.0">
  ...
  <renderers>
    <renderer mime="application/pdf">
      ...
      <fonts>
        ...
        <directory recursive="true">
          file:///C:/mybook/resources/j-docbook-fonts/fonts
        </directory>
        <auto-detect />
        ...
フォントファイルを配置したフォルダのパスを設定します。
prefix(file://)を付けて絶対パスで指定します。

JBossのテンプレートでDocBook - (7/8) -

ビルドする

『build.xml』ファイルを作成します。

build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="docbook" default="build-pdf">
  <property name="root.dir" location="."/>
  <property name="doc.dir" location="${root.dir}/doc"/>
  <property name="html.dir" location="${root.dir}/html"/>
  <property name="pdf.dir" location="${root.dir}/pdf"/>
  <property name="lib.dir" location="${root.dir}/lib"/>
  <property name="res.dir" location="${root.dir}/resources"/>
  <property name="style.dir" 
    location="${res.dir}/j-docbook-style"/>
  <property name="document" value="document"/>
  <property name="book.xml" location="${doc.dir}/book.xml"/>
  <property name="fop.xconf" location="${root.dir}/fop.xconf"/>
  <property 
    name="docbook.dir" 
    location="${res.dir}/docbook-xsl-1.75.2"/>
  <property 
    name="xhtml.stylesheet" 
    location="${res.dir}/j-docbook-xslt/xslt/org/jboss/xhtml.xsl"/>
  <property 
    name="pdf.stylesheet" 
    location="${res.dir}/j-docbook-xslt/xslt/org/jboss/pdf.xsl"/>
  <property 
    name="conf.key" 
    value="-Dorg.apache.xerces.xni.parser.XMLParserConfiguration"/>
  <property 
    name="conf.val" 
    value="org.apache.xerces.parsers.XIncludeParserConfiguration"/>
  <property 
    name="xinclude.parser.conf" 
    value="${conf.key}=${conf.val}"/>
  <property 
    name="builder.key" 
    value="-Djavax.xml.parsers.DocumentBuilderFactor"/>
  <property 
    name="builder.val" 
    value="org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"/>
  <property 
    name="document.builder.factory" 
    value="${builder.key}=${builder.val}"/>
  <property 
    name="parser.key" 
    value="-Djavax.xml.parsers.SAXParserFactory"/>
  <property 
    name="parser.val" 
    value="org.apache.xerces.jaxp.SAXParserFactoryImpl"/>
  <property 
    name="sax.parser.factory" 
    value="${parser.key}=${parser.val}"/>
  <property 
    name="xerces.dir" 
    location="${lib.dir}/xerces-2_9_1"/>
  <property 
    name="saxon.dir" 
    location="${lib.dir}/saxon6-5-5"/>
  <property name="fop.dir" location="${lib.dir}/fop-0.95"/>
  <taskdef name="fop" 
    classname="org.apache.fop.tools.anttasks.Fop">
  <classpath>
    <fileset dir="${fop.dir}/lib">
      <include name="*.jar" />
    </fileset>
    <fileset dir="${fop.dir}/build">
      <include name="fop.jar" />
      <include name="fop-hyph.jar" />
    </fileset>
  </classpath>
  </taskdef>
  <path id="saxon.classpath">
    <fileset dir="${saxon.dir}" id="saxon.fileset">
      <include name="saxon.jar" />
      <include name="saxon-jdom.jar" />
      <include name="saxon-xml-apis.jar" />
    </fileset>
    <fileset dir="${docbook.dir}/extensions" 
      id="saxon.fileset.ext">
      <include name="saxon65.jar" />
    </fileset>
    <fileset dir="${xerces.dir}" id="xerces.fileset">
      <include name="xercesImpl.jar" />
    </fileset>
    <fileset dir="${lib.dir}" id="jboss.fileset">
      <include name="highlight-1.0.0.jar" />
      <include name="jhighlight-1.0.jar" />
    </fileset>
  </path>
  <target name="build-html">
    <delete dir="${html.dir}" />
    <mkdir dir="${html.dir}" />
    <java classname="com.icl.saxon.StyleSheet" 
      classpathref="saxon.classpath" fork="true">
      <jvmarg value="${xinclude.parser.conf}" />
      <jvmarg value="${document.builder.factory}" />
      <jvmarg value="${sax.parser.factory}" />
      <arg line=
        "-o ${html.dir}/${document}.html ${book.xml} ${xhtml.stylesheet} base.dir=${html.dir}/"/>
    </java>
    <copy todir="${html.dir}">
      <fileset dir="${style.dir}/css">
        <include name="css/*" />
      </fileset>
      <fileset dir="${style.dir}/images/">
        <include name="images/**/*" />
      </fileset>
    </copy>
  </target>
  <target name="build-pdf">
    <delete dir="${pdf.dir}" />
    <mkdir dir="${pdf.dir}" />
    <java classname="com.icl.saxon.StyleSheet" 
      classpathref="saxon.classpath" fork="true">
      <jvmarg value="${xinclude.parser.conf}" />
      <jvmarg value="${document.builder.factory}" />
      <jvmarg value="${sax.parser.factory}" />
      <arg line="-o ${pdf.dir}/${document}.fo ${book.xml} ${pdf.stylesheet}"/>
    </java>
    <fop format="application/pdf" outdir="${pdf.dir}" 
      userconfig="${fop.xconf}" messagelevel="debug">
      <fileset dir="${pdf.dir}">
        <include name="*.fo" />
      </fileset>
    </fop>
  </target>
</project>

コマンドフロンプトを開き作業フォルダーに移動します。
cd C:\mybook

ANTを実行してHTMLファイルを作成します。
ant build-html
ビルド後、htmlフォルダにhtmlファイルが出力されます。

ANTを実行してPDFファイルを作成します。
ant build-pdf
ビルド後、pdfフォルダにpdfファイルが出力されます。


JBossのテンプレートでDocBook - (8/8) -

おわりに

Webベースでドキュメントを作ろうと思ったときの選択肢としてはWiki、CMSなんかもあります。(もちろんBlogもありますし直接HTMLで作るのも当然ありです。)
ドキュメントの管理を考えるとDocBookがいいのかなと思っています。
ただ手軽さには難があるかなというのが正直なところです。
あとドキュメントの内容に比べてファイルが長くなるかなとも…
それで管理と手軽さでいくと結局、Wikiが無難なのかも?

今回はANTを使ってビルドしましたがMavenとmaven-jdocbook-pluginを使う方法もあります。(こちらの方法が正攻法だと思います。)
今回の方法はmaven-jdocbook-pluginの処理をantで書き直したような感じになると思います。
またの機会にmaven-jdocbook-pluginについてもまとめてみたいと思います。
参考
http://www.exacteye.com/docbook.html
http://www.docbook.org/tdg5/en/html/docbook.html
http://docbook.sourceforge.net/release/xsl/current/doc/index.html
http://www.sagehill.net/docbookxsl/index.html
http://xmlgraphics.apache.org/fop/0.95/fonts.html

2009年10月29日木曜日

Google App Engine の datastore でシリアライズエラー

やり方の問題なのだろうか?
datastore にアクセスした時にJPA、JDOに限らず
java.lang.RuntimeException: java.io.NotSerializableException: org.datanucleus.store.appengine.query.DatastoreQuery$1
とのエラーが出る。
Eclipseのデバッグ環境では、問題ないのに…
Googleのサーバではうまくいかない。
Wicketを使っているのだけれどそれが原因?
よくわからないけど抽出結果をそのまま使うとダメなようだ。
Arrayオブジェクトなどに入れ直すとエラーは出なくなった。



List<Employee> src
    = em.createQuery("SELECT e FROM Employee").getResultList();
// 抽出結果をArrayオブジェクトに入れ直しをする。
List<Employee> emps = new ArrayList<Employee>(src);
add(new ListView("listviewtable", emps)
{
    protected void populateItem(ListItem item)
    {
       Employee emply = (Employee) item.getModelObject();
       item.add(new Label("firstName", emply.getFirstName()));
       item.add(new Label("lastName", emply.getLastName()));
    }
});

参考
http://groups.google.co.jp/group/slim3-user-japan/msg/71a5dbf7c7c301fc

2009年10月28日水曜日

はじめに

プログラミングとLinuxについて特に目的もなく書き記していこうと思います。