ブログ アーカイブ

2008年6月12日木曜日

seam-genで作成したJBossSeamをTomcat上で動かす

JBossToolsを使うという手もありますが、まずは基本のseam-genから試してみます。
JBossAS上で動くのは当たり前なので、今回はTomcat上で動かしてみます。

筆者の環境
  • JDK1.6.0_05
  • Tomcat6.0.16
  • Apache Ant1.7.0
  • JBoss Seam2.0.2.SP1
seam-genで作成したプロジェクトをそのままビルドした場合、JBossASでは動作しますがTomcatでは動作しません。Tomcatで動作させるためには、seam-genで作成されたプロジェクトにちょっと手を加える必要があります。

簡単にまとめると以下のような変更が必要です。
  • Embeddable EJB3.0を追加
  • JPAのトランザクションをJTAからRESOURCE_LOCALに変更
  • いくつかのjarファイルをTomcatに配置

まずはseam-genします。
途中でearプロジェクトなのかwarプロジェクトなのか聞かれますのでearプロジェクトを選択してください。他は通常通りでOKです。

できあがったプロジェクトに手を加えます。
日本語で説明するよりソースで説明した方がわかりやすいのでAntのbuild.xmlを書きます。
別ファイルにすべき内容もbuild.xmlに含まれているので長くなっていますが、そんなに難しくありません。ファイルをコピーしたり書き換えたりしているだけです。
<?xml version="1.0" encoding="utf-8"?>
<!--
JBoss Seam 2.0.2.SP1 にてseam-genで作成したearプロジェクトをTomcatで利用できるように変更します。

利用前の準備:
Tomcatのlibフォルダに下記のモジュールをコピーしてください。
jboss-embedded-all.jar : プロジェクトのlib/testフォルダにあります。
log4j.jar : プロジェクトのlibフォルダにあります。
hsqldb.jar (HSQLDBを使用する場合) : プロジェクトのlibフォルダにあります。
このファイルの冒頭にあるpropertyを環境に合わせて設定してください。

注意:
このbuild.xmlの実行は、seam-genでプロジェクトを作成した直後に行なってください。
またデータソース定義はデフォルトでHSQLDBの定義を作成していますので、
実行後に環境に合わせてresources/META-INF/persistence-tomcat.xmlを編集してください。

ターゲット:
ant all :プロジェクトをTomcatで利用できるように変更します。
ant remove :プロジェクトへの変更を元に戻します(tomcat用の情報が削除されます)。

解説:
変更後のプロジェクトでは、build.xmlのprofileをtomcatに設定することでTomcat用のビルドが実行されるようになります。
Tomcat用のデータソース定義はresources/META-INF/persistence-tomcat.xmlに記述してください。
また、resources/WEB-INF/components.xml、deployed-jars-war.listがprofile別に定義するように変更されていますので注意してください。
Tomcatへデプロイする場合はdeployターゲットを使用してください。explodeターゲットは使用できません。
-->
<project default="all">

<!-- 環境に合わせて設定する -->
<property name="project.name" value="SeamOnTomcat"/>
<property name="project.home" location="../SeamOnTomcat"/>
<property name="tomcat.home" location="/home/app/apache-tomcat-6.0.16"/>

<!-- 以下は変更不要 -->
<property name="ejb.url" value="http://jaist.dl.sourceforge.net/sourceforge/jboss/jboss-EJB-3.0_Embeddable_ALPHA_8-patch2.zip"/>
<property name="ejb.file" location="${tmp.dir}/jboss-embeddable-ejb.zip"/>
<property name="ejb.dir" location="${tmp.dir}/jboss-embeddable-ejb.zip"/>

<target name="all" depends="prepare-ejb,prepare-profiles,patch-build-xml"
description="seam-genで生成したearプロジェクトをTomcat用にビルドできるようにします。"/>

<target name="remove"
description="allターゲットで行なったプロジェクトへの変更を元に戻します。">
<delete file="${project.home}/lib/jboss-ejb3-all.jar"/>
<delete file="${project.home}/lib/thirdparty-all.jar"/>
<delete dir="${project.home}/resources/tomcat"/>
<delete file="${project.home}/build-tomcat.properties"/>
<delete file="${project.home}/deployed-jars-war-prod.list"/>
<delete file="${project.home}/deployed-jars-war-dev.list"/>
<delete file="${project.home}/deployed-jars-war-tomcat.list"/>
<delete file="${project.home}/resources/import-tomcat.sql"/>
<delete file="${project.home}/resources/META-INF/persistence-tomcat.xml"/>
<delete file="${project.home}/resources/WEB-INF/components-prod.xml"/>
<delete file="${project.home}/resources/WEB-INF/components-dev.xml"/>
<delete file="${project.home}/resources/WEB-INF/components-tomcat.xml"/>
<move file="${project.home}/build.xml.bak" tofile="${project.home}/build.xml"/>
</target>

<target name="prepare-ejb"
description="JBoss Embeddable EJBのモジュールをプロジェクトにコピーします。">

<!-- jarファイルを取得 -->
<mkdir dir="./tmp/ejb"/>
<get src="${ejb.url}" dest="./tmp/ejb.zip"/>
<unzip src="./tmp/ejb.zip" dest="./tmp/ejb"/>

