ラベル Java の投稿を表示しています。 すべての投稿を表示
ラベル Java の投稿を表示しています。 すべての投稿を表示

2015年3月13日金曜日

グラフDB(Neo4j)を試してみる (2)BluePrintsで読み書き

BluePrints

各グラフDBに対するインタフェース、APIを提供するソフトウェア。
複数のグラフDBに対応している。

ライブラリ追加

blueprints-core と好きなGraphDB用のライブラリを追加する。
今回はNeo4jを選んだので blueprints-neo4j-graph を追加。
手元でちょうどPlay Framework触ってたのでビルドツールはSBT。
libraryDependencies ++= Seq(
  "com.tinkerpop.blueprints" % "blueprints-core" % "2.6.0",
  "com.tinkerpop.blueprints" % "blueprints-neo4j-graph" % "2.6.0"
)

DB初期化〜読み書き

DBの初期化からデータの追加、検索を行うとこんな感じになる。
package app;
 
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Graph;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.blueprints.impls.neo4j.Neo4jGraph;
 
public class GraphDB {
 
 public static void main(String[] args) {

  /**
   * init
   * 使用するグラフDB用のディレクトリを指定する。
   * このタイミングでロックファイルを作成し、排他制御をかける。
   * ディレクトリが存在しない場合は初期化してくれる。
   */
  Graph g = new Neo4jGraph("data/graphdb");
  
  addData(g);
  showData(g);

  /**
   * close
   * プログラムを終了する時は必ずshutdown。
   * ロックの解放とか諸々やってるっぽい。
   */
  g.shutdown();
 }
 
 /**
  * Add Node and Edge
  * @param g
  */
 public static void addData(Graph g) {
  /**
   * insert
   * Vertex(頂点、Node)の追加+属性の追加。
   */
  Vertex v1 = g.addVertex(1);
  v1.setProperty("name", "Apache");
  
  Vertex v2 = g.addVertex(2);  
  v2.setProperty("name", "Nginx");
  
  /**
   * insert
   * Edge(辺、Link)の追加+属性の追加。
   */
  Edge e1 = g.addEdge(1, v1, v2, "similar");
  e1.setProperty("since", 2006);
 }
 
 /**
  * show graph data
  * @param g
  */
 public static void showData(Graph g) {
  /**
   * select
   * とりあえず全部取得して表示。
   */
  for (Vertex v : g.getVertices()) {
   System.out.println("Vertex id = " + v.getId().toString()
     + "  property 'name' = " + v.getProperty("name"));
  }
 
  for (Edge e : g.getEdges()) {
   System.out.println("Edge id = " + e.getId().toString()
     + "  property 'since' = " + e.getProperty("since"));
  }
 }
}
Vertex id = 1 property 'name' = Apache Vertex id = 2 property 'name' = Nginx Edge id = 0 property 'since' = 2006

初見の印象

とりあえず触ってみたところ、特に良いなと思ったのが下記。
  • サーバ構築不要
  • 移植性が高い
BruePrintsにはNeo4jのライブラリが同梱されているらしく、DBとはいうもののサーバ構築やサービス起動などの一般的なミドルウェア追加時の作業を行わなくても上記のコードだけで扱うことができる。

もちろん、サーバとして起動して外部からのアクセスを待ち受けたりもできるとのこと。

また、移植性も高い。
new Neo4jGraph(“data/graphdb”) で指定したディレクトリの移動だけで移植が完了する模様。

グラフDBにはスケールしづらいという弱点があるらしいが、その分これだけ軽量になっているのは魅力的。





2015年3月12日木曜日

グラフDB(Neo4j)を試してみる (1)グラフDBとは

会社の勉強会でグラフDBが紹介されて興味を持ったので実践してみた。
全然知らない領域だったのでいろいろ整理したい。

グラフDBとは

まずグラフDBとは何か。

グラフ理論をベースとし、ノード(頂点)とエッジ(関係)を構成要素とするNoSQLデータベースの一つ。関係性を表現が目的のため、その探索が用意。また、ノード・エッジはプロパティ(属性)を持つことができる。

とりあえずこんな理解。

一般的な「グラフ」とは意味合いが違うけれども、計算機科学や数学では「グラフ」といえばこちらを指すことが多いのだろうか。ちなみにWikipediaのグラフ理論がだいぶ参考になった。
グラフ理論(グラフりろん、graph theory)は、数学の一分野。ノード節点頂点[1])の集合とエッジ[2])の集合で構成されるグラフの性質について研究する学問である。なお「エッジ」をリンク[3]という場合もある。コンピュータのデータ構造アルゴリズムなどに広く応用されている。  
Wikipedia グラフ理論
このグラフ構造はRDBでも実現できるが、リレーションを辿る=結合の繰り返しになるのでクエリが複雑になったりパフォーマンスがひどいものになったりする。FacebookやTwitterに代表されるようなリレーションをコアとするアプリケーションでのデータ管理や表現への注目の高まり、

Neo4j

試してみたグラフDBはNeo4j。この他にもいろいろあるらしいけど未知。
グラフデータベース、NOSQL、Neo4jを見ると非OSSが多い印象。

TinkerPop

グラフDBを利用する為のフレームワーク、製品群。
ややキモいキャラクターが各プロダクトのトレードマークっぽい。
TinkerPop - http://goo.gl/zpCCC

今回はこのうちBluePrintsとGremlinを使ってグラフDBへアクセス、読み書きを行ってみた。

長くなりそうなのでコードは次の記事で書く。




2015年3月1日日曜日

[Play Framework] 多対多で自己再帰の関連を持つテーブルのEntity

