木曜日 11 29, 2012

Today is my last day at Oracle

お世話になった皆様へ

私事で大変恐縮ですが、11月末をもちましてOracleを卒業させて頂くことになりました。

Sun+Oracle統合後の2年半、また2002年にSunに入社してから実に10年が経過しました。この間、Java EE関連のコンサルティング案件を中心に、社内外の非常に多くのすばらしい方々と様々なプロジェクトを経験させて頂きました。ここで得た経験と人脈は私にとって貴重な財産となりました。

本当にありがとうございました。

本ブログについては、Sun時代の2006年から開始しておりますが、更新が怠慢で、数えるほどのエントリしか投稿していないにもかかわらず、時々「ブログみましたよ」とか「参考になりました」とか言ってもらえることがあり、少なからず皆さんのお役に立てたであろうことをうれしく思います。

私自身はOracleを離れますが、今後もJavaとITシステムの発展のために微力ながら尽力してまいる所存でおります。

最後に、皆様のご活躍とご健康を心よりお祈り申し上げます。

p.s. 本ブログのエントリの更新は止まりますが、本ブログと過去のエントリはこのまま残していただけるようですので、今後も参照して頂くことが可能です。

これまでsubscribeしていただいた皆様、ありがとうございました。来月以降、また別のどこかのブログでお会いしましょう。

火曜日 10 30, 2012

Java EE 6: How to get module name and app name

Java EE 6では標準仕様として、アプリケーションからモジュール名やアプリケーション名をランタイムに取得する ことができるようになっています[1] 。これらの値は標準名としてJNDIから取得することができ、モジュール名が"java:module/ModuleName"、アプリケー ション名が"java:app/AppName"となっています。

InitialContext ctx = new InitialContext();
String moduleName = ctx.lookup("java:module/ModuleName");  // モジュール名
String appName = ctx.lookup("java:app/AppName");           // アプリケーション名

また、@Resourceによるリソースインジェクションを使用すれば、以下のようにして取得することもできます。

@Resource(lookup="java:module/ModuleName")
String moduleName;                          // モジュール名がインジェクションされる
@Resource(lookup="java:app/AppName")
String appName;                             // アプリケーション名がインジェクションされる

なお、EAR形式ではなく、Webモジュール単体やEJBモジュール単体としてデプロイした時のAppNameの値は、スペック上の記述では明確 ではないのですが、GlassFish V3 (3.1.2.2)、WebLogic 12c (12.1.1)、JBoss AS 7 (7.1.1)で確認した限りでは、 どのアプリケーションサーバでも、AppNameの値はModuleNameと同じ値が取得できるようです。

また、これらの値はWeb Profileのサーバでも同様に取得できるようです(GlassFishとJBossそれぞれのWeb Profile 実装で確認)。しかし、Apache TomEE (1.5.0)の現状の実装ではModuleNameの値が"localhost/<Webモジュール名 >"となってしまい、他のコンテナと異なる結果になってしまう点に注意してください。

Java EE 6以前の環境では、ランタイムにモジュール名やアプリケーション名を取得するには、事前に静的なプロパ ティファイルをアプリに仕込んでおいたり、モジュールのクラスパスから推測するなど、スマートではない手段を 取るしかなかったのですが、上記の仕様により非常に簡単に、かつポータビリティのある方法で実現することが可能 になっています。

なお、Apache Tomcat 7.0はServlet 3.0仕様として実装されているようですが、上記の方法でモジュール名・アプリ ケーション名をJNDIから取得することはできないことを確認しています。

結論:「Tomcatではなく、標準のJava EEコンテナを使うべき理由がまた一つ増えました」


[1] Java EE 6スペック(JSR 316)上の記述を以下に抜粋します (pp.122-123):

EE.5.15 Application Name and Module Name References

A component may access the name of the current application using the pre-defined JNDI name java:app/AppName. A component may access the name of the current module using the pre-defined JNDI name java:module/ModuleName. Both of these names are represented by String objects.

火曜日 8 28, 2012

jrunscript as a cross platform scripting environment