<!-- コピー -->
<copy todir="${project.home}/lib" flatten="true">
<fileset dir="./tmp/ejb">
<include name="*/lib/jboss-ejb3-all.jar"/>
<include name="*/lib/thirdparty-all.jar"/>
</fileset>
</copy>
<copy todir="${project.home}/resources/tomcat" flatten="true">
<fileset dir="./tmp/ejb">
<include name="*/conf/*.*"/>
<exclude name="*/conf/log4j.xml"/>
</fileset>
</copy>

</target>

<target name="prepare-profiles"
description="tomcat用のprofileを作成します。">

<!-- build.xml -->
<copy file="${project.home}/build-dev.properties" tofile="${project.home}/build-tomcat.properties" overwrite="true"/>

<!-- build.properties -->
<echo file="${project.home}/build-tomcat.properties" append="true">tomcat.home=${tomcat.home}
deploy.dir=$${tomcat.home}/webapps
war.deploy.dir=$${deploy.dir}/$${project.name}
</echo>
<replace file="${project.home}/build-tomcat.properties" token="\" value="/"/>

<!-- deployed-jars-war.list -->
<copy file="${project.home}/deployed-jars-war.list" tofile="${project.home}/deployed-jars-war-prod.list"/>
<copy file="${project.home}/deployed-jars-war.list" tofile="${project.home}/deployed-jars-war-dev.list"/>
<echo file="${project.home}/deployed-jars-war-tomcat.list">antlr-runtime.jar
commons-beanutils.jar
commons-digester.jar
core.jar
drools-compiler.jar
drools-core.jar
janino.jar
jboss-el.jar
jboss-seam.jar
jboss-seam-*.jar
jbpm-jpdl.jar
jsf-facelets.jar
mvel14.jar
richfaces-api.jar
richfaces-impl.jar
richfaces-ui.jar
javassist.jar
dom4j.jar
hibernate*.jar
jsf*.jar
commons-logging.jar
commons-collections.jar
lucene-core.jar
jboss-ejb3.jar
thirdparty-all.jar</echo>

<!-- import.sql -->
<copy file="${project.home}/resources/import-dev.sql" tofile="${project.home}/resources/import-tomcat.sql"/>

<!-- persistence.xml -->
<echo file="${project.home}/resources/META-INF/persistence-tomcat.xml"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<!-- Persistence deployment descriptor for dev profile -->
<persistence 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"
version="1.0">

<persistence-unit name="${project.name}" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
<property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
<property name="hibernate.connection.username" value="sa"/>
<property name="hibernate.connection.password" value=""/>
<property name="hibernate.connection.url" value="jdbc:hsqldb:."/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.HashtableCacheProvider"/>
</properties>
</persistence-unit>

</persistence>]]></echo>

<!-- components.xml -->
<copy file="${project.home}/resources/WEB-INF/components.xml" tofile="${project.home}/resources/WEB-INF/components-prod.xml"/>
<copy file="${project.home}/resources/WEB-INF/components.xml" tofile="${project.home}/resources/WEB-INF/components-dev.xml"/>
<echo file="${project.home}/resources/WEB-INF/components-tomcat.xml"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
xmlns:core="http://jboss.com/products/seam/core"
xmlns:persistence="http://jboss.com/products/seam/persistence"
xmlns:drools="http://jboss.com/products/seam/drools"
xmlns:bpm="http://jboss.com/products/seam/bpm"
xmlns:transaction="http://jboss.com/products/seam/transaction"
xmlns:security="http://jboss.com/products/seam/security"
xmlns:mail="http://jboss.com/products/seam/mail"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.0.xsd
http://jboss.com/products/seam/persistence http://jboss.com/products/seam/persistence-2.0.xsd
http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-2.0.xsd
http://jboss.com/products/seam/bpm http://jboss.com/products/seam/bpm-2.0.xsd
http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.0.xsd
http://jboss.com/products/seam/mail http://jboss.com/products/seam/mail-2.0.xsd
http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.0.xsd">

<core:init debug="@debug@" jndi-pattern="@jndiPattern@"/>

<core:manager concurrent-request-timeout="500"
conversation-timeout="120000"
conversation-id-parameter="cid"
parent-conversation-id-parameter="pid"/>

<transaction:entity-transaction entity-manager="#{entityManager}"/>

<persistence:managed-persistence-context name="entityManager"
auto-create="true"
entity-manager-factory="#{${project.name}EntityManagerFactory}"/>

<persistence:entity-manager-factory name="${project.name}EntityManagerFactory"
persistence-unit-name="${project.name}"/>

<drools:rule-base name="securityRules">
<drools:rule-files>
<value>/security.drl</value>
</drools:rule-files>
</drools:rule-base>

<security:identity security-rules="#{securityRules}" authenticate-method="#{authenticator.authenticate}" remember-me="true"/>

<event type="org.jboss.seam.security.notLoggedIn">
<action execute="#{redirect.captureCurrentView}"/>
</event>
<event type="org.jboss.seam.security.loginSuccessful">
<action execute="#{redirect.returnToCapturedView}"/>
</event>

<mail:mail-session host="localhost" port="2525" username="test" password="test" />

<!-- For use with jBPM pageflow or process management -->
<!--
<bpm:jbpm>
<bpm:process-definitions></bpm:process-definitions>
<bpm:pageflow-definitions></bpm:pageflow-definitions>
</bpm:jbpm>
-->

