« Back to Home

Blog

Java, Up to Date

2012-06-12 23:01:03 +0900

JavaOne東京2012から2ヶ月が経ちました。そこで聞いてきた話を基に漸く社内勉強会を行いましたので、その資料を公開します。

今回と次回は2回シリーズで「Javaの現在と未来」をテーマにお届けする予定です。

まず1回目の今回は「Javaの現在」です。以下のスライドと、スライド中で示している主なコードは別にPDFも用意しました。PDFの方はSlideShareから直接ダウンロードして頂いた方が見やすいと思います。

http://www.slideshare.net/akirakoyasu/java-up-to-date

http://www.slideshare.net/akirakoyasu/java-up-to-date-sources

Core JavaとEnterprise Javaそれぞれの分野の最新仕様としては、Core JavaがJavaSE7、Enterprise JavaがJavaEE6ということになります。(最新といってもJavaEE6が出たのは2009年ですが・・・)

取り上げた内容は以下の通りです。

JavaSE7
  • Project Coin
  • Fork/Join Framework
  • InvokeDynamic(紹介だけ)
  • More New I/O
JavaEE6
  • Simplified Packaging(war)
  • Managed Bean
  • CDI

JavaEE6の紹介が随分さっぱりしたものになってしまいました。JavaEE6には他に興味深い機能がいくつもありますので、どこかのタイミングでフォローしていきたいと思っています。

次回は「Javaの未来」です

※ちなみにスライドの表紙写真は、櫻庭さんにあやかってマカロンにしてみました。

s3tools-1.0.0を公開

2012-03-29 22:33:22 +0900

Amazon S3のCLIといえばs3cmdが有名なようですが、API認証の初期設定(--configure)にちょっと違和感を感じます。そこでCLIを自分で書いてみました。これを修正BSDライセンスで公開します。

s3tools: Amazon S3 CLI

動作環境はJavaSE6.0以降です。

まずは最低限必要なコマンドとして、ListとGetとPutの3つだけを用意しました。コマンドもオプションももう少し拡張するつもりです。

それとまだLinux/Mac用のshスクリプトしか書いていませんが、そのうちWindows用のbatファイルも書こうと思っています。

内部実装としても今のところクラス設計がとにかくいい加減なので、今後拡張しやすいように改善していく予定です。

Hello, Guava !

2012-03-12 00:12:36 +0900

Guava(Google発のJavaライブラリ)については過去にも記事を書いていますが、今回は改めて社内勉強会のためにまとめ直しました。

おそらく最も基本で身近で使い出があると思われる、

  • com.google.common.base
  • com.google.common.collect
  • com.google.common.io

この3つのパッケージについて幾つかのクラス・メソッドをピックアップし、以下のスライドで紹介しています。また各パッケージの導入部に設定した課題については、JDKのみで書いた場合とGuavaを使って書いた場合のサンプルコードを用意しました。
(サンプルコードの方はPDFなのですが、埋め込みでは見づらそうなのでSlideShareからダウンロードして頂く方が良いかもしれません)

http://www.slideshare.net/akirakoyasu/hello-guava

http://www.slideshare.net/akirakoyasu/hello-guava-samples

取り上げたクラス・メソッドは以下の通りです。

com.google.common.base
  • Function
  • Predicate
  • Supplier
  • Preconditions
  • Joiner / Splitter
  • CaseFormat
  • Stopwatch
  • Charsets
com.google.common.collect
  • filter() / transform()
  • Lists / Maps
  • ForwardingCollection / ImmutableCollection
  • ComparisonChain
  • Range
com.google.common.io
  • ByteStreams / CharStreams
  • Files
  • Resources

 

HTTPクライアントとして使うjersey-client

2012-02-05 05:12:32 +0900

JAX-RSのリファレンス実装であるJerseyには、RESTサービスを提供するサーバ側の実装(jersey-server)と、RESTサービスを利用するクライアント側の実装(jersey-client)とがあります。

RESTサービスではHTTPを介してサーバとクライアントとがやりとりしますから、当然jersey-clientはHTTPクライアントの機能を備えています。今回はJerseyの使い方の番外編として、RESTサービスを抜きにHTTPクライアントとしても、jersey-clientは使い勝手が良いという話をします。