ちょっとした問題解決や作業の効率化をする時には、何らかのスクリプト言語を使ってツールを作ることがよくあります。私はshスクリプトが得意なので、UNIX環境を前提とする場合は、ほとんどの場合shスクリプトで済ませてしまいますが、作成したツールをお客様に使ってもらうときには、やはりWindows環境でも動作できる必要があります。

shスクリプトで簡単に表現できるfind、grep、sed、awkなどをWindows環境でどう置き換えたらいいかを考えると、頭を抱えてしまいます。そもそも、短時間に簡単に問題解決をするためにスクリプト言語を選択したのに、実現したいことが簡単にできなければ意味がありません。Windows環境にCygwinをインストールすれば同じshスクリプトをWindowsでも利用できますが、お客様にCygwin環境を整えてもらうのはちょっと大げさになるので、お願いするのは気が引けます。そんなとき、JDKに含まれるjrunscriptをベースにJavaScriptベースでのツール開発はこのような悩みを解決する1つの解になります。jrunscriptは以下のような方にお勧めなツールです。

  • Windows環境とUNIX環境の両方で動くコマンドラインツールを作りたい
  • find、grep、sed、awkなどを組み合わせてshスクリプトを書くのは得意だが、Windows Script Hostはスキルがない
  • Javaプログラミングは得意である
  • ツールは他の人にも使ってもらいたいので、事前にインストールしなければならないソフトウェアや環境設定は極力少なくしたい(スクリプトファイルと使用方法のメモだけの小さなファイルだけ渡せばよいというのが理想)

最近では、サーバサイドでもJDK 6を使用している場合が多くなっていますし、事務作業用のPCでは、開発グループの方でなくともJDK 6はPCにインストールしている場合が多いです。もしインストールされていなくても、JDKだけならインストールをお願いするのはそれほど面倒ではないでしょう。その意味では、jrunscriptをベースにツール開発するのは良い選択肢だと思います。

それでは、jrunscriptを使ってJavaScriptベースでツール開発するときの注意点について、少し掘り下げて議論してみようと思います。

1) Windows環境とUNIX環境の両方で使えるようにするには?

これはそれほど難しい作業ではありません。例えば、作成するツールの大部分のロジックはJavaScript形式でmytool.jsというファイルで用意した場合、このスクリプトをjrunscriptコマンド経由で実行するUNIX環境用のshスクリプトと、Windows環境用のbatファイルをそれぞれ追加で用意するだけです。

mytool.sh (UNIX用):

#!/bin/sh
bindir=$(cd $(dirname $0) && pwd)
case "`uname`" in
  CYGWIN*) bindir=`cygpath -w "$bindir"`
           ;;
esac
jrunscript "${bindir}/mytool.js" "$@"
mytool.bat (Windows用):
@echo off
set bindir=%~dp0
jrunscript "%bindir%mytool.js" %*

UNIX用のshスクリプトの方はCygwin環境の場合も考慮しています。このように、起動部分のスクリプトが準備できれば、後は本体のjsスクリプトの開発ではほとんどUNIXとWindowsの違いを意識せずに共通のロジックを定義していくことができます。

2) jrunscriptではcat, cp, find、grepなどが使える

jrunscriptでは、UNIX環境での標準的なコマンドに似せた組込関数がいくつか用意されています。

従って、UNIX系のshスクリプトに慣れている方であれば、ある程度UNIXコマンドを扱うのと同じような感覚で、ロジックを組み立てることができます。例えば、「srcディレクトリ内の全てjavaソースファイルについて、enumを使用しているjavaソースを調べる」ということをするには、以下のようなスクリプトを定義すればよいことになります。
find('src', '.*.java', function(f) { grep('enum', f); });

ただし、全てのUNIXコマンドが用意されているわけではなく、また、同じコマンドがあっても機能的に十分ではない場合がありますので、ある程度自分で有用な関数を用意してあげる必要はあります。例えば、組込関数cp(from, to)は、コピー元とコピー先が共にファイルでなければならないシンプルな実装しかありません。UNIX環境で簡単に記述できる