</components>]]></echo>

</target>

<target name="patch-build-xml"
description="build.xmlをTomcatビルド用に変更します。">

<!-- patchファイルを作成 -->
<echo file="./tmp/build.xml.patch"><![CDATA[***************
*** 5,15 ****
<!-- Give user a chance to override without editing this file or typing -D -->
<property file="$${basedir}/build.properties" />

- <property name="profile" value="dev" />
- <property file="build-$${profile}.properties" />
-
<!-- set global properties for this build -->
<property name="project.name" value="${project.name}"/>
<property name="dist.dir" value="dist" />
<property name="src.model.dir" value="src/model" />
<property name="src.action.dir" value="src/action" />
--- 5,19 ----
<!-- Give user a chance to override without editing this file or typing -D -->
<property file="$${basedir}/build.properties" />

<!-- set global properties for this build -->
<property name="project.name" value="${project.name}"/>
+
+ <property name="profile" value="tomcat" />
+ <condition property="is.tomcat">
+ <equals arg1="$${profile}" arg2="tomcat"/>
+ </condition>
+ <property file="build-$${profile}.properties" />
+
<property name="dist.dir" value="dist" />
<property name="src.model.dir" value="src/model" />
<property name="src.action.dir" value="src/action" />
***************
*** 97,117 ****
overwrite="true"/>
</target>

! <target name="war" depends="compile"
description="Build the distribution .war file">
<copy todir="$${war.dir}">
<fileset dir="$${basedir}/view" />
</copy>
<copy todir="$${war.dir}/WEB-INF">
<fileset dir="$${basedir}/resources/WEB-INF">
<include name="*.*"/>
<include name="classes/**/*.*"/>
<exclude name="classes/**/*.class"/>
</fileset>
- <filterset>
- <filter token="debug" value="$${debug}" />
- <filter token="jndiPattern" value="$${project.name}/#{ejbName}/local" />
- </filterset>
</copy>
<copy todir="$${war.dir}/WEB-INF">
<fileset dir="$${basedir}/resources/WEB-INF">
--- 101,135 ----
overwrite="true"/>
</target>

! <target name="war-tomcat" depends="archive-jboss" if="is.tomcat">
! <copy todir="$${war.dir}.tomcat">
! <fileset dir="$${war.dir}"/>
! </copy>
! <copy todir="$${war.dir}.tomcat/WEB-INF/lib" file="$${dist.dir}/$${project.name}.jar"/>
! </target>
!
! <target name="war-jboss" depends="compile" unless="is.tomcat"
description="Build the distribution .war file">
<copy todir="$${war.dir}">
<fileset dir="$${basedir}/view" />
</copy>
+ <copy todir="$${war.dir}/WEB-INF/classes" failonerror="false">
+ <fileset dir="$${basedir}/resources/$${profile}"/>
+ </copy>
+ <copy file="$${basedir}/resources/WEB-INF/components-$${profile}.xml"
+ tofile="$${war.dir}/WEB-INF/components.xml">
+ <filterset>
+ <filter token="debug" value="$${debug}" />
+ <filter token="jndiPattern" value="$${project.name}/#{ejbName}/local" />
+ </filterset>
+ </copy>
<copy todir="$${war.dir}/WEB-INF">
<fileset dir="$${basedir}/resources/WEB-INF">
<include name="*.*"/>
<include name="classes/**/*.*"/>
<exclude name="classes/**/*.class"/>
+ <exclude name="components*.xml"/>
</fileset>
</copy>
<copy todir="$${war.dir}/WEB-INF">
<fileset dir="$${basedir}/resources/WEB-INF">
***************
*** 121,127 ****
</copy>
<copy todir="$${war.dir}/WEB-INF/lib">
<fileset dir="$${lib.dir}">
! <includesfile name="deployed-jars-war.list" />
<exclude name="jboss-seam-gen.jar" />
</fileset>
</copy>
--- 139,145 ----
</copy>
<copy todir="$${war.dir}/WEB-INF/lib">
<fileset dir="$${lib.dir}">
! <includesfile name="deployed-jars-war-$${profile}.list" />
<exclude name="jboss-seam-gen.jar" />
</fileset>
</copy>
***************
*** 132,137 ****
--- 150,157 ----
</copy>
</target>

+ <target name="war" depends="war-tomcat,war-jboss"/>
+
<target name="ear" description="Build the EAR">
<copy todir="$${ear.dir}">
<fileset dir="$${basedir}/resources">
***************
*** 156,162 ****
</copy>
</target>

! <target name="archive" depends="jar,war,ear"
description="Package the archives">
<jar jarfile="$${dist.dir}/$${project.name}.jar" basedir="$${jar.dir}"/>
<jar jarfile="$${dist.dir}/$${project.name}.war" basedir="$${war.dir}"/>
--- 176,186 ----
</copy>
</target>

! <target name="archive-tomcat" depends="war-tomcat" if="is.tomcat">
! <jar jarfile="$${dist.dir}/$${project.name}-tomcat.war" basedir="$${war.dir}.tomcat"/>
! </target>
!
! <target name="archive-jboss" depends="jar,war-jboss,ear" unless="is.tomcat"
description="Package the archives">
<jar jarfile="$${dist.dir}/$${project.name}.jar" basedir="$${jar.dir}"/>
<jar jarfile="$${dist.dir}/$${project.name}.war" basedir="$${war.dir}"/>
***************
*** 168,174 ****
</fileset>
</jar>
</target>
!
<target name="datasource">
<fail unless="jboss.home">jboss.home not set</fail>
<copy todir="$${deploy.dir}">
--- 192,200 ----
</fileset>
</jar>
</target>
!
! <target name="archive" depends="archive-tomcat,archive-jboss" />
!
<target name="datasource">
<fail unless="jboss.home">jboss.home not set</fail>
<copy todir="$${deploy.dir}">
***************
*** 210,230 ****
<touch file="$${ear.deploy.dir}/META-INF/application.xml"/>
</target>

! <target name="deploy" depends="archive,datasource" description="Deploy to JBoss AS">
<fail unless="jboss.home">jboss.home not set</fail>
<copy todir="$${deploy.dir}" file="$${dist.dir}/$${project.name}.ear" />
</target>

! <target name="undeploy" description="Undeploy the example from JBoss">
<delete file="$${deploy.dir}/$${project.name}.ear" />
<delete file="$${deploy.dir}/$${project.name}-dev-ds.xml" />
<delete file="$${deploy.dir}/$${project.name}-prod-ds.xml" />
</target>

<target name="clean" description="Cleans up the build directory">
<delete dir="$${dist.dir}"/>
<delete dir="$${ear.dir}"/>
<delete dir="$${war.dir}"/>
<delete dir="$${jar.dir}"/>
<delete dir="$${src.schema.dir}" failonerror="no"/>
<delete dir="$${basedir}/test-report"/>
--- 236,270 ----
<touch file="$${ear.deploy.dir}/META-INF/application.xml"/>
</target>

! <target name="deploy-tomcat" depends="archive-tomcat" if="is.tomcat">
! <copy tofile="$${deploy.dir}/$${project.name}.war" file="$${dist.dir}/$${project.name}-tomcat.war" />
! </target>
!
! <target name="deploy-jboss" depends="archive,datasource" unless="is.tomcat" description="Deploy to JBoss AS">
<fail unless="jboss.home">jboss.home not set</fail>
<copy todir="$${deploy.dir}" file="$${dist.dir}/$${project.name}.ear" />
</target>

! <target name="deploy" depends="deploy-tomcat,deploy-jboss" />
!
! <target name="undeploy-tomcat" if="is.tomcat">
! <echo>$${deploy.dir}</echo>
! <delete file="$${deploy.dir}/$${project.name}.war" />
! </target>
!
! <target name="undeploy-jboss" unless="is.tomcat" description="Undeploy the example from JBoss">
<delete file="$${deploy.dir}/$${project.name}.ear" />
<delete file="$${deploy.dir}/$${project.name}-dev-ds.xml" />
<delete file="$${deploy.dir}/$${project.name}-prod-ds.xml" />
</target>

+ <target name="undeploy" depends="undeploy-tomcat,undeploy-jboss"/>
+
<target name="clean" description="Cleans up the build directory">
<delete dir="$${dist.dir}"/>
<delete dir="$${ear.dir}"/>
<delete dir="$${war.dir}"/>
+ <delete dir="$${war.dir}.tomcat"/>
<delete dir="$${jar.dir}"/>
<delete dir="$${src.schema.dir}" failonerror="no"/>
<delete dir="$${basedir}/test-report"/>
]]></echo>

