2014年5月15日木曜日

Spring Batch AdminのMySQL設定

バッチ実行にSpring Batchを利用しようかと検討しています。

jBatchがJavaEEに組み込まれたこともあり、
それに準拠した形でバッチを作ったほうがなにかと恩恵があるのかなと思ったというだけですが。

で、Spring BatchにはSpring Batch AdminというWebUIがあるので
使ってみます。あくまで、実行、停止、実行履歴が見れる程度のものです。
標準のdatabaseがhsqldbとなっているのでMySQLに変更します。

Adminのテンプレートでは
META-INF/batch-hsql.properties
というファイルがあるので次のファイルに変更します。


META-INF/batch-mysql.properties:



batch.jdbc.driver=com.mysql.jdbc.Driver
batch.jdbc.url=jdbc:mysql://localhost/test
batch.jdbc.user=user

batch.jdbc.password=
batch.jdbc.testWhileIdle=true
batch.jdbc.validationQuery=SELECT 1
batch.jdbc.maxActive=70
batch.jdbc.initialSize=20
batch.schema.script=classpath:/org/springframework/batch/core/schema-mysql.sql
batch.drop.script=classpath*:/org/springframework/batch/core/schema-drop-mysql.sql
batch.business.schema.script=classpath:/business-schema-mysql.sql
batch.database.incrementer.class=org.springframework.jdbc.support.incrementer.MySQLMaxValueIncrementer

# Non-platform dependent settings that you might like to change
batch.data.source.init=false




batch.data.source.init=falseはdatabase初期化するかという設定です。
一番最初に起動するときのみtrueにする必要があります。

このままだと、遅いので dataSourceをoverrideします。
次のファイルを作成します。


META-INF/spring/batch/override/war-content.xml :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/batch
        http://www.springframework.org/schema/batch/spring-batch-2.0.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">


<bean id=”dataSource” class=”org.apache.commons.dbcp.BasicDataSource”>
          <property name=”driverClassName” value=”${batch.jdbc.driver}” />
          <property name=”url” value=”${batch.jdbc.url}” />
          <property name=”username” value=”${batch.jdbc.user}” />
          <property name=”password” value=”${batch.jdbc.password}” />

          <property name=”initialSize” value=”${batch.jdbc.initialSize}”/>
          <property name=”maxActive” value=”${batch.jdbc.maxActive}”/>

</bean>
</beans>




実行時に引数を渡す必要があります。
 -DENVIRONMENT=mysql

なのでmaven でデバッグするときは 次のようになる
jetty:run  -Dmaven.surefire.debug test -DENVIRONMENT=mysql



せっかくなのでスケジュールに機能もつけてみました→

2014年5月13日火曜日

Jetty 9.1.* の設定について

jetty 9.1.*系以降jettyの一部がモジュール化され
設定項目体系が変わっています

設定方法についてをすこしまとめておきます。


1.httpsを有効化

ダウンロードしたアーカイブのdemo-base以下にあるstart.d/ssl.ini、https.iniが参考になります
demo-baseのstart.d/ssl.ini、https.iniを start.dにコピペして
環境に合わせて修正する


2. jetty-envを有効化

そのままでOKただし、pom.xmlにjetty関連のdependancyがある場合
loaderエラーになる可能性があるのでjetty pluginでのみ必要な物は
jetty plugin 以下のdependancyに移す


3. jspを有効化

start.iniの--module=jspのコメントを外す


4. servletを有効化

defaultでservletsモジュールは読み込まれないので
jetty-servlets を使う場合は、
start.iniに--module=servletsを追加

 
5. リクエストログの有効化

start.iniに--module=requestlogを追加


等々

9.1.4はログ出力のリンク回りがバグっているのかjetty.shが動かない場合があるので注意
多分9.1.5で修正ずか?


2014年4月17日木曜日

UUID <-> Base64 変換する Jackson用wrapperクラス

データベースでUUIDを何かと使うのですが、
JSONで出力すると'afb055a0-c18f-11e3-8b27-b1398a321474'みないな
文字列になってちょっと長いですし、
UUIDだというのがバレバレでそこはかとなく心配になります。

なので、JSONマッパーに渡すUUIDをwrapしてBase64としてマップするクラスを作ったので共有
表題にある通りJackson向けです


@JsonValueアノテーションをつけることで
このクラスをシリアライズする際にtoString()が呼ばれBase64化された値を返します

デシリアライズ 時には、
UUIDBase64(String uuidBase64)で初期化されUUIDBase64クラスとしてマップされます。


UUIDBase64.java :

import java.nio.ByteBuffer;
import java.util.UUID;

import org.apache.commons.codec.binary.Base64;
import org.codehaus.jackson.annotate.JsonValue;
import org.codehaus.jackson.annotate.JsonIgnore;

public class UUIDBase64 {
   
    @JsonIgnore
    private final UUID uuid;