多対多(Many To Many)で自己再帰的(self recursive)な関係をEbean の Model で表現するのに苦戦した。
あまり良い例ではないけど、
person には似てる人(similarPerson)が複数いる、という設定で書いてみた。
person と person の多対多を解消する中間テーブルは similar_person として定義してみる。
@Entity
public class Person {
    @Id
    public Long id;

    @ManyToMany
    @JoinTable(name = "similar_person",
            joinColumns = @JoinColumn(name = "person_id", referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name = "similar_person_id", referencedColumnName = "id"))
    public List similarPersons;
}
ポイントは @JoinTable の引数の joinColumns と inverseJoinColumns 。
最初これらを書かずにいたら、similar_person テーブルで列名 person_id が重複しています と怒られた。

考えてみたらどちらも外部参照先 (person.id) は同じなんだから、列名指定せずに自動生成したらそうなるよな…。

上記のクラスを定義した上で Evolution を実行すると、下記SQL が自動生成される。
# --- Created by Ebean DDL
# To stop Ebean DDL generation, remove this comment and start using Evolutions

# --- !Ups

create table similar_person (
  person_id                     bigint not null,
  similar_person_id             bigint not null,
  constraint pk_similar_person primary key (person_id, similar_person_id))
;

alter table similar_person add constraint fk_similar_person_person_01 foreign key (person_id) references person (id) on delete restrict on update restrict;

alter table similar_person add constraint fk_similar_person_person_02 foreign key (similar_person_id) references person (id) on delete restrict on update restrict;

# --- !Downs

SET REFERENTIAL_INTEGRITY FALSE;

drop table if exists similar_person;

JPA をちゃんと理解してなかったのでけっこうハマった。

【参考】

JPA – Recursive ManyToMany-Relationship | stuetzpunkt -


2015年2月4日水曜日

[Java][Excel][JETT] Excel帳票出力にJETTを試してみる

シフト表作成アプリ作る中で、Excel帳票を出力したくなったので軽くライブラリについて調べて見る。

調べる前に、以前に使ったことあるやつ

POI …以前に実務で帳票ガリガリ書いてたけど、かなりプリミティブで結構辛かった。なので生で使うのは本当に必要な時だけにしたい。
Fisshplate …テンプレートを使ってみて、やっぱりコレぐらいサクサク書けない と困ると実感した。ただ、今回出力したい xlsx 形式に対応していない(対応予定なし?)のでスルー。

Java × Excel だと POI の話題ばかり

Java8 / play framework でやっており、とりあえず最近だとどんなライブラリがあるのかと思い調べてみると、POI の話題ばかり。

どのライブラリも POI ベースでそれをラップするのが基本ぽいのに、もしかして皆 POI で頑張ってるのか…??

そんな中 Excel方眼紙を支える技術 - 新・たけぞう瀕死の日記 がかなり参考になった。

JETT を試して見る

なんとなく先端ぽく、↓の比較も面白かったので今回は帳票出力用ライブラリ JETT を試してみることにした。
本家サイトの jXLS との比較:JETT - Comparison to jXLS

とりあえずテンプレートファイルへの出力を試す。

1.テンプレートファイルを用意する。
2.出力コードを書く。

 public static File makeShift() {
  System.out.println(System.getProperty("user.dir"));
  
  List<employee> employees = Lists.newArrayList();
  
  Map<string object=""> beans = Maps.newHashMap();
  Employee emp = new Employee();
  emp.name = "Butcher";
  emp.skillLevel = "High";
  
  employees.add(emp);
  
  beans.put("employees", employees);
  
  String inPath  = "resources/shift_template.xlsx";
  String outPath = "resources/shift.xlsx";
  
  try (FileOutputStream fileOut = new FileOutputStream(outPath);
    InputStream fileIn       = new BufferedInputStream(new FileInputStream(inPath))) {

   ExcelTransformer transformer = new ExcelTransformer();
   Workbook workbook = transformer.transform(fileIn, beans);
   workbook.write(fileOut);
   fileOut.close();
   
  } catch (IOException e) {
   System.err.println("IOException reading " + inPath + ": " + e.getMessage());
  } catch (InvalidFormatException e) {
   System.err.println("InvalidFormatException reading " + inPath + ": " + e.getMessage());
  }
  
  return new File(outPath);  
 }

余分なコードが多いが、テンプレートへの出力処理は ExcelTransformer#transform で JavaBeans を渡すだけでOKなのでかなり簡潔。

テンプレートファイルに記述する構文もシンプルで良い。
個人的に、forEach タグを利用する時にデータの無い行や列にタグを書かなくて良いのは嬉しい。

番外

その他、調べて評判良かったのが ExCella だったけど、Maven repository に登録されておらずライブラリ管理を考えるのが面倒になってスルーした。
(今回は play なので build.sbt で管理)

2014年12月31日水曜日

俳句調のツイートを検索して表示するWebアプリ作ってみた

俳句調のツイートを Twitter から検索して表示するWebアプリ作ってみた。

http://haiku-matcher.herokuapp.com/
https://github.com/ohbarye

テキストボックスに入力された値でツイートを取得して、その中から575調になっているものだけを表示する。
入力値がユーザ名なら、そのユーザのタイムラインから。ユーザ名でなければキーワード検索した結果からツイートを取得する。


言語/フレームワーク

  • Java8
  • Play Framework 2.2.6

□よかったこと
・やっぱりJava8で書くとコードが変わると思った。まだ知らない機能が多いので引き続き学びたい
・Play、噂通りで開発効率がかなり上がる
・特に立ち上げの速さ
・sbtによる依存性管理がストレスフリー
・Java EEから解放された感
 http://d.hatena.ne.jp/wyukawa/20140416/1397662264
・フルスタック感
・Hot Deploy
・scala templateの型安全性
…+まだやってないこといっぱいある