<!-- 元のファイルをバックアップ -->
<copy file="${project.home}/build.xml" tofile="${project.home}/build.xml.bak"/>

<!-- build.xmlにpatch -->
<patch patchfile="./tmp/build.xml.patch" originalfile="${project.home}/build.xml"/>

</target>

</project>

あまり情報が無いので試行錯誤でしたが何とかできました。
Seamがバージョンアップしたらこれも修正ですね。

2008年6月10日火曜日

Tomcat上でJBossSeamを動かす

Tomcat上でJBossSeamのサンプルを動かしてみます。

筆者の環境
  • JDK1.6.0_05
  • Tomcat6.0.16
  • Apache Ant1.7.0
  • Maven Ant Tasks2.0.9
  • JBoss Seam2.0.2.SP1
Maven Ant TasksはMaven ProjectからダウンロードしてAntのlibフォルダにコピーしておきます。

手順
※ $SEAM_HOMEはJBossSeamのフォルダ、$CATALINA_HOMEはTomcatのフォルダです。
$ cp $SEAM_HOME/lib/hsqldb.jar $CATALINA_HOME/lib
$ cd $SEAM_HOME/examples/jpa
$ ant tomcat6
$ cp dist-tomcat6/jboss-seam-jpa.war $CATALINA_HOME/webapps
これでサンプルのデプロイ完了です。
Tomcatを起動してhttp://localhost:8080/jboss-seam-jpa/をブラウザで開けばサンプルアプリケーションが表示されます。


Login NameとPasswordはともにdemoでログインできます。


次回は自作のサンプルを動かしてみます。

Tomcat上でEJB3を動かす

Embeddable EJB 3.0を使用してTomcat上でEJB3.0を動かしてみます。

筆者の環境
  • JDK1.6.0_05
  • Tomcat6.0.16
  • Eclipse 3.3.1.1