    public UUIDBase64(UUID uuid) {
        this.uuid = uuid;
    }

    public UUIDBase64(String uuidBase64) {
        byte[] bytes = Base64.decodeBase64(uuidBase64);
        ByteBuffer bb = ByteBuffer.wrap(bytes);
        this.uuid = new UUID(bb.getLong(), bb.getLong());
    }
   
    @JsonValue
    public String toString(){
        if (uuid == null)
            return null;
       
        ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
        bb.putLong(uuid.getMostSignificantBits());
        bb.putLong(uuid.getLeastSignificantBits());           
        return Base64.encodeBase64URLSafeString(bb.array());
    }
   
    public UUID getUUID() {
        return this.uuid;
    }
   
    public static UUIDBase64 fromUUID(UUID uuid) {
        return new UUIDBase64(uuid);
    }
   
    public static UUIDBase64 fromBase64(String str) {
        return new UUIDBase64(str);
    }

}


次のように変換されます

class User {
    UUIDBase64 id;
}

<-> { "id" : "base64の文字列" }

2014年4月11日金曜日

HttpServletRequestのthread内共有 Spring編

HttpServletRequestを参照しようとした時に
引数で渡さずに、static fieldのthread localに保存して
スレッド内から参照できるようにする方法を昔紹介していましたが、
Spring使用時には標準の機能としてすでにあります。


次のようにRequestContextHolderからHttpServletRequestを取得します。

        RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = 

(HttpServletRequest)attrs.resolveReference(RequestAttributes.REFERENCE_REQUEST);


web.xmlにlistenerを登録する必要があります。

web.xml:

    <listener>
      <listener-class>

     org.springframework.web.context.request.RequestContextListener
   </listener-class>
    </listener>



Spring 2.5以上が必要です
使い道としては、エラー出力にリクエスト情報を出すとか
エラーハンドルのためにいちいち、
リクエストを引数には渡せないのでthreadに保持しているものを参照します。


ThreadLocalに自分で保存する記事よりは
既存のものを使うべきでしょう

2014年3月28日金曜日

Spring Security Auto Login

Spring Security を利用してログインセッション管理を
している方も多いと思います。

Spring Securityデフォルトのままで使うと
アカウント作成後に一度ユーザにログインしてもらわないといけないとか
イケてないので、そういう時に自動でログインセッションを作成する方法です


アカウント作成後に次のようなメソッドを呼びます
AuthenticationProvideのBeanを指定します(自作クラスでも動きます)

    @Autowired
    AuthenticationProvider authenticationProvider;



HttpServletRequestを予め取得しておいて、username, passwordから
tokenを発行し、authenticationProviderに登録します

    private void doAutoLogin(String username, String password,
            HttpServletRequest request) {

        try {
            // Must be called from request filtered by Spring Security,
            // otherwise SecurityContextHolder is not updated
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
                    username, password);
            token.setDetails(new WebAuthenticationDetails(request));
            Authentication authentication = this.authenticationProvider
                    .authenticate(token);
            // logger.debug("Logging in with [{}]",
            // authentication.getPrincipal());
            SecurityContextHolder.getContext()
                    .setAuthentication(authentication);
        } catch (Exception e) {
            SecurityContextHolder.getContext().setAuthentication(null);
            logger.error("Failure in autoLogin", e);
        }

    }


AutowiredしなくてもSecurityContextHolderからいけるかも
しれないですね。そのうちやってみよ。

2014年3月25日火曜日

Session Counter Listener

セッション数をカウントするServlet コンテナ用のListenerを自作したので共有
サービスのセッション数 を監視してーという人はどうぞ
thread safeなはず。 Java1.5以上必要です


SessionCounterListener.java:

public class SessionCounterListener implements HttpSessionListener {
    private static AtomicInteger totalActiveSessions = new AtomicInteger();

    public static int getTotalActiveSession() {
        return totalActiveSessions.get();
    }

    @Override
    public void sessionCreated(HttpSessionEvent arg0) {
        totalActiveSessions.incrementAndGet();
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent arg0) {
        totalActiveSessions.decrementAndGet();
    }
}


web.xmlにはListenerとして登録

web.xml :

 <listener>
        <listener-class>my.package.SessionCounterListener</listener-class>
</listener>


   

Cassandra3.x CQLでBlobを扱う

CassandraでのBlobの扱いに半日ぐらい使ったので覚書


Cassandra3.0、datastax driver 2.0
でCQLを利用してBlobを扱う場合、
bindにByteBufferをかまさないといけません。
それと、row.getBytes()で取得したByteBufferをそのままarray()しても
ゴミの入ったbyte[]になってしまいます。

なので、次のようにする
bind時
byte[] byteArray;
prepare(cql).bind(ByteBuffer.wrap(byteArray));

取得時
import com.datastax.driver.core.utils.Bytes;
byte[] byteArray = Bytes.getArray(row.getBytes("bytes"));