Rhythm & Biology

Engineering, Science, et al.

WildFly: JBoss Forgeによるアプリケーション開発

環境

プロジェクト作成

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"}

まとめ

  • 開発のベースはforgeで作ると楽
  • Forge Consoleが遅い・不安定
    • 正直、まともに動かない
    • Eclipse上ではなくCLIであれば普通に動く

参考

WildFly: Arquillianを利用したテスト

WildFly+Arquillianでテストを実行してみたログ

環境

プロジェクト新規作成

  1. 新規プロジェクト → Maven → Webアプリケーション
  2. プロジェクト名: arq-base
  3. パッケージ: com.mythosil.arqbase
  4. サーバ: WildFly
  5. 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
    • EJBJUnit、Arquillianを依存に追加
  • 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を指定
  • テスト対象クラス
  • アーカイブ作成
    • Deploymentアノテーションを利用
    • ShrinkWrapクラスを利用し、テスト時にWildFlyにデプロイするアーカイブを生成
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ドキュメント
    • ファイル名: arquillian
    • フォルダ: src/test/resources/META-INF
<?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(インメモリ)を利用します

  • 新規 → エンティティクラス
    • クラス名: Person
    • 場所: ソース・パッケージ
    • パッケージ: com.mythosil.arqbase
    • 主キー型: Long
    • 持続性ユニット名: arqbasePU
    • 永続性プロバイダ: Hibernate (JPA2.1)
    • データ・ソース: java:jboss/datasources/test
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作成

  • テスト用DBとしてH2(インメモリ)を利用
  • 新規 → XMLドキュメント
    • ファイル名: test-ds
    • フォルダ: src/test/resources/WEB-INF
<?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
}