Embeddable EJB3.0をダウンロードして解凍します。
※Preview RC9はTomcatデプロイ時にエラーとなってしまうためRC8を取得してください。

まずはEmbeddable EJB3.0の動作確認を行います。


はじめにEmbeddable EJBのjarファイルをUser LibraryとしてEclipseに定義してしまいます。



EclipseでDynamic Web Projectを作成します。今回はプロジェクト名をEJB3OnTomcatにしました。
srcにEmbeddable EJB3.0のconfフォルダにあるファイルを全てコピーします。


ProjectのPropertiesのJava Build Pathで、Libraryに先ほど定義したEmbeddable EJBのライブラリを追加します。



さらにJ2EE Module DependenciesでもEmbeddable EJBのライブラリを選択します。



上記の操作が終わったらTomcatにデプロイして起動してみます。
例外が無く正常に起動できればOKです。ちょっと警告が出ますが、とりあえず気にしないでおきます。

次にプログラムを書きます。

まずはEJBのプログラムです。EJBはjarファイルにしてWEB-INF/libに配置する必要があるので、別プロジェクトに作成します。
EclipseでJava Projectを作成します。プロジェクト名はEJB3OnTomcatEJBにしました。
Webプロジェクトと同様に、ProjectのPropertiesのJava Build Pathで、Embeddable EJBのライブラリを追加します。

プロジェクトのsrcディレクトリに以下の4つのファイルを作成します。

META-INF/persistence.xml
永続化定義です。defaultの永続化機構をjava:/DefaultDSにマップします。java:/DefaultDSの定義は、Webプロジェクトのembedded-jboss-beans.xmlに記述してあります。
<?xml version="1.0" encoding="UTF-8"?>
<persistence>
<persistence-unit name="default">
<jta-data-source>java:/DefaultDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>


tekit/ejb3tomcat/ejb/Car.java
Entity Beanです。
package tekit.ejb3tomcat.ejb;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Car {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private Integer price;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
}


tekit/ejb3tomcat/ejb/CarDAO.java
SessionBeanのインターフェイスです。
package tekit.ejb3tomcat.ejb;

import java.util.List;
import javax.ejb.Local;
import javax.ejb.Remote;

@Local
@Remote
public interface CarDAO {
public abstract void create(String name, Integer price);
public abstract List getList();
}


tekit/ejb3tomcat/ejb/CarDAOBean.java
SessionBeanの実装です。
package tekit.ejb3tomcat.ejb;

import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class CarDAOBean implements CarDAO{
@PersistenceContext
private EntityManager em;

public void create(String name, Integer price) {
Car car = new Car();
car.setName(name);
car.setPrice(price);
em.persist(car);
}

public List getList(){
return em.createQuery("select c from Car c").getResultList();
}
}



次にWebのプログラムを書きます。EJBを参照するのでProjectのPreferencesにて、J2EE Module Dependenciesの設定でEJB3OnTomcatEJBプロジェクトを選択します。


EJBを呼び出すロジックはJSPに書いてしまいます。
WebContent/index.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@page import="tekit.ejb3tomcat.ejb.Car"%>
<%@page import="java.util.Hashtable"%>
<%@page import="tekit.ejb3tomcat.ejb.CarDAO"%>
<%@page import="javax.naming.InitialContext"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Tomcat上でEJB3.0を動かすサンプル</title>
</head>
<body>

<%
// EJBを取得する
Hashtable<String, String> props = new Hashtable<String, String>();
props.put("java.naming.factory.initial", "org.jnp.interfaces.LocalOnlyContextFactory");
props.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces");
CarDAO dao = (CarDAO)new InitialContext(props).lookup("CarDAOBean/local");

// 作成ボタンからの遷移ならエンティティを作成する
if("create".equals(request.getParameter("command"))){
dao.create(request.getParameter("name"), new Integer(request.getParameter("price")));
}

// エンティティを列挙する
for(Car car : dao.getList()){
%>
<%= car.getName() %> / <%= car.getPrice() %><br/>
<%
}
%>

<form action="./index.jsp" method="post">
名前<input name="name" type="text"/>
価格<input name="price" type="text"/>
<input name="command" value="create" type="submit" title="作成"/>
</form>

</body>
</html>

以上でプログラムは終了です。


Tomcatにデプロイして実行してみると、こんな感じで動作確認できます。


アルファ版なのですがEntityBeanとSessionBeanを使う分には問題なさそうです。
非力なPCで開発する際にJBossが重い場合は、Tomcat+EJBで開発してみるのも良いかもしれませんね。

次はTomcat上でJBoss Seamを動かしてみます。

2008年6月9日月曜日

Tomcat上でWebServiceを動かす

JBossやGlassFishも良いですが、開発時に使うには重たいです。
そこでTomcat上でWebServiceを使えるようにしてみます。


筆者の環境
  • JDK1.6.0_05
  • Tomcat6.0.16

Tomcat側でWebServiceを提供するためにJAX-WSのRI(リファレンス インプリメンテーション)を使用します。
ダウンロードしたjarを実行すると解凍されます。
$ wget https://jax-ws.dev.java.net/2.1.4/JAXWS2.1.4-20080502.jar
$ java -jar JAXWS2.1.4-20080502.jar

libフォルダにWebServiceの提供に必要なjarファイルが格納されています。