ちなみに、JAX-RS 1.1にはクライアント側のAPIは定義されておらず、今年(2012年)のQ2にリリース予定のJAX-RS 2.0から定義されることになっています。(本稿執筆時点ではEarly Draft Reviewの段階)

まず使ってみる

何はともあれ、使ってみましょう。

Mavenを使用する場合は、以下のdependencyを追加します。

<dependency>
	<groupId>com.sun.jersey</groupId>
	<artifactId>jersey-client</artifactId>
	<version>1.11</version>
</dependency>

あるいは、http://jersey.java.net/からjarをダウンロードしてクラスパスに含めます。

まずは簡単なところで、あるサイトのHTMLを取得したいとしましょう。その場合以下のようなコードになります。

Client c = Client.create();
WebResource r = c.resource("http://www.example.com/");
String html = r.get(String.class);

これだけです。説明も不要なくらいごく簡潔ですね。ClientとWebResource

  1. Clientインスタンスを作る
  2. URLからWebResourceインスタンスを作る
  3. リクエストの結果をオブジェクトとして取得する

基本的にはこの3ステップで進んでいきます。

Clientの設定

先のコードのように、これはこれで手軽に書けて良いのですが、実際的にはクライアントにいくつかの設定をしたいことが多いでしょう。

タイムアウトとリダイレクトの追跡を設定するには、次のようにします。

Client c = Client.create();
c.setConnectTimeout(3*1000);
c.setReadTimeout(5*1000);
c.setFollowRedirects(Boolean.TRUE);

Basic認証を設定するには、次のようにします。

c.addFilter(new HTTPBasicAuthFilter("user", "passwd"));

WebResourceの加工

個々のリクエストに関する変更はWebResourceを使って行います。以下のコードがどのような結果になるか見てみましょう。

WebResource r = c.resource("http://www.example.com/");
WebResource r2 = r
	.path("search")
	.queryParam("key", "value");

System.out.printf("r : %s%n", r);
System.out.printf("r2: %s%n", r2);
r : http://www.example.com/
r2: http://www.example.com/search?key=value

このように、WebResourceの持つメソッドの多くはそのインスタンス自身を変更せず、変更を加えた新しいWebResourceインスタンス・あるいはWebResource.Builderインスタンスを生成して返します。

いくつかの変更・設定を行う例です。

WebResource.Builder b = c.resource("http://www.example.com/")
	.path("search")
	.queryParam("key", "value")
	.cookie(new Cookie("name", "value"))
	.accept(MediaType.TEXT_HTML_TYPE)
	.header("name","value");

レスポンスをオブジェクトとして取得

HTTPリクエストを行ったら、レスポンスを受け取ります。get()メソッドに渡すクラスによって、レスポンスをどのクラスのオブジェクトに変換して取得するかを指定します。とは言え、さすがに任意のクラスに変換できるというわけではなく、JAX-RSで変換が定義されているクラスと、Jerseyで独自に変換が提供されているクラスに限られます。

以下のクラスが使用可能です。

  • byte[]
  • String
  • java.io.InputStream
  • java.io.Reader
  • java.io.File
  • javax.xml.transform.Source (vaildなxhtmlの場合など)
  • org.w3c.dom.Document (vaildなxhtmlの場合など)
  • java.awt.image.RenderedImage (画像の場合)

またget()メソッドはHTTPのGETメソッドに対応しており、同様にPOST・PUT・DELETE・HEAD・OPTIONSメソッドそれぞれにも対応するメソッドが用意されています。

ここまでをまとめると、以下のようなコードになります。

Client c = Client.create();
c.setConnectTimeout(3*1000);
c.setReadTimeout(5*1000);
c.setFollowRedirects(Boolean.TRUE);
c.addFilter(new HTTPBasicAuthFilter("user", "passwd"));

String html = c.resource("http://www.example.com/")
	.path("search")
	.queryParam("key", "value")
	.cookie(new Cookie("name", "value"))
	.accept(MediaType.TEXT_HTML_TYPE)
	.header("name","value")
	.get(String.class);

ClientFilter

jersey-clientの特徴として、フィルターによるカスタマイズがあります。プログラミングモデルとしてはServletFilterに近いイメージですね。この仕組みがコンパクトなAPIに柔軟性を与えていて、個人的に気に入っているポイントでもあります。

ClientFilterのチェーン

