Hatena::ブログ(Diary)

裏紙

 

2013-01-12

ブログをhttp://backpaper0.github.com/にするます

23:54

“たぶんブログを移行するけどしばらくはこっちにも記事へのリンクを含んだエントリを投稿しようと思います。”などと書いておりましたが、移行しちゃいます。Tinkerer好きになっちゃいました。

2013-01-06

Welcome to Tinkerer

22:15


たぶんブログを移行するけどしばらくはこっちにも記事へのリンクを含んだエントリを投稿しようと思います。

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-30

2012年を振り返る

00:44

春頃

鍋駆動 テスト冬の陣 〜テイスティング駆動モグモグさせろください〜を開催。相変わらずタイトルが酷い←

鹿駆動勉強会を開催(一応中の人)。TL上の会いたいと思っていた人達にお会い出来て嬉しかった。あと奈良公園の公衆トイレは鹿が入ってこないよう入り口に可動式の柵みたいなのがあった。

結婚式。従妹の子供がリングピローを運んでくれたり、同期(男)が号泣してくれたり、二次会に呼んだhoge駆動メンバーがTLで会話してたり、楽しくて嬉しくて感動した。二次会に来てくれたhogeメンのみんな、本当にありがとう!コミュ症治せよ!←

夏頃

なぜかプロジェクターがあるいろふ宅にてClassLoader駆動 15の昼を開催した。ClassLoaderを触った記憶が無い。たんごさんは終始カレー作ってた。めっちゃ美味しかったのでまた食べたい。よろしく!

神戸らへんでScalaを開催した。昼食をCafe Scalaで頂いた。「もう今日の目標は5割達成しましたね」料理もおいしかったしな! 冗談抜きで本編も楽しかったのでまたやりたい。というかやっていなくてすみません。

秋頃

新婚旅行でイタリアに行ってきた。ジョジョ5部とARIA(ただしベネチアのみ)に思いを馳せたりしつつ楽しんだ。あとスカラ座見たときは自分の中のプログラマがテンション上がった。

やきに駆動 2.0 〜日本よ、これがHoge駆動だ〜を開催した。相変わらずタイトルが酷い。HTTPサーバに滅びのHTTPメソッド、BALSを追加したという発表とデモを行った。簡易で低機能でははあるがHTTPサーバを書くのは楽しかった。

冬頃

社内でJenkinsを布教。まだまだ成功とは言えないが最初の一歩は踏み出せたと思う。来年、継続する。

大都会の合同勉強会と忘年会に参加。きちんとした発表が続いていた中、BALSサーバの再演をした。正直ビビっていたけれど、ウケて良かった。それにしてもあの瀬戸内コミュニティの結びつきの強さは見習うべきものがあると思う。楽しかったので可能であれば来年もお邪魔したいです。

先日忘年会駆動 2012を開催した。そういえばさっきから「開催した」と書いているがhoge駆動勉強会は全員が開催者兼、参加者兼、発表者ですのでそこんとこよろしく! 暗号について簡単に話した。次回はホームズの発表をしたい。IT関係無いけど問題ない。そう、hoge駆動ならね。初めましてのひとも何人かいらっしゃった。くっくっく……hogeの輪が順調に広がっておる(黒幕)。

まとめ

素敵な一年でした。TL、勉強会、会社、プライベートで関わったすべての方々、本当にありがとうございました! 来年もよろしくお願い致します!

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>

参考リソース