続いてプログラムの作成に進みます。まずはサーバー側です。
作成するのは
  • サービスを提供するクラス
  • web.xmlファイル
  • sun-jaxws.xmlファイル
の3つです。

サービスを提供するクラスです。
package tekit.wstomcat;
import javax.jws.WebService;

@WebService
public class SimpleWS {
public String echo(String message){
return message;
}
}


web.xmlはこんな感じです。サーブレットリスナとサーブレットにJAX-WS RIのクラスを使用しています。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>WebServiceOnTomcat</display-name>
<listener>
<listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
</listener>
<servlet>
<servlet-name>WSServlet</servlet-name>
<servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>WSServlet</servlet-name>
<url-pattern>*.ws</url-pattern>
</servlet-mapping>
</web-app>


sun-jaxws.xmlはこんな感じです。このファイルはJAX-WS RI固有のファイルで、サービスを提供するクラスとそのURLをマップします。
※ ちなみにJBossの場合はsun-jaxws.xmlにあたるファイルは不要です。
<?xml version="1.0" encoding="UTF-8"?>
<endpoints version="2.0" xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime">
<endpoint implementation="tekit.wstomcat.SimpleWS" name="simpleWS" url-pattern="/simpleWS.ws"/>
</endpoints>


これでプログラムは揃ったので、先ほど解凍したJAX-WS RIのlibフォルダにあるjarファイルをWEB-INF/libにコピーし、warファイルを作成してTomcatにデプロイします。

Tomcatを起動してSimpleWSのwsdlを見てみます。
正常に起動できていれば、http://localhost:ポート/Webアプリ名/simpleWS.ws?wsldでwsdlを取得することができます。
URLの/simpleWS.wsの部分はweb.xmlとsun-jaxws.xmlの定義によって決まります。

次にこのWebServiceを呼び出すクライアント側を作成します。
まずはWebServiceを利用するためのクラス群(ポータブルアーティファクトと言うらしい)をwsdlから生成します。
パッケージ名とURLは適当に読み変えてください。
$ $JAVA_HOME/bin/wsimport.exe -keep -p tekit.wstomcat http://localhost:8080/WebServiceOnTomcat/simpleWS.ws?wsdl

コマンドが成功すると、classファイルとjavaファイルが生成されます。
これを使ってWebServiceを呼び出すプログラムを作成します。サーバーとクライアントでエンコーディングを揃えれば日本語も通ります。
import tekit.wstomcat.SimpleWS;
import tekit.wstomcat.SimpleWSService;

public class WSClient {
public static void main(String[] args) {
SimpleWS ws = new SimpleWSService().getSimpleWSPort();
System.out.println(ws.echo("ほげ"));
}
}

簡単ですね。動作も軽いのでストレス無く開発できます。
次回はTomcat上でEJB3を動かしてみようと思います。

2008年6月5日木曜日

Box2Dを試す

Flash Developを使って2D物理ライブラリBox2Dを試してみます。


まずはBox2DFlashAS3のプロジェクトページからライブラリをダウンロードします。
筆者はバージョン2.0.0を取得しました。
$ wget http://downloads.sourceforge.net/box2dflash/Box2DFlashAS3_2.0.0.zip
$ unzip Box2DFlashAS3_2.0.0.zip

ライブラリとして使うのでSWCにしてしまいます。FlashDevelopでのSWC作成方法がわからないのでSDKのコンパイラを使いました。
※HelloWorld.asはコンパイルエラーになってしまうので除外しています。
$ cd Box2DFlashAS3_2.0.0
$ find . -name '*.as' ! -name 'HelloWorld.as' | awk -F '/' '/./{buf="";for(i=2;i<=NF;++i)buf=buf "." $i;print buf}' | sed -e 's/^\.//g' -e 's/\.as$/ /g' | tr -d '\n' | xargs $FLEX_HOME/bin/compc.exe --source-path . -output Box2D.swc -include-classes


FlashDevelopで適当にFlex3プロジェクトを作成します。

プロジェクトフォルダにlibフォルダを作成し、そこに先ほど作成したBox2D.swcをコピーします。
そしてFlash Develop上でBox2D.swcを右クリックしてAdd To Libraryを選択します。

これでBox2Dライブラリが参照され、コードヒントが使えるようになります。




次にBox2DユーザーマニュアルのHello Box2Dを参考にプログラムを作成します。
サンプルは1つのMXMLと1つのASで作成しました。

Main.mxml
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*" layout="vertical">
<local:HelloBox2D out="{hoge}" />
<mx:TextArea id="hoge" width="800" height="600" />
</mx:Application>


