2012年6月6日水曜日

JBoss Forgeを試してみた

ForgeはJava EE6を利用したWebアプリケーション(のひな形)の作成 〜 アプリケーションサーバへのディプロイまで可能なコマンドラインツールです。作成されるアプリケーションはMavenプロジェクトのため、そのままIDE等で開くことができます。

また、プラグインによりサポートするフレームワークやAPサーバが追加できします。自分でプラグインを作成することもできます。

今回はForgeでJPAとJSFを使ったサンプルアプリケーションを作ってみます。

まず、Forgeをダウンロードして展開します。

$ https://repository.jboss.org/nexus/service/local/artifact/maven/redirect?r=releases&g=org.jboss.forge&a=forge-distribution&v=1.0.5.Final&e=zip
$ unzip Downloads/forge-distribution-1.0.5.Final.zip 

環境変数を設定します。

export FORGE_HOME=~/forge-distribution-1.0.5.Final/
export PATH=$PATH:$FORGE_HOME/bin

forgeコマンドでforgeのシェルを起動します。

list-commandで利用可能なコマンドを確認できます。–allを付けないと現在のコンテキストで利用可能なコマンドのみ表示されます。OSのコマンドやgit、mavenもforgeから呼び出すことができます。

$ forge
    _____                    
   |  ___|__  _ __ __ _  ___ 
   | |_ / _ \| `__/ _` |/ _ \  \\
   |  _| (_) | | | (_| |  __/  //
   |_|  \___/|_|  \__, |\___| 
                   |___/      
 
[no project] kenichiro22 $ list-commands --all 

[FILE & RESOURCES]
cat*                                    cd*
edit*                                   find*
fingerprint*                            grep*
list-web-resources*                     ls
ls*                                     mkdir*
mv*                                     open*
pick-up*                                pwd*
pwr*                                    rm*
wc*                                     

[OTHER]
alias*                                  beans list-alternatives*
beans list-decorators*                  beans list-interceptors*
beans new-bean*                         beans new-conversation
beans setup*                            constraint AssertFalse
constraint AssertTrue                   constraint DecimalMax
constraint DecimalMin                   constraint Digits
constraint Future                       constraint Max
constraint Min                          constraint NotNull
constraint Null                         constraint Past
constraint Pattern                      constraint Size
constraint Valid                        ejb setup*
entity*                                 faces new-view*
faces project-stage*                    faces setup*
faces*                                  field
field boolean                           field custom
field int                               field long
field manyToMany                        field manyToOne
field number                            field oneToMany
field oneToOne                          field string
field temporal                          git clone*
git git-checkout*                       git setup*
i18n add-locale*                        i18n faces-setup*
i18n get*                               i18n put*
i18n remove*                            i18n setup*
java list-imports                       java new-class*
java new-enum-const                     java new-enum-type*
java new-field                          java new-method
java*                                   jms setup*
jstl setup*                             jta setup*
persistence setup*                      persistence*
plugins new-plugin*                     plugins setup*
remove-constraint                       rest endpoint-from-entity*
rest setup*                             servlet setup*
servlet*                                soap setup*
unalias*                                validation setup*

[PROJECT]
build*                                  execute-java*
maven remove-parent*                    maven set-artifactid*
maven set-groupid*                      maven set-parent*
maven set-version*                      mvn*
new-project*                            project add-dependency*
project add-known-plugin-repository*    project add-known-repository*
project add-managed-dependency*         project add-plugin-repository*
project add-repository*                 project find-dependency*
project find-managed-dependency*        project install-facet*
project list-dependencies*              project list-facets*
project list-managed-dependencies*      project list-plugin-repositories*
project list-properties*                project list-repositories*
project remove-dependency*              project remove-facet*
project remove-managed-dependency*      project remove-plugin-repository*
project remove-property*                project remove-repository*
project set-property*                   project*
setup*                                  shade exclude*
shade include*                          shade make-executable*
shade relocate*                         shade remove*
shade reset*                            shade setup*
test*                                   

[SHELL ENVIRONMENT]
about*                                  clear*
echo*                                   exec*
exit*                                   forge find-plugin*
forge git-plugin*                       forge install-plugin*
forge list-plugins*                     forge remove-plugin*
forge restart*                          forge source-plugin*
forge*                                  help*
less*                                   list-commands*
list-config*                            list-properties*
more*                                   reset*
run*                                    run-url*
set*                                    version*
wait*                                   

[UI GENERATION & SCAFFOLDING]
list-scaffold-providers*                scaffold from-entity*
scaffold indexes*                       scaffold setup*
scaffold templates*                     

[VERSION CONTROL]
git*                                    

(* = command accessible from current context)

プロジェクトを作成します。

[no project] kenichiro22 $ new-project --named try-forge --topLevelPackage com.azuki3.forge --projectFolder workspace/try-forge
***SUCCESS*** Created project [try-forge] in new working directory [/Users/kenichiro22/workspace/try-forge]
scaffoldの設定を行います。JSFを使います。
[try-forge] try-forge $ scaffold setup --scaffoldType faces
 ? Scaffold provider [faces] is not installed. Install it? [Y/n] Y
 ? Facet [forge.maven.WebResourceFacet] requires packaging type(s) [war], but is currently [jar]. Update packaging? (Note: this could deactivate other plugins in your project.) [Y/n] Y
***SUCCESS*** Installed [forge.maven.WebResourceFacet] successfully.

JPAの設定を行います。 JPAプロバイダーとコンテナを指定します。JPAプロバイダーはHibernateをコンテナはJBoss AS7を指定してみます。

[try-forge] try-forge $ persistence setup --provider HIBERNATE --container JBOSS_AS7
***INFO*** Setting transaction-type="JTA"
***INFO*** Using example data source [java:jboss/datasources/ExampleDS]
 ? The JPA provider [HIBERNATE], also supplies extended APIs. Install these as well? [y/N] 
***SUCCESS*** Persistence (JPA) is installed.

ここから実際のアプリケーションを作成します。

まず、Entityを作成します。今回はリレーションを試してみたいので、User - Blog - Tagという3つのEntityを作成します。BlogとUserがManyToOneでBlogとTagがManyToManyです。

[try-forge] try-forge $ entity --named User
 ? In which package you'd like to create this @Entity, or enter for default [com.azuki3.forge.model] 
Created @Entity [com.azuki3.forge.model.User]
Picked up type : com.azuki3.forge.model.User

[try-forge] User.java $ field string --named name
Added field to com.azuki3.forge.model.User: @Column private String name;

[try-forge] User.java $ field string --named email
Added field to com.azuki3.forge.model.User: @Column private String email;

[try-forge] User.java $ entity --named Blog
Created @Entity [com.azuki3.forge.model.Blog]
Picked up type : com.azuki3.forge.model.Blog

[try-forge] Blog.java $ field string --named title
Added field to com.azuki3.forge.model.Blog: @Column private String title;

[try-forge] Blog.java $ field string --named content
Added field to com.azuki3.forge.model.Blog: @Column private String content;

[try-forge] Blog.java $ field temporal --named createdAt --type TIMESTAMP 
Added field to com.azuki3.forge.model.Blog: private @Temporal(TemporalType.TIMESTAMP) Date createdAt;

[try-forge] Blog.java $ field manyToOne --named user --fieldType com.azuki3.forge.model.User

[try-forge] Blog.java $ entity --named Tag
Created @Entity [com.azuki3.forge.model.Tag]
Picked up type : com.azuki3.forge.model.Tag

[try-forge] Tag.java $ field string --named name
Added field to com.azuki3.forge.model.Tag: @Column private String name;

[try-forge] User.java $ field manyToMany --named tag --fieldType com.azuki3.forge.model.Tag.java

Blogにバリデーションを追加します。

[try-forge] Blog.java $ validation setup --provider JAVA_EE
***SUCCESS*** Installed [forge.spec.validation] successfully.

[try-forge] Blog.java $ constraint NotNull --onProperty title 
Constraint NotNull has been successfully added on property named 'title'

[try-forge] Blog.java $ constraint NotNull --onProperty content 
Constraint NotNull has been successfully added on property named 'content'

[try-forge] Blog.java $ constraint Size --onProperty title --min 1 --max 256
Constraint Size has been successfully added on property named 'title'

[try-forge] Blog.java $ constraint Size --onProperty content --min 1 --max 256
Constraint Size has been successfully added on property named 'content'

[try-forge] Blog.java $ constraint NotNull --onProperty user 
Constraint NotNull has been successfully added on property named 'user'

EntityからJSFのscaffoldを作成します。

[try-forge] try-forge $ scaffold from-entity com.azuki3.forge.model.Blog.java ***INFO*** Using currently installed scaffold [faces]
 ? [/Users/kenichiro22/workspace/try-forge/src/main/webapp/resources/scaffold/pageTemplate.xhtml] File exists, overwrite? [Y/n] 
***SUCCESS*** Generated UI for [com.azuki3.forge.model.Blog]

ビルドしてJBoss AS7にディプロイします。JBoss AS7は別途起動しておきます。

[try-forge] try-forge $ build 
[try-forge] try-forge $ as7 deploy

こんな感じの画面ができます。ManyToOne, ManyToManyの関連も自動作成されます。ただ、バリデーションは動作しませんでした。もともとサポートされていないのか、私の使い方が悪いのかはわかりません。。

これくらいできれば、PlayやGrailsのscaffoldと同じくらいのことはできるでしょうか。

ついでにREST APIも作成します。

[try-forge] try-forge $ rest setup --activatorType APP_CLASS 
 ? What root path do you want to use for your resources? [/rest] 
 ? In what package do you want to store the Application class? com.azuki3.forge.
rest
 ? How do you want to name the Application class? [RestApplication] 
***SUCCESS*** Installed [forge.spec.jaxrs.applicationclass] successfully.
***SUCCESS*** Rest Web Services (JAX-RS) is installed.

[try-forge] try-forge $ rest endpoint-from-entity com.azuki3.forge.model.Blog.java
***SUCCESS*** Generated REST endpoint for [com.azuki3.forge.model.Blog]
他にもi18n用のファイルの作成や、JAX-RSからEJBまでなどひと通りのJavaEE用のコマンドがあるようです。

今回使ってみた感想ですが、、コマンドの補完機能を備えているので非常に使いやすいです。Scaffoldまで使わなくても、Java EE6をサポートしたコンテナを使い場合でもpom.xmlに各APIのライブラリを追加するのは結構面倒なので、それだけでも助かります。プラグインがどんどん増えてJava EE6以外のライブラリも簡単に使えるようになるといいですね。プラグインは、"find-plugin"コマンドで検索できるようなのですが、どこかで一覧でも見られるページはないのでしょうか。

あとscaffoldのテンプレートにTwitter bootstrapが使えたらいいですね。さらに、JSFの他にGWTでCRUDが作れたりすると助かるのですが。GWT用プラグインもあるので、どんなものかはそのうち試してみたいと思います。

今回のサンプルアプリケーションのコードは以下にあります。
https://github.com/kenichiro22/try-forge-scaffold

2012年6月1日金曜日

JBoss Erraiを試してみた

JBoss Erraiは、GWTをベースに次のような機能を追加した、RIA開発向けのフレームワークです。最近2.0がリリースされました。

  • 独自の非同期メッセージング(Errai bus)
  • RPC(not GWT-RPC)
  • JAX-RS対応
  • Beanのマーシャリング
  • クライアントでのDIとCDI(JSR-299/JSR-330)のサポート(一部)
  • CDIベースののクライアント・サーバ間のイベント機構

GWTをベースにしていますが、GWTの一部やGINを置き換えとなる多くの機能があります。

個人的に気になっているのはJAX-RS対応で、クライアントから直接サーバのJAX-RSのインタフェースを呼び出すことができます。また、Beanのマーシャリング機能を備えるため、GWT-RPCと同様にクライアント・サーバで同一のモデルを使用することができます。

GWTののRequestFactoryやAutoBeanではモデルに対してもインタフェースを定義する必要があり煩雑なので、GWT-RPCの大体として有望ではないでしょうか。ただ、同一のモデルを使用するため、GWTをの制約はかかってきますが。

また、JAX-RSのサービスを利用する場合、RequestBuilderとJavaScriptObjectを使用する必要がありましたが、Erraiを使うとサーバのJAX-RSインタフェースがそのまま使えるのでかなり楽になりそうです。

ErraiはManve archetypeがあるので(Errai Quickstart guide)、サンプルとしてJAX-RSを使ったプロジェクトを作ってみました。

mvn archetype:generate \
-DarchetypeGroupId=org.jboss.errai.archetypes \
-DarchetypeArtifactId=jaxrs-quickstart \
-DarchetypeVersion=2.0.0.Final \
-DarchetypeRepository=https://repository.jboss.org/nexus/content/groups/public/

JAX-RSのサービスインタフェースとして以下のようなクラスが作成されます。

@Path("customers")
public interface CustomerService {
  @GET
  @Produces("application/json")
  public List listAllCustomers();

  @POST
  @Consumes("application/json")
  @Produces("text/plain")
  public long createCustomer(Customer customer);

  @PUT
  @Path("/{id}")
  @Consumes("application/json")
  @Produces("application/json")
  public Customer updateCustomer(@PathParam("id") long id, Customer customer);

  @DELETE
  @Path("/{id}")
  public void deleteCustomer(@PathParam("id") long id);

  @GET
  @Path("/{id}")
  @Produces("application/json")
  public Customer retrieveCustomerById(@PathParam("id") long id);
}

JAX-RSのアノテーションのみでErraiのためのコードはありません。実装クラスの方は省略します。

JAX-RSでやり取りされるモデルクラスは以下のようになります。

@Portable
@Entity
public class Customer implements Serializable, Comparable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    private long id;

    @NotNull
    @Size(min = 1)
    private String firstName;

    @NotNull
    @Size(min = 1)
    private String lastName;

    @NotNull
    @Size(min = 1)
    private String postalCode;

    private Date lastChanged;

    public Customer() {
    }

    // setter/getter ...
}

