Hatena::ブログ(Diary)

裏紙

 

2012-12-31

Thymeleaf + JAX-RS + DomaをGlassFishで試してみる

| 23:30


というわけでやってみました。

とは言え、Thymeleaf + JAX-RSとJAX-RS + Domaをやれば良いのでそれぞれ見ていきます。

Thymeleaf + JAX-RS

以前やったときはJAX-RSのリソースメソッドでテンプレートのパスとバインドするパラメータをまとめたオブジェクトを返してました。

例えば、こんな感じです。

    @GET
    public ThymeleafTemplate sayHello(@QueryParam("yourName") String yourName) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("message", "Hello, " + yourName + "!");
        return new ThymeleafTemplate("hello.html", variables);
    }

ThymeleafTemplateというぁゃιぃクラスをnewしてたり、テンプレートにバインドする変数名がマップのキーなので文字列で設定してたり、あんまり嬉しくないなー、とか思ったり。

POJOを返すようにしておけばJacksonかましてJSONを返すように簡単に出来たりテストやりやすかったりすると思うので、今回は@Templateというアノテーションを作ってそれでテンプレートのパスを設定するようにしてみました。

リソースメソッドはこんな感じになりました。

    @GET
    @Template("hello.html")
    public HelloResponse sayHello(@QueryParam("yourName") String yourName) {
        HelloResponse response = new HelloResponse();
        response.setMessage("Hello, " + yourName + "!");
        return response;
    }

MessageBodyWriter#writeToではリフレクション使ってプロパティ名をキーにしてマップに突っ込んでからテンプレートに渡しています。リソースメソッドの戻り値がStringや基本型のラッパー、コレクションの場合は固定でvalueというキーでマップに突っ込むなどすると上記サンプルコードのように余計なクラスを作らなくてすむので良いかもしれませんね。というかどっちかっつーとMessageBodyWriterの実装の方が大事なのにコードを載せていないという。詳しくはThymeleafMessageBodyWriterを見てくださいませ。

JAX-RS + Domaそのいち

シンプルに普通にDaoImplをnewしても良いかもですね。

@Path("hello")
public class Hello {

    private MessageDao messageDao = new MessageDaoImpl();

    ...
}

Configはこんな感じ。DataSourceはJNDIルックアップ。

public class DomaConfig extends DomaAbstractConfig {

    private static Dialect dialect = new StandardDialect();