■困ったこと
・Play2.2系はJava8対応してなくて、sbtのバージョンあげたり結局諦めたエラーとかあった(2.3以降は対応済。よく調べずに2.2を選んだのは失敗)
・Playの更新頻度が高いので調べた情報が違うバージョンの情報…というのはよくあった

形態素解析

  • kuromoji

文章ぶち込むだけで解析結果を返してくれるライブラリ。全文検索エンジンの Solr とかでも使っているらしい。
Tweet から拾う単語は未知語(内蔵辞書に載ってない語)が多かった。今回は雑に処理したけど、そこらへん真面目にやろうとしたら自分で辞書用意する必要があるっぽくて震えた。

Twitter連携

  • twitter4j

直接 Twitter API を叩く必要ない。
ライブラリの問題じゃないけど、検索後によって401が返ってくるというよくわからないエラーでかなり悩んだ。時間を置いたりリトライしたら通るようになった。
いろいろググってたら Twitter から401エラーが返ってきて困った的な質問がめちゃ多くて世界中の悩み感があった。

プラットフォーム

  • heroku

初めて使ってみた。確かに爆速でデプロイできて便利っぽい。
環境・サーバ構築とかをほとんど考えなくてよくなったのでアプリ作る〜公開のハードルがまた下がった感がある。

けどまだ全然使いこなせてない。

git push heroku master した後の待ち時間が長いので休憩にしたりしてた。

フロントサイド

  • webjars
  • Bootstrap
  • LESS

フロントサイドの知識全然無いけど、この辺組み合わせるのがややモダンらしいので試してみた。
jqueryとか、クライアントサイドライブラリ揃えるのも確かに面倒なので webjars のこと知れてよかったという気持ちがある。

あまりよく知らないのでデザイン領域触れたくなかったけど、Bootstrap とか LESS とか試してみたら思ったより軽く書けたのでちょっと興味湧いた。けど深入りしたくない感じ。

その他感想

仕事で試す日が来ることは暫く無さそうで辛い(からい)。

2014年12月24日水曜日

[Play Framework] 設定ファイル(application.conf)から値を取得する

設定ファイル:application.conf に定義した値を取得する方法。意外と簡単で良かった。

とりあえず取得

key="value"
まず conf ファイルに定義する。
文字列にダブルクォーテーション付ける必要があるので注意。
Play.application()
       .configuration()
       .getString("key");    // "value" が取得できる

Configulationクラス

Configulation クラスには getString 以外にも getBoolean, getDouble など様々な型での値の取得が可能で便利だが、上記の書き方だとやたら長いのでラップしたメソッドを用意して使ってる。
 public static String getString(String key) {  
     return Play.application()
       .configuration()
       .getString(key);
 }

 public static boolean getBoolean(String key) {  
     return Play.application()
       .configuration()
       .getBoolean(key);
 }

2014年12月23日火曜日

[Play Framework] controllers 内でサブパッケージを作成する場合の注意点

Play Framework 始めた。

controllers 内にサブパッケージを作成して、その中のクラスおよびメソッドへのルーティングで一度失敗したのでメモ。

controller 直下のクラスにアクセスする

まず基本のおさらい。
パッケージ名:controllers
クラス名:MyClass
メソッド名:index
↑ココにアクセスしたい場合は以下のように routes とテンプレートファイルを編集する。
POST /index  controllers.MyClass.index()
@form(action = routes.MyClass.index) {
    <!-- 省略 -->
} 

controller 下のサブパッケージのクラスにアクセスする

本題はこのクラスのサブパッケージ内に作成する場合。
パッケージ名:controllers.sub
クラス名:MyClass
メソッド名:index
まず routes を修正する。
POST /index  controllers.sub.MyClass.index()
その後に scala.html 内のPOST先 action を修正。
サブパッケージ名をクラス名に付けてみる。
@form(action = routes.sub.MyClass.index) {
    <!-- 省略 -->
}
これでアクセスしてみると…
value sub is not a member of object controllers.routes
「"sub"なんてオブジェクトないよ」とのエラーメッセージ。
調べてみたところ完全修飾名で書けばOKとのこと。
There is a routes subpackage for each controller package. So the rightfully qualified name of your route is:@controllers.foo.routes.Home.index()
It's done this way to "see" the routes subpackage relatively from any
controllet package as "routes" 
[2.0] [Scala] routes for controller sub-packages - Google Groups -  
下記の通り修正したら無事通るようになった。
@form(action = controllers.sub.routes.MyClass.index) {
    <!-- 省略 -->
}

2014年9月6日土曜日

[Java] JDKコマンドを使う時は対象のVMとバージョンを合わせる

でないと VMVersionMismatchException という非常に明確なExceptionが飛ぶ。

$ jstack -F 5268
Attaching to process ID 5268, please wait...
Exception in thread "main" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:601)
        at sun.tools.jstack.JStack.runJStackTool(JStack.java:136)
        at sun.tools.jstack.JStack.main(JStack.java:102)
Caused by: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 23.5-b02. Target VM is 23.1-b03
        at sun.jvm.hotspot.runtime.VM.checkVMVersion(VM.java:234)
        at sun.jvm.hotspot.runtime.VM.(VM.java:297)
        at sun.jvm.hotspot.runtime.VM.initialize(VM.java:367)
        at sun.jvm.hotspot.bugspot.BugSpotAgent.setupVM(BugSpotAgent.java:598)
        at sun.jvm.hotspot.bugspot.BugSpotAgent.go(BugSpotAgent.java:493)
        at sun.jvm.hotspot.bugspot.BugSpotAgent.attach(BugSpotAgent.java:331)
        at sun.jvm.hotspot.tools.Tool.start(Tool.java:163)
        at sun.jvm.hotspot.tools.JStack.main(JStack.java:86)
        ... 6 more