HelloBox2D.as
package
{
import Box2D.Collision.b2AABB;
import Box2D.Collision.b2Bound;
import Box2D.Collision.Shapes.b2PolygonDef;
import Box2D.Common.Math.b2Vec2;
import Box2D.Dynamics.b2Body;
import Box2D.Dynamics.b2BodyDef;
import Box2D.Dynamics.b2World;
import flash.events.Event;
import mx.containers.Canvas;
import mx.controls.Alert;
import mx.controls.TextArea;
import mx.core.UIComponent;
import mx.events.FlexEvent;

public class HelloBox2D extends UIComponent
{
public var out:TextArea;

public function HelloBox2D()
{
addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
}

private function onCreationComplete(event:Event):void
{
var worldAAABB:b2AABB = new b2AABB();
worldAAABB.lowerBound.Set(-100.0, -100.0);
worldAAABB.upperBound.Set(100.0, 100.0);

var gravity:b2Vec2 = new b2Vec2(0.0, -10.0);
var doSleep:Boolean = true;
var world:b2World = new b2World(worldAAABB, gravity, doSleep);

var groudBodyDef:b2BodyDef = new b2BodyDef();
groudBodyDef.position.Set(0, -10);

var groudBody:b2Body = world.CreateStaticBody(groudBodyDef);

var groudShapeDef:b2PolygonDef = new b2PolygonDef();
groudShapeDef.SetAsBox(50, 10);

groudBody.CreateShape(groudShapeDef);

var bodyDef:b2BodyDef = new b2BodyDef();
bodyDef.position.Set(0, 4);
var body:b2Body = world.CreateDynamicBody(bodyDef);

var shapeDef:b2PolygonDef = new b2PolygonDef();
shapeDef.SetAsBox(1, 1);
shapeDef.density = 1;
shapeDef.friction = 1;
body.CreateShape(shapeDef);
body.SetMassFromShapes();

var timeStep:Number = 1 / 60;

var iterations:Number = 10;

for (var i:Number = 0; i < 60; ++i)
{
world.Step(timeStep, iterations);
var position:b2Vec2 = body.GetPosition();
var angle:Number = body.GetAngle();
out.text += position.x + "," + position.y + "," + angle + "\n";
}
}
}
}

あとはこれをコンパイルなのですが、Flash DevelopではBox2Dのライブラリを参照できていないようでコンパイルエラーになってしまいます。
仕方ないのでSDKでコンパイルします。
$ $FLEX_HOME/bin/fcsh.exe
(fcsh) mxmlc src/Main.mxml -compiler.include-libraries lib/Box2D.swc

これでコンパイルできました。
Main.swfを実行してみます。

position.yの値が徐々に減っている様子がわかりますね。
Box2Dは2D物理計算だけ行うようですね。図形の描画は自分で実装するようです。
その辺はまた次回にやります。

2008年6月4日水曜日

ブログの投稿を自動化する

PythonとGoogle APIに慣れてきたところで、Bloggerの投稿を自動化してみようと思います。
投稿内容を書いたテキストファイルを準備してコマンドを実行すれば投稿が実行されるようにしました。

#!/usr/bin/python
import getpass
import os
import re
import sys
import atom
import gdata.service

# validating arguments
if len(sys.argv) < 6:
print 'usage:%s email blog_name title label file [encoding]' % sys.argv[0]
print '"label" is a commma separated label list'
print '"encoding" is encoding of console. (ex. Shift_JIS, utf-8, ...)'
print 'file encoding must be utf-8.'
exit()

email = sys.argv[1]
blog_name = sys.argv[2]
title = sys.argv[3]
label = sys.argv[4]
article = sys.argv[5]
if len(sys.argv) > 6:
encoding = sys.argv[6]
title = unicode(title, encoding)
title = title.encode('utf-8')

if not os.path.exists(article):
print '"%s" is missing.' % article
exit()

if not os.path.isfile(article):
print '"%s" is not a file.' % article
exit()

contents = open(article, 'r').read()
password = getpass.getpass()

# authentication
blogger_service = gdata.service.GDataService(email, password)
blogger_service.service = 'blogger'
blogger_service.server = 'www.blogger.com'
blogger_service.ProgrammaticLogin()

# retrieving blog id
query = gdata.service.Query()
query.feed = '/feeds/default/blogs'
feed = blogger_service.Get(query.ToUri())
blog_id = None
for entry in feed.entry:
if entry.title.text == blog_name:
blog_id = entry.GetSelfLink().href.split('/')[-1]
if blog_id == None:
print 'Blog "%s" does not exist.' % blog_name
exit()

# post blog
entry = gdata.GDataEntry()
if len(label) != 0:
entry.category.append(atom.Category(label, 'http://www.blogger.com/atom/ns#'))
entry.title = atom.Title(title_type='xhtml', text=title)
entry.content = atom.Content(content_type='html', text=contents)
blog_entry = blogger_service.Post(entry, '/feeds/%s/posts/default' % blog_id)

使い方は以下のようにします。
$ ./blogger-post.py foo.bar@gmail.com 'hoge blog' 'post title' 'Label1, Label2' post.txt Shift_JIS
引数は下記の通りです。
  1. GMailのアドレス(パスワードはコマンド実行後のプロンプトで入力します)
  2. ブログのタイトル
  3. 投稿記事のタイトル
  4. 投稿記事のラベル(複数指定する場合はカンマ区切り、省略する場合は空文字にします)
  5. 投稿記事が書かれたテキストファイル
  6. コンソールのエンコーディング(utf-8の場合は省略可能です)

FlashDevelopを使う

Adobe Flex Builder 3の評価期限が切れてしまいました。
ライセンスを購入してもらう予定なのですが、まだ先になりそうです。それまでエディタで開発するのも何なので、FlashDevelopを使ってみようと思います。

Flash Developのページからインストーラを入手します。
Releasesフォーラムにて取得できます。筆者は3.0.0 Beta7をダウンロードしました。


インストーラを起動して普通にインストールします。