    @Override
    public DataSource getDataSource() {
        try {
            return InitialContext.doLookup("jdbc/__default");
        } catch (NamingException ex) {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public Dialect getDialect() {
        return dialect;
    }
}

ただこの方法だとDaoをモックにすげ替えて雲丹っとテスト回したりするのがちょい面倒です。Java EE 6ならCDIで接着する方が嬉しそうですね。

JAX-RS + Domaそのに(CDIで接着バージョン)

DataSource、Dialectのプロデューサーを作成します。DataSourceのプロデューサーはこんな感じ。

package sample;

import javax.annotation.Resource;
import javax.enterprise.inject.Produces;
import javax.sql.DataSource;

public class DataSourceProducer {

    @Resource(name = "jdbc/__default")
    @Produces
    DataSource dataSource;

}

適当にクラス作ってフィールド定義して@Producesで注釈するだけです。JAX-RSのアノテーションにも@Produces(javax.ws.rs.Produces)があるけどそっちじゃないのでパッケージ名に注意です。

次に、Config実装クラスはJNDIルックアップしていましたが、フィールドを定義して@Injectでインジェクトします。

package sample;

import javax.inject.Inject;
import javax.sql.DataSource;
import org.seasar.doma.jdbc.DomaAbstractConfig;
import org.seasar.doma.jdbc.dialect.Dialect;

public class DomaConfig extends DomaAbstractConfig {

    @Inject
    private DataSource dataSource;

    @Inject
    private Dialect dialect;

    @Override
    public DataSource getDataSource() {
        return dataSource;
    }

    @Override
    public Dialect getDialect() {
        return dialect;
    }
}

DAOクラスは@DaoでConfig実装クラスを指定するのではなくコンストラクタインジェクションされるようにします。コンストラクタインジェクションするにはコンストラクタを@Injectで注釈する必要があります。DomaではDao実装クラスへの注釈を@AnnotateWithで設定することができます。Doma素晴らしいですね。

package sample;

import javax.inject.Inject;
import org.seasar.doma.AnnotateWith;
import org.seasar.doma.Annotation;
import org.seasar.doma.AnnotationTarget;
import org.seasar.doma.Dao;
import org.seasar.doma.Select;

@Dao
@AnnotateWith(annotations = {
    @Annotation(type = Inject.class, target = AnnotationTarget.CONSTRUCTOR)
})
public interface MessageDao {

    @Select
    Message select(Long id);
}

あとはリソースクラスでDaoを@Injectでインジェクトすればおk。

package sample;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;

@Path("hello")
@LocalBean
@Stateless
public class Hello {

    @Inject
    private MessageDao messageDao;

    ...
}

@Statelessで注釈してSession Beanにするとリソースメソッドがトランザクション境界になってSAStrutsにお世話になってたひと(つまり私)は幸せです。あとno-interfaceなので@LocalBeanで注釈しています。

まとめ

  • Thymeleaf良いですね
  • JAX-RS良いですね
  • EJB 3.1良いですね
  • Doma良いですね
  • CDI良いですね
  • GlassFish良いですね
  • Arquillian良いですね
  • Guice良いですね

大晦日に何やってんねん。

こーど

2012-12-14

GlassFishのカスタムレルムを作る

| 00:00

GlassFishにはデフォでもJDBCレルムとかLDAPレルムとかありますけど、オレオレ認証したいんじゃー、ってときにはカスタムレルムですよ!


AppservPasswordLoginModuleのサブクラスを作る

FQDNはcom.sun.appserv.security.AppservPasswordLoginModuleです。extendsしてauthenticateUser()メソッドを実装しましょう。例えばこんな感じ。

package example;

import com.sun.appserv.security.AppservPasswordLoginModule;
import javax.security.auth.login.LoginException;

public class ExampleLoginModule extends AppservPasswordLoginModule {

    @Override
    protected void authenticateUser() throws LoginException {
        ExampleRealm realm = (ExampleRealm) getCurrentRealm();
        String[] groups = realm.authenticate(_username, _passwd);
        if (groups == null) {
            throw new LoginException("ログイン失敗><");
        }
        commitUserAuthentication(groups);
    }
}

ユーザ名やパスワードはAppservPasswordLoginModuleに定義されているprotectedフィールドから取得できます。realmに処理を投げて、戻りがnullなら認証失敗とみなします。認証に成功したらcommitUserAuthentication(groups)を忘れず呼びましょー。


AppservRealmのサブクラスを作る

FQDNはcom.sun.appserv.security.AppservRealmです。extendsしてgetAuthType()メソッドとgetGroupNames(String)メソッドを実装します。こんな感じ。

package example;

import com.sun.appserv.security.AppservRealm;
import com.sun.enterprise.security.auth.realm.BadRealmException;
import com.sun.enterprise.security.auth.realm.InvalidOperationException;
import com.sun.enterprise.security.auth.realm.NoSuchRealmException;
import com.sun.enterprise.security.auth.realm.NoSuchUserException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Properties;

public class ExampleRealm extends AppservRealm {

    @Override
    protected void init(Properties props) throws BadRealmException, NoSuchRealmException {
        String jaasContext = props.getProperty(JAAS_CONTEXT_PARAM);
        setProperty(JAAS_CONTEXT_PARAM, jaasContext);
    }

    @Override
    public String getAuthType() {
        return "example";
    }

    @Override
    public Enumeration getGroupNames(String username) throws InvalidOperationException, NoSuchUserException {
        return Collections.enumeration(Arrays.asList(findGroups(username)));
    }

    String[] authenticate(String username, char[] password) {
        if (validate(username, password) == false) {
            return null;
        }
        return findGroups(username);
    }

    private boolean validate(String username, char[] password) {
        return "hoge".equals(username) && Arrays.equals("fuga".toCharArray(), password);
    }

    private String[] findGroups(String username) {
        return new String[]{"foo", "bar", "baz"};
    }
}

init(Properties)はスーパークラスで実装されていますが、上記例のようにJAASコンテキストをカスタムRealmクラス自身にsetPropertyする必要があります。わざわざ引数のPropertiesから取得しなくても固定値を突っ込んでも良いでしょうし、getJAASContext()をオーバーライドしても良いかも知れませんが(スーパークラスのgetJAASContextでgetProperty(JAAS_CONTEXT)して得た値を返してる)。まあでもinitでゴニョるのが楽っぽいです。


ドメインディレクトリに突っ込む

ふたつのクラスを実装したらJARにしてGlassFishのドメインのlibディレクトリに突っ込みます。


ログイン構成ファイルを編集する

GlassFishのドメインのconfig/login.confを編集します。他のレルムの設定をパクってこんな感じのを追加します。

exampleRealm {
    example.ExampleLoginModule required;
};

GlassFishを起動して認証レルムの設定を行う

こんな感じ。

f:id:backpaper0:20121214235037p:image

あとはこの認証レルムを使用するアプリケーションをデプロイするだけです。


本日のpom.xml

<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>net.hogedriven.backpaper0</groupId>
  <artifactId>glassfish-custom-realm-example</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>

  <dependencies>
    <dependency>
      <groupId>org.glassfish.main.security</groupId>
      <artifactId>security</artifactId>
      <version>3.1.2.2</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
</project>

参考リソース

2011-09-03

関西GlassFish勉強会、中止しました

| 00:21

本日開催予定だった関西GlassFish勉強会ですが、台風の影響が大きいと判断して中止致しました。


楽しみにして下さっていた皆様やスピーカーの吉田さんと寺田さん、会場を押さえてくれていた奥さん、申し訳ありませんでした。

いつになるかは分かりませんが、いずれリベンジしたいと考えています。その際はまた皆様にお付き合い頂けると嬉しいです。

あわせてよみたい

2011-07-26

【緩募】関西GlassFish(JavaEE6)勉強会に強力してくれる方

| 23:52

どうも。#kancon2011GlassFish勉強会やろうず!と言っていたうらがみです。

というわけでやろうず!

まず初回はGlassFish入門的な内容にしたいと考えています。それとJavaEE6入門っぽいセッションもあれば良いなぁと思っています。

私ひとりでは力不足なので次のような内容で話してくださるスピーカーさんを緩募します。(もちろん、言い出しっぺの法則、という事で私も話します(・∀・)b)

  • GlassFishはインストール簡単&GUI管理コンソール便利
  • JavaEE6がいかに簡単開発か
  • 自己増殖クラスタリングすげー

その他、GlassFish入門、JavaEE6入門的なお話であれば上記テーマにこだわりませんのでご協力頂けるとありがたいです。御連絡は backpaper0@gmail.com またはTwitterで @ まで宜しくお願いします。

なお、開催日は9月3日(土)、場所は株式会社クロノス様の研修ルームを予定しております(@さん、いつもありがとう)。

ATND立てました。


最初に“初回は”と書いた通り、二回三回と続けていけるような有意義なものにしたいと思っています。至らない点も多いと思いますが、私と一緒にこのイベントを作って下さる方をお待ちしております。

2011-05-25

GlassFish v3.1で組込み用のAPIが変わった?

| 02:23

どうも! ぼけーっとウェブ徘徊してたらGlassFishRuntimeとかいうクラスをハヶ━m9( ゚д゚)っ━ン!!しました。どうやら組込みGlassFishのためのAPIっぽいのですが、v3.0.1のときは無かったように思います。

私が今まで使ってた組込み用のAPIはこんな感じでした。

GlassFishRuntimeを使うと下記のような感じっぽいです。

package samples.javaee;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import java.io.File;
import java.io.IOException;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import javax.transaction.UserTransaction;
import javax.ws.rs.core.MediaType;

import org.glassfish.embeddable.GlassFish;
import org.glassfish.embeddable.GlassFishProperties;
import org.glassfish.embeddable.GlassFishRuntime;
import org.junit.Test;

import com.sun.jersey.api.client.Client;

/**
 * pom.xmlへの記述例。
 * 
 * <pre>
 * {@code
 * <repository>
 *  <id>glassfish</id>
 *  <url>http://download.java.net/maven/glassfish</url>
 * </repository>
 * (略)
 * <dependency>
 *  <groupId>org.glassfish.extras</groupId>
 *  <artifactId>glassfish-embedded-all</artifactId>
 *  <version>3.1</version>
 * </dependency>
 * }
 * </pre>
 * 
 */
public class GlassFishEmbeddedSample {

    @Test
    public void test() throws Exception {
        GlassFishRuntime gfr = GlassFishRuntime.bootstrap();
        GlassFishProperties props = new GlassFishProperties();
        props.setPort("http-listener", 8080);

        // GlassFishのインスタンス作ってサーバ起動?
        GlassFish gf = gfr.newGlassFish(props);
        gf.start();

        // JDBCコネクションプールとJDBCリソース作成
        gf.getCommandRunner().run(
            "create-jdbc-connection-pool",
            "--datasourceclassname",
            "org.apache.derby.jdbc.EmbeddedDataSource40",
            "--property",
            "user=sa:password=pwd:databaseName=gftest",
            "gftest");

        gf.getCommandRunner().run(
            "create-jdbc-resource",
            "--connectionpoolid",
            "gftest",
            "jdbc/gftest");

        // デ プ ロ イ (`・ω・´)シャキーン
        gf.getDeployer().deploy(
            new File("src/main/webapp"),
            "--contextroot",
            "sample",
            "--name",
            "sampleapp");

        // 動作の確認はJerseyのクライアントでやってみた
        Client client = Client.create();

        String servletResponse = client
            .resource("http://localhost:8080/sample/hello")
            .queryParam("name", "world")
            .get(String.class);

        assertThat(servletResponse, is("Hello, world!"));

        gf.stop();
        gfr.shutdown();
    }

    /**
     * サーブレットはアノテーションで定義しました。web.xml要らないです。
     * 
     */
    @WebServlet(urlPatterns = "/hello")
    public static class HelloServlet extends HttpServlet {

        @EJB
        HelloService service;

        @Resource
        UserTransaction userTransaction;

        @Resource(name = "jdbc/gftest")
        DataSource dataSource;

        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
                throws ServletException, IOException {
            // UserTransactionもDataSourceも取って来れてる様子
            System.out.println(userTransaction);
            System.out.println(dataSource);

            resp.setContentType(MediaType.TEXT_PLAIN);
            String name = req.getParameter("name");
            if (name == null) {
                name = "Guest";
            }
            String said = service.say(name);
            resp.getWriter().print(said);
        }
    }

    /**
     * EJBもフツーに使えるみたいです。お手軽。
     * 
     */
    @Stateless
    public static class HelloService {

        public String say(String name) {
            return "Hello, " + name + "!";
        }
    }
}

個人的にはGlassFishRuntimeの方がさくっと書けて好きです。JDBCリソース作るところとかかなりasadminコマンドに近いし。

楽しいのでもっと遊ぼうと思います。GlassFish v3.1いいよ! JavaEE6良いよ!