表示されるバージョンはJVM(上の例だとHotSpot)のバージョン。
JavaSEとかに比べるとあまり馴染みが無い。
Supported versions are 23.5-b02. Target VM is 23.1-b03

【参考】

7杯目のおかわりコーヒーはイルカの夢を見るか? - Programming Studio -

2014年9月5日金曜日

[SAStruts] SAStrutsのActionではgetから始まるメソッド名を付けてはいけない

ActionクラスにgetHogeという名前のメソッドを追加したところ、変更箇所以外の挙動がおかしくなった。

SAStrutsの落とし穴 - じゃばらの手記 -によると…
SAStrutsJSPレンダリングするとき、ActionFormとActionのプロパティとpublicフィールドの値をMapに詰め込みます。こうすることでpublicフィールドをJavaBeansのプロパティのような感覚で扱うことが出来るわけです。
ハマった。。

【環境】

sa-struts-1.0.4-sp9

2014年9月4日木曜日

[Java] jpsコマンドでTomcatのプロセスが表示されない


jps ツールは、ターゲットシステム上で計測された HotSpot Java 仮想マシン (JVM) を一覧表示します。このツールで表示できるレポート情報は、アクセス権を持った JVM に関するものに限定されます。(jps - Java 仮想マシンプロセスステータスツール -
というから、
起動中のTomcatのPIDを知りたくてjpsコマンドを実行したら。
$ jps
8960 Jps
jpsコマンドのプロセスしか結果に表示されない。 ググったところ、jpsコマンドでTomcatのプロセスが表示されない場合の対処方法がすぐ見つかった。

jpsコマンドはjavaアプリケーションが出力するtempファイルからプロセス情報を拾っているとのこと。

Windowsだと下記ディレクトリにPIDがファイル名のファイルが生成されていた。

C:\Users\[ログオンユーザ名]\AppData\Local\Temp\hsperfdata_[ログオンユーザ名]


で、Tomcatはと言うと、システムプロパティ:java.io.tmpdirで指定したディレクトリにPIDファイルを出力する。デフォルトだと $CATALINA_HOME\temp になる気がする…、未確認。

JVMの起動オプションで出力先をjpsの確認先にしてやり、Tomcatを再起動すると…
-Djava.io.tmpdir=C:\Users\[ログオンユーザ名]\AppData\Local\Temp\hsperfdata_[ログオンユーザ名]

jpsコマンドでTomcatのPIDが表示されることを確認。

$ jps
13212 Bootstrap
8960 Jps

Tomcat以外にも独自のファイルを作成するjavaアプリケーションはありそう。

【環境】

JDK 6.0.45
Tomcat 6.0系(マイナーバージョン未確認)

2014年7月13日日曜日

[S2Chronos] リクエストをトリガーにTaskを非同期実行

本来の使い方に即しているかわからないけど、S2Chronosで非同期処理が簡単に実現できる。

前回書いた記事の通りに設定を行った後、リクエストをトリガーにTaskを非同期実行するプログラムを書いた。
package org.seasar.example.action;

import javax.annotation.Resource;

import org.seasar.chronos.core.Scheduler;
import org.seasar.example.task.SampleTask;
import org.seasar.framework.log.Logger;
import org.seasar.struts.annotation.Execute;

public class IndexAction {

    private static final Logger logger = Logger.getLogger(IndexAction.class);

    @Resource
    private Scheduler scheduler;

    @Execute(validator = false)
    public String index() throws InterruptedException {

         // タスク登録
         // 登録後、Chronosがプールしている別スレッドで自動実行
         scheduler.addTask(SampleTask.class);

        // 実行状況を確認するログ出力
        for (int i=0;i<10;i++) {
            logger.info("アクションの処理実行中 " + i);
            Thread.sleep(1000);
        }
        return "index.jsp";
    }

}
package org.seasar.example.task;

import org.seasar.chronos.core.annotation.task.Task;
import org.seasar.chronos.core.annotation.task.method.NextTask;
import org.seasar.chronos.core.annotation.trigger.NonDelayTrigger;
import org.seasar.framework.log.Logger;

@Task(autoSchedule=false)
@NonDelayTrigger
public class SampleTask {

    @NextTask("test")
    public static void doExecute() throws InterruptedException {

        // 実行状況を確認するログ出力
        for (int i=0;i<10;i++) {
            logger.info("タスクの実行中 " + i);
            Thread.sleep(800);
        }
    }

    private static final Logger logger = Logger.getLogger(SampleTask.class);

    public void initialize() {
        logger.info("[initialize]");
    }

    public void start() {
        logger.info("[start]");
    }

    public void finish() {
        logger.info("[finish]");
    }

    public void destroy() {
        logger.info("[destroy]");
    }
}
ブラウザからリクエストを送信した結果が下記。 非同期処理が実現されている。
DEBUG 2014-07-13 21:40:30,588 [http-bio-8080-exec-8] BEGIN org.seasar.example.action.IndexAction#index()
DEBUG 2014-07-13 21:40:30,599 [http-bio-8080-exec-8] トランザクションを開始しました。tx=[FormatId=4360, GlobalId=1405255230598/0, BranchId=]
DEBUG 2014-07-13 21:40:30,599 [http-bio-8080-exec-8] BEGIN org.seasar.chronos.core.impl.SchedulerImpl#addTask(class org.seasar.example.task.SampleTask)
DEBUG 2014-07-13 21:40:30,604 [http-bio-8080-exec-8] END org.seasar.chronos.core.impl.SchedulerImpl#addTask(class org.seasar.example.task.SampleTask) : true
INFO  2014-07-13 21:40:30,604 [http-bio-8080-exec-8] アクションの処理実行中 0
INFO  2014-07-13 21:40:31,604 [http-bio-8080-exec-8] アクションの処理実行中 1
INFO  2014-07-13 21:40:32,604 [http-bio-8080-exec-8] アクションの処理実行中 2
INFO  2014-07-13 21:40:33,605 [http-bio-8080-exec-8] アクションの処理実行中 3
DEBUG 2014-07-13 21:40:33,875 [chronos-pool-3-thread-1] BEGIN org.seasar.example.task.SampleTask#initialize()
INFO  2014-07-13 21:40:33,876 [chronos-pool-3-thread-1] [initialize]
DEBUG 2014-07-13 21:40:33,876 [chronos-pool-3-thread-1] END org.seasar.example.task.SampleTask#initialize() : null
INFO  2014-07-13 21:40:34,605 [http-bio-8080-exec-8] アクションの処理実行中 4
INFO  2014-07-13 21:40:35,605 [http-bio-8080-exec-8] アクションの処理実行中 5
DEBUG 2014-07-13 21:40:35,879 [chronos-pool-1-daemonthread-1] タスク (org.seasar.example.task.SampleTask) を 開始します.
DEBUG 2014-07-13 21:40:35,881 [chronos-pool-1-daemonthread-2] タスク (org.seasar.example.task.SampleTask) を 開始しました.
INFO  2014-07-13 21:40:35,882 [chronos-pool-3-thread-1] [start]
INFO  2014-07-13 21:40:35,886 [chronos-pool-3-thread-1] タスクの実行中 0
INFO  2014-07-13 21:40:36,605 [http-bio-8080-exec-8] アクションの処理実行中 6
INFO  2014-07-13 21:40:36,686 [chronos-pool-3-thread-1] タスクの実行中 1
INFO  2014-07-13 21:40:37,486 [chronos-pool-3-thread-1] タスクの実行中 2
INFO  2014-07-13 21:40:37,605 [http-bio-8080-exec-8] アクションの処理実行中 7
INFO  2014-07-13 21:40:38,286 [chronos-pool-3-thread-1] タスクの実行中 3
INFO  2014-07-13 21:40:38,605 [http-bio-8080-exec-8] アクションの処理実行中 8
INFO  2014-07-13 21:40:39,086 [chronos-pool-3-thread-1] タスクの実行中 4
INFO  2014-07-13 21:40:39,605 [http-bio-8080-exec-8] アクションの処理実行中 9
INFO  2014-07-13 21:40:39,886 [chronos-pool-3-thread-1] タスクの実行中 5
DEBUG 2014-07-13 21:40:40,609 [http-bio-8080-exec-8] トランザクションをコミットしました。tx=[FormatId=4360, GlobalId=1405255230598/0, BranchId=]
DEBUG 2014-07-13 21:40:40,609 [http-bio-8080-exec-8] END org.seasar.example.action.IndexAction#index() : index.jsp
DEBUG 2014-07-13 21:40:40,630 [http-bio-8080-exec-8] 

** after *****************************************: /index.do
  Response class=org.apache.catalina.connector.ResponseFacade, instance=org.apache.catalina.connector.ResponseFacade@413803ba
  [request]indexAction=org.seasar.example.action.IndexAction$$EnhancedByS2AOP$$60223be4@60c0a150
  [request]indexActionForm=org.seasar.struts.action.ActionFormWrapper@447cc9c9
  [request]javax.servlet.forward.context_path=/s2sample
  [request]javax.servlet.forward.request_uri=/s2sample/
  [request]javax.servlet.forward.servlet_path=/
  [request]org.apache.struts.action.MESSAGE=org.seasar.struts.util.S2PropertyMessageResources@5ca3ce3f
  [request]org.apache.struts.action.MODULE=org.seasar.struts.config.S2ModuleConfig@639b99d
  [request]org.apache.struts.action.mapping.instance=ActionConfig[path=/index,name=indexActionForm,scope=request,validate=false,cancellable=false
  [request]org.seasar.struts.util.S2ExecuteConfigUtil=org.seasar.struts.config.S2ExecuteConfig@37c8f59c
  [cookie]JSESSIONID=B594B4C31C8CDCC115905A8A57A739AA
  [cookie]JSESSIONID=560C273D113E86C2FED64320DEADF6AB
  [session]org.apache.struts.action.LOCALE=ja

INFO  2014-07-13 21:40:40,702 [chronos-pool-3-thread-1] タスクの実行中 6
INFO  2014-07-13 21:40:41,502 [chronos-pool-3-thread-1] タスクの実行中 7
INFO  2014-07-13 21:40:42,302 [chronos-pool-3-thread-1] タスクの実行中 8
INFO  2014-07-13 21:40:43,102 [chronos-pool-3-thread-1] タスクの実行中 9
INFO  2014-07-13 21:40:43,903 [chronos-pool-3-thread-1] [finish]
DEBUG 2014-07-13 21:40:43,904 [chronos-pool-1-daemonthread-2] タスク (org.seasar.example.task.SampleTask) を 終了します.
DEBUG 2014-07-13 21:40:43,905 [chronos-pool-1-daemonthread-2] タスク (org.seasar.example.task.SampleTask) を 終了しました.
DEBUG 2014-07-13 21:40:47,885 [chronos-pool-3-thread-1] BEGIN org.seasar.example.task.SampleTask#destroy()
INFO  2014-07-13 21:40:47,885 [chronos-pool-3-thread-1] [destroy]
DEBUG 2014-07-13 21:40:47,885 [chronos-pool-3-thread-1] END org.seasar.example.task.SampleTask#destroy() : null
もちろん自分でスレッド作っても同じだけど、こっちのほうがコード少なくてすむ。
また、あまり多くないかもしれないが、契機が時間でなくリクエストとなる(API的な)バッチ処理や、画面からバッチの実行を制御するような場合にはこんな感じで実現すると良さそう。

【環境】

Java SE7
Seasar2.4.45
S2Chronos1.0.0

2014年7月8日火曜日

[SAStruts] RequestProcessor 内で Action やMethod のインスタンスを取得する

Seasar2のS2RequestProcessorを拡張しての共通処理に挑戦中。

やりたいことは殆ど『Seasar2 徹底入門』の「6.5 認証処理を実装する」の通りだけども、自前の処理を書くと途端に戸惑う。
特に、呼び出されるMethodやActionの取得方法がわからなくて困った。
調べる前にS2RequestProcessorのソースを見てたら類似の処理があったので、おそらくこれかな~。
// Actionの取得
S2ActionMapping s2mapping = S2ActionMappingUtil.getActionMapping();
Object action = s2mapping.getAction();
やりたい共通処理は認証で、自前のAbstractActionを継承したActionにのみ行いたい処理を今回のRequestProcessorに書くつもり。

認証を行う対象はアノテーションでマーキングすべきかと思ったけど、AbstractActionに認証用のDtoを定義している。
その為、↑で取得したActionのプロパティを見て対象を判定することにする。
// Methodの取得
S2ExecuteConfig executeConfig = S2ExecuteConfigUtil.getExecuteConfig();
Method method = executeConfig.getMethod();
なんとなく、org.seasar.struts.utilパッケージのユーティリティが鍵っぽい。

SAどころかStrutsの中身もちゃんと見たことないので、この機会にもう少し学んでみよう。

2014年7月4日金曜日

[Seasar2] S2Chronos1.0.0 のセットアップが公式通りだとうまくいかない

"Seasar2に対応したJavaオブジェクトスケジューリングフレームワーク"、S2Chronosの有用性検証を依頼されたのでセットアップ。

公式を見ながら行ったのだが、diconファイルの読み込みあたりでClassNotFoundExceptionなんかが頻発。。いくつかのblogを参考にしてみると公式に記載の無い設定が必要だったりした。

以下、インストールに要した全作業。

1. S2Choronosのjarファイルを↓からダウンロード。WEB-INF/lib に配置し、ビルドパスに加える。
 http://s2chronos.sandbox.seasar.org/ja/download.html

 ソースは下記から。
 http://maven.seasar.org/maven2/org/seasar/chronos/s2chronos-core/1.0.0/
 http://maven.seasar.org/maven2/org/seasar/chronos/s2chronos-extension/1.0.0/


2.chronosCustomize.dicon を作成し、 src/main/resource に配置する。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
    "http://www.seasar.org/dtd/components24.dtd">
<components>
    <component name="schedulerConfiguration" class="org.seasar.chronos.core.SchedulerConfiguration">
        <property name="hotdeployDisable">true</property>
        <property name="daemon">true</property>
        <property name="autoFinish">false</property>
        <property name="autoFinishTimeLimit">5000L</property>
        <property name="taskScanIntervalTime">2000L</property>
        <property name="threadPoolType">@org.seasar.chronos.core.ThreadPoolType@CACHED</property>
    </component>
</components>
※ chronosCustomizer.dicon ではないので要注意。
※ パッケージ名のorg.seasar.chronos.coreが公式だと org.seasar.chronos.core.model になっている。1.0.0ではmodelというパッケージは存在しないので修正する。

3. creator.dicon の<components>タグ中に下記を追記する。
<component class="org.seasar.chronos.core.creator.TaskCreator"/>
<component class="org.seasar.chronos.core.creator.TriggerCreator"/>
4. customizer.dicon の<components>タグ中に下記を追記する。
 <component name="taskSupportAspectCustomizer" class="org.seasar.framework.container.customizer.AspectCustomizer">
   <initMethod name="addInterceptorName">
    <arg>"aop.traceInterceptor"</arg>
   </initMethod>
   <property name="pointcut">"do.*, initialize, destroy"</property>
 </component>
 <component name="taskCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain">
  <initMethod name="addCustomizer">
   <arg>taskSupportAspectCustomizer</arg>
  </initMethod>
 </component>
 <component name="triggerSupportAspectCustomizer" class="org.seasar.framework.container.customizer.AspectCustomizer">
  <initMethod name="addInterceptorName">
   <arg>"aop.traceInterceptor"</arg>
  </initMethod>
  <property name="pointcut">".*"</property>
 </component>
 <component name="triggerCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain">
  <initMethod name="addCustomizer">
   <arg>triggerSupportAspectCustomizer</arg>
  </initMethod>
 </component>
5. app.diconの<components>タグ中に下記を追記する。
<include path="chronos-extension.dicon"/>
※ 公式に記述なし。

6. web.xml の<web-app>タグ中に下記を追記する。
<servlet>
    <servlet-name>chronosServlet</servlet-name>
    <servlet-class>org.seasar.chronos.extension.servlet.S2ChronosServlet</servlet-class>
    <load-on-startup>3</load-on-startup>
</servlet>
※ 公式に記述なし。

これでTomcatを起動する。記述に間違いがある場合はコンソールにログが出力されるので確認する。当たり前だが、エラー発生時にはException名よりもどのファイルの読み込み時に落ちているのかに着目して追うと原因を突き止めやすい。

【環境】

Java SE7
Seasar2.4.45
S2Chronos1.0.0
※ そもそもJava SE5までしかサポートしていない模様。

[Java] アプリケーションの実行環境(ホスト名, IPなど)を取得する

java.net.InetAddressクラスを利用することでアプリケーションの実行されているマシン名を取得できる。
import java.net.InetAddress;

// ホスト名取得はこれだけ
System.out.println(InetAddress.getLocalHost().getHostName());
他にもIPアドレスを取得したりできる。

Webアプリケーションだと、AP起動時にホスト名から実行されている環境を判定してメモリに保持しておくなどの使い道がありそう。 ソースで環境毎の分岐を記述することで、環境依存ファイルを少し減らせる…かも。
例えば、起動時に以下のようなクラスをロードしておく。Singletonだと良さそう。
import java.net.InetAddress;
import java.net.UnknownHostException;

public class RuntimeEnvironment {

 private static RuntimeEnvironment instance = new RuntimeEnvironment();
 private RuntimeEnvironment() {}

 private static String hostName;
 private static InetAddress inetAddress;

 public static void init() {
  try {
   inetAddress = InetAddress.getLocalHost();
  } catch (UnknownHostException e) {}
  hostName = inetAddress.getHostName();

 }

 public static String getHostName()             {return hostName;}
 public static InetAddress getInetAddress() {return inetAddress;}

        // 以下、用意する環境を表す列挙型を定義するなど…

}




【環境】

 Java SE7

2014年5月5日月曜日

[Java] POIで、読み込んだExcelファイルから図形を取得して値をセットする

Excelファイル(※2007以降の.xlsx形式)から目的の図形を取得し、値をセットする。
今回はテキストボックスを取得してセットするサンプル。

既に対象のWorkbookとSheetを取得している状態から。
// 図形描画の為のXSSFDrawingから図形のListを取得
XSSFDrawing drawing = sheet.createDrawingPatriarch();
List<XSSFShape> shapeList = drawing.getShapes();

// Listから目的の図形を探索
for (XSSFShape sh : shapeList) {
  // (1)実際の型を確認
  if (sh instanceof XSSFSimpleShape) {
     XSSFSimpleShape ss = (XSSFSimpleShape) sh;
     // (2)テキストの値をkeyに対象かどうか判断
     if (ss.getText().equals("key")) {
      ss.setText("value you want to set");
     }
  }
}

(1)Listで取得されるオブジェクトはXSSFShapeのサブクラス(※)であり、検査せずにSimpleShapeとして扱おうとするとClassCastExceptionが発生する。
 ※こいつら。↓
  XSSFConnector, XSSFGraphicFrame, XSSFPicture, XSSFShapeGroup, XSSFSimpleShape。

(2)APIDocなんかを読み漁ったけれども、定義した名前から図形を取得する方法がわからず。とりあえずテキストから判断するやり方で記述。


【環境】

 POI 3.10
 Java SE7

【参考】

XSSFSimpleShape (POI API Documentation)


[追記 2014/5/7] 

上記のやり方で値をセットすると、図形にもともと設定されていたフォントや揃方がクリアされてデフォルト値になってしまうことがわかった。

もともと設定されていたフォントなどを維持したままテキストのみを置き換える場合は下記のようにテキストを分解し、テキストの最小単位(TextRun)に対して操作を行わなければいけないらしい。

// 上記の11行目から
if (ss.getText().equals("key")) {
  for (XSSFTextParagraph p : ss.getTextParagraphs()) {
   for (XSSFTextRun r : p.getTextRuns()) {
    r.setText("value you want to set");  // ※1
   }
  }
}
※1 改行を含む文字列をブチ込んでも大丈夫だった。だがParagraphの扱いがよくわかっていないので、複数行を操作する場合はもう少し工夫が必要なのかもしれない。

FontはFontクラスで管理してたり、AlignはTextParagraphで管理していたり。Excelの実装自体がそうなのかもしれないけど、Excel上の設定と各オブジェクトの結びつきがわかりづらく、直感的に操作しづらい。。

2014年5月2日金曜日

[Java] enum型にデータと振る舞いを持たせる

Effective Java第二版でJavaの勉強中。

「項目 30  int定数の代わりに enum を使用する」にて紹介されている、
"enum型にデータと振る舞いを持たせる"コードを真似して書いてみた。
// 面積の単位ごとのデータ、メソッドを定義
private enum Unit {
HEIBEI ("㎡"){
  String getArea(double area) {
   return area + this.toString();
  }
 },
 TSUBO ("坪"){
  String getArea(double area) {
   return area * 0.3025 + this.toString();
  }
 };

 private final String unitName;

 // コンストラクタ
 Unit(String unitName) { this.unitName = unitName; }

 // すべての定数にオーバーライドを強制する
 abstract String getArea(double area);

 // カスタム文字列表現
 @Override
 public String toString() {
  return unitName;
 }


 // 一意の文字列表現から定数を取得できるようMap化しておく
 private static final Map stringToEnum = new HashMap<>();
 static {
   for (Unit unit : values())
    stringToEnum.put(unit.toString(), unit);
 }

 // 一意の文字列表現から定数を取得する(toStringと対)
 public Unit fromString(String unitName) {
  return stringToEnum.get(unitName);
 }
}

// 定義したenumを利用する
public static void main(String[] args) {
 double area = 320.25;

 System.out.println(Unit.HEIBEI.getArea(area));
 System.out.println(Unit.TSUBO.getArea(area));
}
実行結果
320.25㎡
96.875625坪

これまでenum型については、
バッドプラクティスとして知られる"int enumパターン"(下記)の代用として定数を列挙する、ぐらいにしか使ったことがなかった。。
// No More int enum pattern!!
public static final int HEIBEI = 1;
public static final int TSUBO  = 2;

2014年5月1日木曜日

[Java] POIで、名前が定義されているセルに値をセットする

Excelファイル(※2007以降の.xlsx形式)から名前が定義されているセルを取得し、値をセットする。

既に対象のWorkbookとSheetを取得している状態から。
XSSFName name = workbook.getName("cell's Name");
CellReference ref = new CellReference(name.getRefersToFormula());
XSSFRow row = sheet.getRow(ref.getRow());
XSSFCell cell = row.getCell(ref.getCol());
cell.setCellValue("value you want to set");

上記コードにて、単一のセルにも結合されたセルにも値をセットできることを確認済み。
結合されたセルは範囲で取得したりする必要があるかと思ったけど、今のところ問題無さそう。

また、存在しないセル名を指定している場合、1行目のworkbook#getNameはNullを返すので注意。

【環境】

 POI 3.10
 Java SE7

【参考】

POIで名前定義されているセルを取得する話 - kelkronsoの日記 -
POIで、名前定義の入ったカラムから値を取得。 - るいもの戯れ言 -

2014年4月30日水曜日

[Java] Apache POIで指定した範囲の中心に画像を貼り付けようとして挫折

下記の条件でExcelファイル(※2007以降の.xlsx形式)に画像を貼り付ける。(だいぶ限定的…)

1.画像を貼り付けるセル範囲が指定されている。
2.画像は指定された範囲内で、アスペクト比を保ったまま最大化する。
3.範囲は任意だが、指定された範囲のセルはすべて同じ幅であり、
 かつ指定された範囲のセルはすべて同じ高さとする。


こんな感じ。(水平・垂直方向が逆のケースもあり)


結論から言うと、完全にアスペクト比を保ったままの最適化は出来なかった。

上記図で言うmarginは画像貼り付け先の開始位置をずらしつつ、オフセットを指定することで構成されるが、オフセットの指定はint型でしか行えないことによる。(必要なオフセットは当然、小数の場合もある)

単純なことだが、それに気付くまでの軌跡は↓。。

// 画像とセル範囲のアスペクト比を比較
double imgRatio = (orgImgWidth / (double)orgImgHeight);
double cellRangeRatio = (cellRangeWidth / (double)cellRangeHeight);

// 高さを最大化し、水平方向位置を計算する
if (imgRatio < cellRangeRatio) {
  // 貼り付けられる画像の幅
  double imgWidth = (orgImgWidth * (cellRangeHeight / (double)orgImgHeight));

  // 余白の幅
  double margin = (cellRangeWidth - imgWidth) / 2 ;
  int offsetCol = (int)(margin /  cellWidth);
  imgStartCol = rcol1 + offsetCol;
  imgEndCol = rcol2 - offsetCol;
  int offset = (int)(XSSFShape.EMU_PER_PIXEL * (margin  %  cellWidth));
  dx1 = offset;
  dx2 = offset;

// 横を最大化し、垂直方向位置を計算する
} else if (imgRatio > cellRangeRatio) {
  // 貼り付けられる画像の高さ
  double imgHeight = (orgImgHeight * (cellRangeWidth / (double)orgImgWidth));

  // 余白の高さ
  double margin = (cellRangeHeight - imgHeight) / 2 ;
  int offsetRow = (int)(margin /  cellWidth);
  imgStartRow = rrow1 + offsetRow;
  imgEndRow = rrow2 - offsetRow;
  int offset =  (int)(XSSFShape.EMU_PER_PIXEL * (margin  %  cellHeight));
  dy1 = offset;
  dy2 = offset;
}

【環境】

 POI 3.10
 Java SE7

2014年4月28日月曜日

[Java] Apache POI XSSFClientAnchor でoffset を指定して画像を貼り付ける

初POI。
EXCEL(.xlsx)ファイルに画像を貼り付ける際の offset の指定に苦戦。

画像の位置指定は XSSFClientAnchor クラスを用いて行う。
設定方法は下記の2通りが可能。

1.create時に位置指定を行う。

XSSFClientAnchor anchor = XSSFDrawing.createAnchor(
                          dx1, dy1, dx2, dy2, col1, row1, col2, row2);

2.create後に位置指定を行う。

XSSFClientAnchor anchor = XSSFCreationHelper.createClientAnchor();
anchor.setDx1(dx1);
anchor.setDy1(dy1);
anchor.setDx2(dx2);
anchor.setDy2(dy2);
anchor.setCol1(col1);
anchor.setRow1(row1);
anchor.setCol2(col2);
anchor.setRow2(row2);

※XSSFDrawing クラスに引数無しのcreateAnchorメソッドは無い模様。

開始位置・終了位置の指定

XSSFAnchor.setCol , setRowでcolumn・rowをそれぞれ指定する。
注意点としては、指定したセルの最右下の座標から開始されること。
anchor.setCol1(1);
anchor.setRow1(1);
anchor.setCol2(4);
anchor.setRow2(4);

と指定すると

と出力される。

また、左辺と上辺は罫線上に描画され、右辺と下辺は罫線に重ならない。


オフセットの指定

XSSFClientAnchor#setDx1, setDy1, setDx2, setDy2 で指定する。
これら setter の引数はjavadocには
the x coordinate within the first cell.
とあり、int型の引数が要求されているが、ピクセルなのかポイントなのかわからない。
適当な数字を入れても挙動の法則がわからなかったので、
The offsets are measured in EMUs (english metric units). There are 12700 emus in a point , see EMU_PER_PIXEL and EMU_PER_POINT constants in the XSSFShape class. 

To position a rectange with given width and height at a point (x,y) 
you need to transform all coordinates to Excel coordinate system, i.e 

x1 --> col1 + dx1 
y1 --> row1 + dy1 
x2 --> col2 + dx2 
y2 --> row2 + dy2 
EMUという単位で指定する、とのこと。
この数字はXSSFShape クラスに定義されており、1ポイント=12700EMU、1ピクセル=9525となっている。

その為、setColやsetRowで指定した開始位置から10ピクセルのoffsetを作りたい時は
setDx1(XSSFShape.EMU_PER_PIXEL * 10);
とすれば良い模様。


指定するdx1, dy1, dx2, dy2 はそれぞれ下記のようにオフセットを構成する。




【環境】

POI 3.10
Java SE7

【参考】

apache poi の HSSFClientAnchor について。 - 人工無脳が作りたかった
POI - User - How to calculate location of the shape in Excel 2007?
POI - Dev - Re: Anchor type for images in Excel