Clientクラス・WebResourceクラスともにFilterableインターフェイスを実装しており、各々にフィルタを適用できます。具体的にはClientFilterを拡張したクラスを作成し、handle()メソッドを実装します。
※先ほどBasic認証の設定で使ったHTTPBasicAuthFilterは、予め用意されているClientFilterの拡張クラスです。

Filterable#addFilter(new ClientFilter() {
	@Override
	public ClientResponse handle(ClientRequest cr)
			throws ClientHandlerException {

		doSome(cr);		// リクエストに対して何かをする

		// 次のフィルタへ処理を渡す
		ClientResponse res = getNext().handle(cr);

		doOther(res);		// レスポンスに対して何かをする

		return res;
	}
});

例えばClientにユーザエージェントを設定する場合には、次のようにします。

Client c = Client.create();
c.addFilter(new ClientFilter() {
	@Override
	public ClientResponse handle(ClientRequest cr)
			throws ClientHandlerException {
		if (!cr.getHeaders().containsKey(HttpHeaders.USER_AGENT)) {
			cr.getHeaders().add(
				HttpHeaders.USER_AGENT,
				"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:9.0.1)"
						+ " Gecko/20100101 Firefox/9.0.1");
		}
		return getNext().handle(cr);
	}
});

共通的なHTTPヘッダを設定するというシーンは割とよくあると思うのですが、それ用の拡張クラスは用意されていません。

HTTPクライアントとしてのjersey-client

元はRESTサービスのクライアントとして開発されたjersey-clientではありますが、HTTPクライアントとして見ても実用に耐えるものです。そして最近のシステムでは、"Web上にある何かを取ってくる"という機能も当たり前になりました。それを速く・分かりやすく・かつ柔軟に実装したいとき、jersey-clientはとても有用だと思います。

30分で実行するAmazon Elastic MapReduce(Amazon EMR)

2012-01-16 03:26:09 +0900

今年のテーマの一つはビッグデータということで、そろそろ本格的にMapReduceに手を出そうと思います。

手軽にMapReduceを試してみるのであれば、やはりAWSでしょう。今回はAmazon Elastic MapReduce(Amazon EMR)を動かす方法を簡単にまとめておきます。

実行する処理の例としては、Hadoopのチュートリアルにある処理をそのまま使わせてもらいましょう。与えられたテキスト(英文)に出現する単語を数えるというものです。

実行する処理
与えられたテキストに出現する単語を数える

実行の手順

Amazon EMRにはApache Hadoopが使われています。本来Hadoopを使うためには、Hadoopの環境そのものをセットアップする必要があるのですが、そこをAWSが既にやってくれているわけです。

そのため、手順としては

  1. Hadoopのジョブとなるjarファイルを作る
  2. そのjarファイルと入力ファイルをAmazon S3へアップロードする
  3. Amazon EMRのコンソールから処理を実行する

となります。

ジョブのjarファイルを作る

まずはmavenでプロジェクトを作成しましょう。

執筆時点でサポートされているHadoopのバージョンは0.20.2ですので、該当するdependencyを追加します。

<dependency>
  <groupId>org.apache.hadoop</groupId>
  <artifactId>hadoop-core</artifactId>
  <version>0.20.205.0</version>
</dependency>

次に必要なクラスを実装していきます。

  • エントリーポイントとなる、mainメソッドをもつクラス
  • Mapperクラス
  • Reducerクラス

この3つのクラスが必要になりますが、今回は1つのソースファイルに納めてしまうことにしましょう。コードは以下のようになります。内容はチュートリアルのサンプルコードほぼそのままです。

package hadooptest;

import java.io.IOException;
import java.util.StringTokenizer;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;

public class WordCount {

	/**
	 * Mapper
	 */
	public static class Map
			extends Mapper<LongWritable, Text, Text, IntWritable> {

		private final static IntWritable one = new IntWritable(1);
		private Text word = new Text();

		@Override
		public void map(
				LongWritable key,
				Text value,
				Context context) throws IOException, InterruptedException {

			String line = value.toString();
			StringTokenizer tokenizer = new StringTokenizer(line);
			while (tokenizer.hasMoreTokens()) {
				word.set(tokenizer.nextToken());
				context.write(word, one);
			}
		}
	}