@Portableというアノテーションがあるクラスがマーシャリングの対象になります。アノテーションを付けられないようなケースやマーシャリング方法を変更したい場合の対応方法も用意されています。

実際にJAX-RSにアクセスするクライアントのコードは以下のようになります。

@EntryPoint
public class App {

    @Inject
    private Caller customerService;
    
    // ….
    
    final RemoteCallback creationCallback = new RemoteCallback() {
        @Override
        public void callback(Long id) {
            // レスポンスの処理
            // ...
        }
    };
    
    @PostConstruct
    public void init() {
        final Button create = new Button("Create", new ClickHandler() {
            @Override
            public void onClick(ClickEvent clickEvent) {
                Customer customer = new Customer(custFirstName.getText(), custLastName.getText(), custPostalCode.getText());
                customerService.call(creationCallback).createCustomer(customer);
            }
        });
        
    // …..
    }
}

サービスの呼び出しは非同期処理のためCallerというProxyを経由します。Proxyのcallメソッドに、レスポンスを処理するRemoteCallbackを渡します(サービスの戻り値が渡されます)。customerServiceは@Injectアノテーションがついているので、DI対象になります。

GWTのEntryPointは@EntryPointアノテーションで指定します。initメソッドには@PostConstructアノテーションが付いているので、インスタンスが生成されDIが行われた後に呼び出されます。ここでUIの初期化も行います。

ちょっと見た感じかなりよさそうなErraiですが、以下は2.0では提供されていないようです。MVPについてはGWTの標準もしくは、GWTPと組み合わせることになりそうです。どちらもGINを使用しているので、次はDIにErraiを使うように変更できるか試してみたいと思います。

  • MVPパターンのサポート
  • Widget類