WildFly: JBoss Forgeによるアプリケーション開発
環境
- Mac OSX 10.9
- Eclipse Luna
- Red Hat JBoss Developer Studio (Luna) 8.0.0.Beta3
- Eclipse Market Placeからインストール
プロジェクト作成
Forge Consoleを開きます
- Show View → Forge Console
- 緑の▶ボタンを押して起動
- Forge 2.7.2.Finalを起動 (1.4.4.Finalではない)
新規プロジェクトを作成します
- プロジェクト名: forge-proj
- パッケージ名: com.mythosil.forge
[workspace]$ project-new --named forge-proj --topLevelPackage com.mythosil.forge --type war ***SUCCESS*** Project named 'forge-proj' has been created. [forge-proj]$ ls pom.xml src target
forgeのプロンプトがディレクトリ名(=プロジェクト名)になっています
エンティティ作成
- エンティティクラス名: Person
[forge-proj]$ jpa-new-entity --named Person ***SUCCESS*** Persistence (JPA) is installed. ***SUCCESS*** Entity com.mythosil.forge.model.Person created [Person.java]$ ls [fields] id::java.lang.Long version::int [methods] equals(java.lang.Object)::boolean getId()::java.lang.Long getVersion()::int hashCode()::int setId(java.lang.Long)::void setVersion(int)::void toString()::java.lang.String
jap-new-entityコマンドでは、エンティティクラスの作成だけでなく、persistence.xmlの作成やpom.xmlの修正(依存追加)を行ってくれます
Java EEのバージョンは6.0、JPAのバージョンは2.0で作成されます
(Java EE 7に変更したければ手作業で書き換えるしかない?)
<dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.spec</groupId> <artifactId>jboss-javaee-6.0</artifactId> <version>3.0.2.Final</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.0-api</artifactId> <scope>provided</scope> </dependency> </dependencies>
データソースとしてはH2が利用されます
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> <persistence-unit name="forge-proj-persistence-unit" transaction-type="JTA"> <description>Forge Persistence Unit</description> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties> <property name="hibernate.hbm2ddl.auto" value="create-drop"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.transaction.flush_before_completion" value="true"/> <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/> </properties> </persistence-unit> </persistence>
エンティティにフィールド追加
- フィールド名: name
- 型: String
- 長さ: 50
[Person.java]$ jpa-new-field --named name --type String --length 50 ***SUCCESS*** Field name created
RESTエンドポイント作成
Personエンティティを元に作成してみます
(Eclipse上で実行するとエラーが出る場合は、CLIで試してみて下さい)
[Person.java]$ rest-generate-endpoints-from-entities --targets com.mythosil.forge.model.Person ***SUCCESS*** JAX-RS has been installed. ***SUCCESS*** Endpoint created
この際、RESTエンドポイントクラス作成と同時に、Personクラスに@XmlRootElementアノテーションが付加されます
また、エンティティ作成時と同様、pom.xmlが自動で修正されます
<dependency> <groupId>org.jboss.spec.javax.servlet</groupId> <artifactId>jboss-servlet-api_3.0_spec</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.spec.javax.ws.rs</groupId> <artifactId>jboss-jaxrs-api_1.1_spec</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.spec.javax.ejb</groupId> <artifactId>jboss-ejb-api_3.1_spec</artifactId> <scope>provided</scope> </dependency>
ビルド
[PersonEndpoint.java]$ build ***SUCCESS*** Build Success
デプロイ
jboss-cli.shを利用してデプロイします
(wildflyは事前に起動しておく)
$ cd /path/to/forge-proj $ $JBOSS_HOME/bin/jboss-cli.sh -c --command="deploy target/forge-proj-1.0.0-SNAPSHOT.war"
実行
$ curl -XPOST 'http://localhost:8080/forge-proj-1.0.0-SNAPSHOT/rest/people' \ -d '{"name":"my name"}' \ -H "Content-Type: application/json" $ curl 'http://localhost:8080/forge-proj-1.0.0-SNAPSHOT/rest/people/1' {"id":1,"version":0,"name":"my name"}
まとめ
参考
WildFly: Arquillianを利用したテスト
WildFly+Arquillianでテストを実行してみたログ
環境
プロジェクト新規作成
- 新規プロジェクト → Maven → Webアプリケーション
- プロジェクト名: arq-base
- パッケージ: com.mythosil.arqbase
- サーバ: WildFly
- Java EE バージョン: Java EE 7 Web
テスト対象クラスの用意
まずはシンプルなセッションBeanをテスト対象クラスとして用意します
- 新規 → セッションBean
- EJB名: SimpleBean
- 場所: ソース・パッケージ
- パッケージ: com.mythosil.arqbase
- セッションのタイプ: ステートレス
package com.mythosil.arqbase; import javax.ejb.Stateless; @Stateless public class SimpleBean { public String hello(String name) { return String.format("Hello, %s", name); } }
pom.xmlの用意
- repositories
- dependencyManagement
- WildFlyとArquillianのBOMを追加
- dependencies
- profile
- managed版とremote版をそれぞれ用意
- arquillianの依存もそれぞれに合わせた物を依存に追加
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mythosil</groupId> <artifactId>arq-base</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>arq-base</name> <properties> <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.jboss.spec.javax.ejb</groupId> <artifactId>jboss-ejb-api_3.2_spec</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.jboss.arquillian.junit</groupId> <artifactId>arquillian-junit-container</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.jboss.arquillian.protocol</groupId> <artifactId>arquillian-protocol-servlet</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.wildfly.bom</groupId> <artifactId>jboss-javaee-7.0-with-all</artifactId> <version>8.1.0.Final</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.jboss.arquillian</groupId> <artifactId>arquillian-bom</artifactId> <version>1.1.5.Final</version> <scope>import</scope> <type>pom</type> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.7</source> <target>1.7</target> <compilerArguments> <endorseddirs>${endorsed.dir}</endorseddirs> </compilerArguments> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.3</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.6</version> <executions> <execution> <phase>validate</phase> <goals> <goal>copy</goal> </goals> <configuration> <outputDirectory>${endorsed.dir}</outputDirectory> <silent>true</silent> <artifactItems> <artifactItem> <groupId>javax</groupId> <artifactId>javaee-endorsed-api</artifactId> <version>7.0</version> <type>jar</type> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> </plugins> </build> <profiles> <profile> <id>arq-wildfly-managed</id> <dependencies> <dependency> <groupId>org.wildfly</groupId> <artifactId>wildfly-arquillian-container-managed</artifactId> <scope>test</scope> </dependency> </dependencies> </profile> <profile> <id>arq-wildfly-remote</id> <dependencies> <dependency> <groupId>org.wildfly</groupId> <artifactId>wildfly-arquillian-container-remote</artifactId> <scope>test</scope> </dependency> </dependencies> </profile> </profiles> <repositories> <repository> <id>JBoss Repository</id> <url>https://repository.jboss.org/nexus/content/groups/public/</url> </repository> </repositories> </project>
テストの用意
- 新規 → JUnitテスト
- クラス名: SimpleBeanTest
- 場所: テスト・パッケージ
- パッケージ: com.mythosil.arqbase
- RunWith
- Arquillian.classを指定
- テスト対象クラス
- アーカイブ作成
package com.mythosil.arqbase; import javax.ejb.EJB; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.WebArchive; import static org.junit.Assert.*; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(Arquillian.class) public class SimpleBeanTest { @EJB SimpleBean target; @Test public void testHello() { String actual = target.hello("myname"); assertEquals("Hello, myname", actual); } @Deployment public static Archive<?> createTestArchive() { return ShrinkWrap.create(WebArchive.class) .addClass(SimpleBean.class); } }
arquillian.xmlの用意
<?xml version="1.0" encoding="UTF-8"?> <arquillian xmlns="http://jboss.org/schema/arquillian" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd"> <container qualifier="wildfly-managed" default="true"> <configuration> <property name="jbossHome">WildFlyのインストール先</property> </configuration> </container> <container qualifier="wildfly-remote" default="false"> <configuration> <property name="managementPort">9990</property> <property name="managementAddress">WildFlyサーバのアドレス</property> <property name="username">ユーザ名</property> <property name="password">パスワード</property> </configuration> </container> </arquillian>
テスト実行
# managed版の実行 $ mvn clean test -Parq-wildfly-managed ...(中略)... Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 20.780 s [INFO] Finished at: 2014-09-04T01:04:17+09:00 [INFO] Final Memory: 29M/221M [INFO] ------------------------------------------------------------------------ # remote版の実行 $ mvn clean test -Parq-wildfly-remote
テスト対象エンティティクラスの用意
エンティティクラスもテストしてみます
DBにはH2(インメモリ)を利用します
- 新規 → エンティティクラス
package com.mythosil.arqbase; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Person implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; /* Setter, Getter */ }
pom.xml修正
<dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.1-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.spec.javax.transaction</groupId> <artifactId>jboss-transaction-api_1.2_spec</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>javax.enterprise</groupId> <artifactId>cdi-api</artifactId> <scope>provided</scope> </dependency>
テスト用datasource作成
<?xml version="1.0" encoding="UTF-8"?> <datasources xmlns="http://www.jboss.org/ironjacamar/schema"> <datasource jndi-name="java:jboss/datasources/test" pool-name="TestDS" enabled="true" use-java-context="true"> <connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</connection-url> <driver>h2</driver> <security> <user-name>sa</user-name> <password>sa</password> </security> </datasource> </datasources>
テストの用意
- 新規 → JUnitテスト
- クラス名: PersonTest
- 場所: テスト・パッケージ
- パッケージ: com.mythosil.arqbase
package com.mythosil.arqbase; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.transaction.UserTransaction; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.EmptyAsset; import org.jboss.shrinkwrap.api.spec.WebArchive; import static org.junit.Assert.*; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(Arquillian.class) public class PersonTest { @PersistenceContext EntityManager em; @Inject UserTransaction tx; @Test public void testPersist() throws Exception { Person target = new Person(); target.setName("myname"); tx.begin(); em.joinTransaction(); em.persist(target); tx.commit(); em.clear(); Person p = em.find(Person.class, 1L); assertEquals("myname", p.getName()); } @Deployment public static Archive<?> createTestArchive() { return ShrinkWrap.create(WebArchive.class) .addClass(Person.class) .addAsResource("META-INF/persistence.xml") .addAsWebInfResource("WEB-INF/test-ds.xml") .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); } }
テスト実行
$ mvn clean test -Parq-wildfly-managed
WildFly: ポート番号の変更方法
ポートを変更する方法を何種類か、備忘録
デフォルト
- HTTPポート: 8080
- 管理ポート: 9990
起動時にポート番号指定
- HTTPポート: 18080
- 管理ポート: 19990
$ ./standalone.sh \ -Djboss.http.port=18080 \ -Djboss.management.http.port=19990
起動時にオフセット値指定
オフセット値を指定すると、HTTPポート、管理ポートなど、全てのポート番号がオフセット値ぶんだけ加算されて起動する
同一マシンでWildFlyを複数立ち上げようとするとポートの衝突が問題になるが、オフセットを利用することで、全ポート番号を指定する必要がなくなる
$ ./standalone.sh -Djboss.socket.binding.port-offset=100
ちなみに、ポート番号指定も同時にすると、オフセット値は無視される(ポート番号指定されていないものだけにオフセット値が適用される)
standalone.xmlを直接編集
変数の部分にダイレクトに数値を埋め込む
書き方を真似すれば、デフォルトのポート番号を変更して、起動時にさらに変更可能にしておくこともできる
<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}"> <socket-binding name="management-http" interface="management" port="${jboss.management.http.port:9990}"/> <socket-binding name="management-https" interface="management" port="${jboss.management.https.port:9993}"/> <socket-binding name="ajp" port="${jboss.ajp.port:8009}"/> <socket-binding name="http" port="${jboss.http.port:8080}"/> <socket-binding name="https" port="${jboss.https.port:8443}"/> <socket-binding name="txn-recovery-environment" port="4712"/> <socket-binding name="txn-status-manager" port="4713"/> <outbound-socket-binding name="mail-smtp"> <remote-destination host="localhost" port="25"/> </outbound-socket-binding> </socket-binding-group>
CLIツールで編集
write-attributeコマンドを利用してポート番号を変更(standalone.xmlが書き変わる)
reloadコマンドを実行するとWildFlyが新しいポートで再起動する
$ ./jboss-cli.sh -c [standalone@localhost:9990 /] /socket-binding-group=standard-sockets/socket-binding=http:write-attribute(name="port",value="18080") { "outcome" => "success", "response-headers" => { "operation-requires-reload" => true, "process-state" => "reload-required" } } [standalone@localhost:9990 /] /socket-binding-group=standard-sockets/socket-binding=management-http:write-attribute(name="port",value="19990") { "outcome" => "success", "response-headers" => { "operation-requires-reload" => true, "process-state" => "reload-required" } } [standalone@localhost:9990 /] :reload { "outcome" => "success", "result" => undefined }