	/**
	 * Reducer
	 */
	public static class Reduce
			extends Reducer<Text, IntWritable, Text, IntWritable> {

		@Override
		public void reduce(
				Text key,
				Iterable<IntWritable> values,
				Context context) throws IOException, InterruptedException {
			int sum = 0;
			for (IntWritable value : values) {
				sum += value.get();
			}
			context.write(key, new IntWritable(sum));
		}
	}

	public static void main(String[] args) throws Exception {
		Job job = new Job();
		job.setJarByClass(WordCount.class);
		job.setJobName("wordcount");

		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(IntWritable.class);

		job.setMapperClass(Map.class);
		job.setReducerClass(Reduce.class);

		job.setInputFormatClass(TextInputFormat.class);
		job.setOutputFormatClass(TextOutputFormat.class);

		FileInputFormat.setInputPaths(job, new Path(args[0]));
		FileOutputFormat.setOutputPath(job, new Path(args[1]));

		job.waitForCompletion(true);
	}
}

なお、hadoop-0.20.2はAPIの移行期にあり、旧APIはorg.apache.hadoop.mapredパッケージ以下に、新APIはorg.apache.hadoop.mapreduceパッケージ以下にあります。似たような名前のクラスが多いので注意してください。

またmapとreduceの実装メソッドには、必ず@Overrideアノテーションを付けておくことをお勧めします。なぜならMapperクラス・Reducerクラスとも具象クラスであり、メソッドの実装を強制されないためです。(それはシグネチャが間違っていても、コンパイルエラーにならないことを意味します)

これをmavenでjarにしておきましょう。

ファイルをAmazon S3へアップロードする

処理の入力となるテキストファイルを作成します。なんでも構わないのですが、今回はこんなテキストを用意しました。

Now the serpent was more subtil than any beast of the field which the LORD God had made. And he said unto the woman, Yea, hath God said, Ye shall not eat of every tree of the garden?
And the woman said unto the serpent, We may eat of the fruit of the trees of the garden:
But of the fruit of the tree which is in the midst of the garden, God hath said, Ye shall not eat of it, neither shall ye touch it, lest ye die.
And the serpent said unto the woman, Ye shall not surely die:
For God doth know that in the day ye eat thereof, then your eyes shall be opened, and ye shall be as gods, knowing good and evil.
And when the woman saw that the tree was good for food, and that it was pleasant to the eyes, and a tree to be desired to make one wise, she took of the fruit thereof, and did eat, and gave also unto her husband with her; and he did eat.
And the eyes of them both were opened, and they knew that they were naked; and they sewed fig leaves together, and made themselves aprons.

AWSのWebコンソールにログインして、S3のタブを開きます。

次にEMR用に新しくバケットを作成し、更にバケットの中にフォルダ"input"、"jar"を作成します。そのフォルダにそれぞれ入力ファイル、jarファイルをアップロードします。

これで実行の準備ができました。

処理を実行する

AWSのWebコンソールで、Elastic MapReduceのタブを開きます。

ジョブフローの作成ボタンを押し、ウィザードを開きます。いくらか細かい設定もできるのですが、ここでは大切なところだけを挙げます。

  • Job Typeでは"Custom JAR"を選択します
  • JAR Locationは、s3://<backet-name>/jar/<jarfile-name> とします
  • JAR Argumentsでは、以下のように引数を3つ与えます
    • hadooptest.WordCount ・・・mainメソッドをもつクラス名
    • s3://<backet-name>/input ・・・入力ファイルのを置いたフォルダ
    • s3://<backet-name>/output ・・・結果を出力するフォルダ
  • Keep Aliveは"No"を選択します(処理が終了したらEC2インスタンスを破棄します)

出力フォルダが既に存在しているとエラーになりますので注意してください。

ウィザードを完了すると、ジョブフロー一覧画面に今作成したジョブフローが表示されます。処理が完了するまでしばらく待ちましょう。

結果

処理が完了すると、状態のカラムが"COMPLETED"になります。(失敗した場合は”FAILED”)Amazon S3の指定したフォルダ内に結果が出力されているはずですので、確認してみましょう。

以下実行結果の一部です。ちゃんと単語が数えられていますね。

And	5
But	1
LORD	1
Now	1
We	1
also	1
as	1
beast	1
desired	1
did	2
doth	1
eat	4
eyes,	1
fruit	3
garden,	1

これでMapReduceを使った処理が実行できました。