$ cp -r src/* tmp/
のようなディレクトリ丸ごとコピーには対応していません。しかしながら、先ほど紹介したfind()関数を使えば、cp -rに似たリカーシブ・コピーの関数を簡単に用意することができます。
function cpr(fromdir, todir, pattern) {
    if (pattern == undefined) {
        pattern = ".*";
    }
    var frdir = pathToFile(fromdir).getCanonicalPath();
    find(fromdir, pattern, function(f) {
        // relative dir of file f from 'fromdir'.
        var relative = f.getParentFile().getCanonicalPath().substring(frdir.length() + 1);
        var dstdir = pathToFile(todir + "/" + relative);
        if (!dstdir.exists()) {
            // Create the destination dir for file f.
            mkdirs(dstdir);
        }
        // Copy file f to 'dstdir'.
        cp(f, dstdir + "/" + f.getName());
    });
}
javaのファイルI/OのAPIは、Windows環境の場合でもパス区切りに"/"を使っても問題ないため、上記で示したロジックの範囲においてはUNIXとWindowsを意識することはありません。

また、もう一つの注意は、exec(cmd)関数です。例えば、jarコマンドを以下のように実行すると、日本語出力の部分が文字化けしてしまうことが分かります。

$ jrunscript
js> exec("jar xvf example.jar")
  META-INF/ ?????¬???????μ???B
 META-INF/MANIFEST.MF ???W?J???????μ???B
  com/ ?????¬???????μ???B
  com/example/ ?????¬???????μ???B
 com/example/Bar.class ???W?J???????μ???B
  com/example/dummy/ ?????¬???????μ???B
com/example/dummy/dummy.txt ?????o???????μ???B
com/example/dummy.properties ?????o???????μ???B
 com/example/Foo.class ???W?J???????μ???B
また、exec()関数から実行するコマンドが標準エラー出力に出力を行う場合、コマンドの実行が途中で止まってしまうことがあるという問題があります。特にWindows環境では標準エラー出力のI/Oバッファが小さいようで、問題が発生しやすいです。例えば、以下のようなBATファイルを用意し、

errmsg.bat:

for /L %%i in (1,1,50) do echo "Error Message count = %%i" 1>&2
jrunscriptからexec()関数で実行してみると、ループの18回目ぐらいでコマンドの実行が止まってしまうことが確認できると思います。
C:\tmp>jrunscript -e "exec('errmsg.bat')"

C:\tmp>for /L %i in (1 1 100) do echo "Error Message count = %i"  1>&2

C:\tmp>echo "Error Message count = 1"  1>&2

        :

C:\tmp>echo "Error Message count = 18"  1>&2   ← 止まる
実際に以下のようにしてexec()関数の実装を確認してみると、やはり、実行したコマンドの出力については標準出力しか読み取っていない実装になっており、その出力の扱いもDataInputStreamを使っていることが文字化けの原因と考えられます。
$ jrunscript
js> this["exec"].toString()

function exec(cmd) {
    var process = java.lang.Runtime.getRuntime().exec(cmd);
    var inp = new DataInputStream(process.getInputStream());
    var line = null;
    while ((line = inp.readLine()) != null) {
        println(line);
    }
    process.waitFor();
    $exit = process.exitValue();
}
この問題を回避するには、実行するコマンドの標準出力と標準エラー出力の両方をマルチスレッドで処理する修正版のexec()関数をスクリプトの中に用意し、exec()関数をオーバライドしてしまえば回避できます。以下が修正したexec()関数の例です。
function exec(cmd) {
    var process = java.lang.Runtime.getRuntime().exec(cmd);
    var stdworker = new java.lang.Runnable(
        {run: function() { cat(process.getInputStream()); }});
    var errworker = new java.lang.Runnable(
        {run: function() { cat(process.getErrorStream()); }});
    new java.lang.Thread(stdworker).start();
    new java.lang.Thread(errworker).start();
    return proc.waitFor();
}
文字化け問題の解決にはビルトイン関数のcat()を使用しています。関数cat()はInputStreamReaderクラスを使用した文字化けしない実装であるため、以下のようにシンプルに修正版を実装することができます。

3) JavaScript以外の言語を使用するのはどうか?

JavaScriptはJavaと同じようにシンタックスが冗長なため、できればJavaScript以外のより表現力豊かな、Ruby、Groovy、Scalaなどを使ってツール開発したいところです。しかし、これらスクリプト言語のランタイムのライブラリは10MBを越えてしまうため、開発チーム以外の利用者への配布を前提とした場合はやはりJavaScript以外の言語の使用は控えたいところです。数KBのスクリプトのために、十数MBのJARを同梱するのはちょっとバランスが悪いですしね。早く、JREやJDKの標準インストールだけで任意のスクリプト言語が自由に使えるようになるといいですね。

月曜日 8 13, 2012

Running BTrace custom script with GlassFish V3

GlassFish V3でBTraceのカスタムスクリプトを使用しようとすると、以下のようなNoClassDefFoundErrorが出て、正しく実行できないことがあります。

致命的: GRIZZLY0038: HTTP Processing error.
java.lang.NoClassDefFoundError: MyTracer
	at com.sun.enterprise.web.pwc.connector.coyote.PwcCoyoteRequest.$btrace$MyTracer$m(PwcCoyoteRequest.java)
	at com.sun.enterprise.web.pwc.connector.coyote.PwcCoyoteRequest.(PwcCoyoteRequest.java)
	at com.sun.enterprise.web.connector.coyote.PECoyoteConnector.createRequest(PECoyoteConnector.java:283)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:205)
	at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:174)
	at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:828)
	at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:725)
	at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1019)
	at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:225)
	at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137)
	at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104)
	at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90)
	at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79)
	at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54)
	at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59)
	at com.sun.grizzly.ContextTask.run(ContextTask.java:71)
	at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532)
	at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513)
	at java.lang.Thread.run(Thread.java:680)
Caused by: java.lang.ClassNotFoundException: MyTracer not found by org.glassfish.web.glue [288]
	at org.apache.felix.framework.ModuleImpl.findClassOrResourceByDelegation(ModuleImpl.java:787)
	at org.apache.felix.framework.ModuleImpl.access$400(ModuleImpl.java:71)
	at org.apache.felix.framework.ModuleImpl$ModuleClassLoader.loadClass(ModuleImpl.java:1768)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
	... 19 more

どうやらこの原因は、OSGiフレームワークのfelixとの兼ね合いによる問題のようです。felixの設定ファイルは、3.1.1まではglassfish/osgi/felix/conf/config.propertiesにあったのですが、3.1.2からはEquinoxと統合され、glassfish/conf/osgi.propertiesにあります。最新のGlassFish 3.1.2.2でosgi.propertiesの内容を確認すると、以下のようにBTrace関連のライブラリはデフォルトでfelixの管理外でクラスが読まれるようになっていますが、BTraceスクリプトも同じようにfelix管理外となるようにしなければならないようです。

glassfish/conf/osgi.properties:

org.osgi.framework.bootdelegation=${eclipselink.bootdelegation}, \
                                  com.sun.btrace, com.sun.btrace.*, \
                                  org.netbeans.lib.profiler, org.netbeans.lib.profiler.*

BTraceスクリプトのパッケージ名をbootdelegationに追加しても良いのですが、環境設定に手をいれると後々困ることもあるので、簡単に対応するには、BTraceスクリプトのパッケージ名の方をbootdelegationに列挙されているパッケージ名のいずれかになるように変更してしまってもよいでしょう。例えば、BTraceスクリプトのパッケージ名をcom.sun.btrace.scriptsと宣言してしまいます。

MyTrace.java:

package com.sun.btrace.scripts;
         :
@BTrace public class MyTrace {
         :

後は修正したスクリプトをbtracecでコンパイルすれば、実行中のGlassFishサーバにスクリプトをアタッチできます。

$ btracec MyTracer.java

$ jps
80075 Jps
79806 glassfish.jar

$ btrace 79806 com/sun/btrace/scripts/MyTracer.class

ただし、GlassFish本体のクラス群をトレースしたいときは、btraceコマンドでは手遅れだったりするため、domain.xmlのJVMオプションで起動時から組み込まれるようにする必要がある場合もあります。

glassfish/domains/domain1/config/domain.xml:

<jvm-options>-javaagent:/Users/foo/java/tool/btrace-1.2.1/build/btrace-agent.jar=script=/Users/foo/java/btscripts/com/sun/btrace/scripts/MyTracer.class</jvm-options>

About

Takashi Nishigaya
Principal Consultant
Technology Solution Consulting
Oracle Consulting Services

Search

Categories
Archives
« 4月 2014
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
今日