インストール終了後、Flash Developを起動します。
筆者はFlex 3で開発を行うので、Flash DevelopにFlex 3の設定を行います。
Flex 3 SDKはあらかじめダウンロードして任意の場所に解凍しておきます。
Flash Developのメニューバーから「Tools」ー「Program Settings...」を選択します。


左側のPluginsからAs3Contextを選択し、右側のFlex SDK LocationにFlex SDKを解凍したディレクトリを設定します。


次に新規プロジェクトを作成します。
メニューバーから「Project」ー「New Project...」を選択します。


Installed TemplatesからFlex 3 Projectを選択します。
Name,Locationを適当に入力してOKを押します。


「Hello, World!」を表示するサンプルを動作させてみます。
Main.mxmlを開いて、以下のように編集します。
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Label text="Hello, World!"/>
</mx:Application>



実行させます。
たぶんメニューバーの「Project」ー「Test Movie」から実行できるはずなのですが、うまく動作しません。
代わりにメニューアイコンの「Build Current File」を押します。


しばらくするとビルドが完了してプレビューが表示されます。

2008年6月3日火曜日

ブログに複数の画像を簡単に投稿する(その2)

前回の続きです。
画像を一括アップロードして画像のHTMLを生成するPythonスクリプトを作成しました。

#!/usr/bin/python
import getpass
import os
import re
import sys
import gdata.photos.service

# validating arguments
if len(sys.argv) < 3:
print 'usage:%s email photo_dir' % (sys.argv[0])
exit()

email = sys.argv[1]
photo_dir = sys.argv[2]

if not os.path.exists(photo_dir):
print 'Directory "%s" is missing.' % (photo_dir)
exit()

if not os.path.isdir(photo_dir):
print '"%s" is not a directory.' % (photo_dir)
exit()

album_name = os.path.basename(os.path.abspath(photo_dir))
password = getpass.getpass()

# authentication
gd_client = gdata.photos.service.PhotosService()
gd_client.email = email
gd_client.password = password
gd_client.ProgrammaticLogin()

# creating new album
album_entry = gd_client.InsertAlbum(title=album_name, summary='Blogger resources', access='private')
album_url = '/data/feed/api/user/%s/album/%s' % (email, album_entry.name.text)

# adding photos
for photo in os.listdir(photo_dir):
photo_path = '%s/%s' % (photo_dir, photo)
if re.search(r'\.jpe?g', photo_path.lower()) != None:
print 'Uploading "%s"...' % (photo_path)
photo_entry = gd_client.InsertPhotoSimple(album_url, 'Blogger resource', 'Uploaded using the API', photo_path, content_type='image/jpeg')

# generate html
album_feed = gd_client.GetFeed('%s?kind=photo' % (album_url))
for photo in album_feed.entry:
photo_url = photo.GetMediaURL()
print '<span><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="%s"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px;" src="%s" border="0" alt="" /></a>\n</span>' % (photo_url, photo_url)

スクリプトの引数はGmailアドレスとアップロードする画像が入っているディレクトリの2つです。
パスワードはスクリプト実行後に標準入力から読み込みます。
Googleの認証が成功したら、引数のディレクトリの名称でPicasaウェブアルバムに新しいアルバムを作成します。
そしてそのディレクトリにあるJPEGファイルをすべてアップロードします。
JPEGファイルかどうかは拡張子で判定しています。
アップロードが終了したらそれらの画像をBloggerに貼り付けるためのHTMLを標準出力に出力します。

ブログに複数の画像を簡単に投稿する(その1)

Bloggerを始めるにあたって、複数の画像を使って簡単に書き込みできる方法を考えてみました。


画像を縦に並べて、その横に説明文をつらつら書いていくようなレイアウトにしたいのですが、Bloggerの機能を使って1ファイルずつ画像をアップしていくのは面倒です。

いろいろと試行錯誤した結果、
  1. Picasaに画像を一括アップロード
  2. アップロードした画像のURLを取得
  3. 画像のURLからBloggerに投稿するHTMLを生成

という手順を自動化できれば投稿が簡単になりそうです。
画像のHTML生成まで自動化できれば、あとは文章を書き込むだけです。

ということで、これらの処理をGoogle APIを使って実装してみます。
Picasa ウェブ アルバム データ APIというものです。
http://code.google.com/intl/ja/apis/picasaweb/overview.html

言語はJava、.NET、PHP、Pythonが使えるようです。今回は使ったことの無いPythonで試してみることにします。

まずはPython環境の構築です。
Getting Started with the Google Data Python Libraryを参考にしました。

はじめにPythonのインストールですが私の環境はCygwinなのでCygwinのインストーラでインストールすることができました。バージョンは2.5.2です。

次に必要な外部モジュールの確認を行います。
pythonインタプリタを実行します。
$ python

以下を入力してエラーが無ければOKです。
>> from xml.etree import ElementTree


次にPicasa ウェブ アルバム データ APIを利用するためにGoogle Data Python Libraryをインストールします。
$ wget http://gdata-python-client.googlecode.com/files/gdata.py-1.0.13.tar.gz
$ tar xf gdata.py-1.0.13.tar.gz
$ cd gdata.py-1.0.13
$ ./setup.py install


最後にモジュールのインストール確認を行います。
$ ./tests/run_data_tests.py

これでエラーが無ければOKです。

次回はPythonでGoogle APIを使ってみます。