以下HADOOP_CLASSPATHを設定して、
$ export HADOOP_CLASSPATH=dependency/mahout-math-0.4.jar:dependency/mahout-core-0.4.jar:dependency/mahout-utils-0.4.jar:dependency/google-collections-1.0-rc2.jar:dependency/gson-1.3.jar:dependency/mahout-collections-1.0.jar:dependency/commons-cli-2.0-mahout.jar
以下を実行。なぜかKMeansはhadoopがローカルで待ち受けていないと動かない様子。
$ ~/hadoop-0.20.2/bin/hadoop jar mahout-examples-0.4.jar org.apache.mahout.clustering.display.DisplayKMeans
以下は、Hadoopが待ち受けていなくても動きますが、クラスがpublic宣言されていないのでソースを書きかえる必要がありました。
$ ~/hadoop-0.20.2/bin/hadoop jar mahout-examples-0.4.jar org.apache.mahout.clustering.display.DisplayCanopy
mahoutにGUIが付いていることは、意外にしられていないのでは、と思います。
火曜日, 12月 21, 2010
火曜日, 12月 07, 2010
mahoutでTSP!
mahoutの中には、TSP(Travelling Salesman Problem / 巡回セールスマン問題)の例が入っています。一応GUIも付いてます。でも、なぜかsrc版からコンパイルしないと動きません。
うーん。。地味。もっと派手なのかと思ったのですが(AWT入ってるからバイナリのやつだと動かないのかな)。ちなみにmahoutはTESTを回すと準備にかなり時間がかかるので、
とやるのが良いようです。そして以下で起動。
しかし、Hadoopでやらせる(チェックをつける)と、outputパスがもうあるから書けないよ!と文句を言われるのだけど、これはどうすればよいのだろう。
MahoutEvaluator.javaの中でoutputって定義してあるから、複数回(Generationの分だけ)この評価を回したら、そらバッティングすると思うんだけど。。?(これはHDFS上のホームディレクトリというか、/user/~/outputとかのようにできる)
このディレクトリを、ランダム文字列付きとして実行したら最後まで動くことは動いた。
なお、cygwinなどでローカル実行するときは、Generationを(デフォルトの100から)低い数字にしておかないと大変な時間がかかるので注意。
うーん。。地味。もっと派手なのかと思ったのですが(AWT入ってるからバイナリのやつだと動かないのかな)。ちなみにmahoutはTESTを回すと準備にかなり時間がかかるので、
$ mvn install -Dmaven.test.skip=true
とやるのが良いようです。そして以下で起動。
$ ~/hadoop-0.20.2/bin/hadoop jar examples/target/mahout-examples-0.4-job.jar org.apache.mahout.ga.watchmaker.travellingsalesman.TravellingSalesman
しかし、Hadoopでやらせる(チェックをつける)と、outputパスがもうあるから書けないよ!と文句を言われるのだけど、これはどうすればよいのだろう。
MahoutEvaluator.javaの中でoutputって定義してあるから、複数回(Generationの分だけ)この評価を回したら、そらバッティングすると思うんだけど。。?(これはHDFS上のホームディレクトリというか、/user/~/outputとかのようにできる)
このディレクトリを、ランダム文字列付きとして実行したら最後まで動くことは動いた。
なお、cygwinなどでローカル実行するときは、Generationを(デフォルトの100から)低い数字にしておかないと大変な時間がかかるので注意。
水曜日, 11月 10, 2010
rhinoから"Google Storage"にupload!
GSUtil というコマンドラインツールが用意されているものの、Python(?Rubyだっけ)なので使っていなかった、が、以下ソースでuploadに成功。
"WebOS Goodies"さんの情報を参考にしました。ありがとうございます!
適当なソースですが、ご参考までに。
"WebOS Goodies"さんの情報を参考にしました。ありがとうございます!
importPackage(javax.crypto);
importPackage(javax.crypto.spec);
importPackage(org.apache.commons.codec.binary);
importPackage(org.apache.commons.httpclient);
importPackage(org.apache.commons.httpclient.methods);
var ACCESS_KEY = 'GOOG4C72MAOUMK7xxx3Z';
var SECRET = 'xy4jTrxxxxuTZEtqUItfZIViVUF3xEKx4xxxp';
var HOST = 'commondatastorage.googleapis.com';
var strDate = getDate();
var strPath = '/test/start.txt';
var headers = {
// 'Content-Length': '0',
'Content-Type': 'text/plain',
'Date': strDate,
'User-Agent': 'Jakarta Commons-HttpClient/3.1',
'Host': 'commondatastorage.googleapis.com'
};
// rhino + Java package/class
// http://d.hatena.ne.jp/terurou/20100918/1284791541
// http://www.fireproject.jp/feature/uzumi/httpclient/logging.html
java.lang.System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog");
java.lang.System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true");
java.lang.System.setProperty("org.apache.commons.logging.simplelog.log.httpclient.wire.header", "debug");
java.lang.System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.commons.httpclient", "debug");
var httpclient = new HttpClient();
if(false){
var httpget = new GetMethod("http://commondatastorage.googleapis.com" + strPath);
httpget.setRequestHeader(new Header("Date",strDate));
httpget.setRequestHeader(new Header("Authorization",getAuthHeader('GET', headers,strPath)));
var statusCode = httpclient.executeMethod(httpget);
print(httpget.getStatusLine());
}else{
var httpput = new PutMethod("http://commondatastorage.googleapis.com" + '/tf0054'+strPath);
var jstrTmp = "Hello this is a test in one line that is longer 12345677890\n";
httpput.setRequestHeader(new Header("Authorization",getAuthHeader('PUT', headers, strPath)));
httpput.setRequestHeader(new Header("Date",strDate));
httpput.setRequestHeader(new Header("x-goog-acl","public-read"));
httpput.setRequestHeader(new Header("Content-Type","text/plain"));
httpput.setRequestHeader(new Header("Content-Length","" + jstrTmp.length));
httpput.setRequestBody(jstrTmp);
httpclient.executeMethod(httpput);
print(httpput.getResponseBodyAsString());
}
function getAuthHeader(strMethod, headers, strPath){
function getHmacSign(strTmp){
var strType = 'HmacSHA1';
var jstrTmp = new java.lang.String(strTmp);
var jstrSec = new java.lang.String(SECRET);
var mac = Mac.getInstance(strType);
mac.init(new SecretKeySpec(jstrSec.getBytes(), strType));
return new java.lang.String(Base64.encodeBase64(mac.doFinal(jstrTmp.getBytes())),"UTF-8");
}
var strHeaders = strMethod + "\n";
strHeaders += "\n"; // Content-MD5 value
for(var i in headers){
if(i.toLowerCase() == 'date' || i.toLowerCase() == 'content-type'){
strHeaders += headers[i] + "\n";
} else {
// strHeaders += i.toLowerCase()+':'+headers[i]+"\r\n";
}
}
strHeaders += "x-goog-acl:public-read\n";
strHeaders += '/tf0054' + strPath;
print(">"+strHeaders);
return 'GOOG1 ' + ACCESS_KEY + ':' + getHmacSign(strHeaders);
}
function getDate(){
var d = new Date(new Date().getTime());
return d.toGMTString().replace(/UTC/,'GMT');
}
適当なソースですが、ご参考までに。
水曜日, 11月 03, 2010
BigQueryを試してみた
GoogleさんからBigQueryの利用OKがメール出来たので、(公開されているお試し手順に沿って)自らuploadしたデータに検索ができる、までを確認しました。
また、このときPythonが手元にない環境だったので、先に作ったcygwin環境のシェル+curlでお手軽に試せることも分かりました。
- - - ①操作用Tokenを取得
curl -X POST -d accountType=HOSTED_OR_GOOGLE -d Email=tf0054@gmail.com -d Passwd=xxx -d service=ndev -d source=google-bigquery-manualimport-1 -H "Content-Type: application/x-www-form-urlencoded" https://www.google.com/accounts/ClientLogin
xxxの部分はご自分のpwに変えて下さい。このcurlコマンド実行で得られるレスポンスにある、AUTH=yyyのyyy部分ががTokenになります。けっこう長いです。
②各種操作用シェルスクリプトを保存
Librariesのページにあるシェルをファイルに落とします。
check_import_status.sh
createtable.sh
delete_table.sh
import.sh
query.sh
このとき、curlからhttpsアクセスをするので、(私の環境では)各シェルの中にあるcurlのオプションに、"-k"を足す必要がありました。
③SSAのサンプルデータをダウンロード&配置
本家には日本からアクセスできなさそう?なので、ココからダウンロードし、以下絵の感じに「tf0054/yob1880.txt」として管理画面からupload。
④テーブルを作成
$ ./createtable.sh tf0054/tables/babynames
上のGoogleStorage管理画面キャプチャのとおりですが、ここの階層にあるディレクトリは自動的に掘られるようです(上記tablesなど)。
⑤データをインポート
$ ./import.sh tf0054/tables/babynames tf0054/yob1880.txt
これまでにGoogleStorageへ上げたデータがあれば、それをBigQueryで見られるようにする(BigQueryの管理下に置く)ことができました。
⑥インポート処理状況を確認
$ ./check_import_status.sh tf0054/tables/babynames b85867e864bf1259
先のインポートコマンド投入時にもらえるレスポンスに、ジョブIDがもらえるのでそれを最終パラメータにします。
⑦セレクトしてみる
$ ./query.sh "SELECT name,count FROM [tf0054/tables/babynames] WHERE gender = 'F' ORDER BY count DESC LIMIT 5;"
テーブル名はカギ括弧に括って記述し、さらにGoogleStorage的にはフルパスで参照する感じになりました。
- - -
レスポンスがJSONでもらえるだけど、見た目には分かりずらい。Parseエラーなどになっていないことを随時確認しましょう。例えば⑦の結果は以下です。
{"result":{"kind":"bigquery#queryResults","fields":[{"id":"name","type":"string"},{"id":"COUNT","type":"integer"}],"rows":[{"f":[{"v":"Mary"},{"v":"7065"}]},{"f":[{"v":"Anna"},{"v":"2604"}]},{"f":[{"v":"Emma"},{"v":"2003"}]},{"f":[{"v":"Elizabeth"},{"v":"1939"}]},{"f":[{"v":"Minnie"},{"v":"1746"}]}]}}
何やら可能性を感じますが、Hadoop上のHIVE、みたいな位置づけですね(でも、だとするとGoogleStorageがHadoopにあたるものとなる、、、のでしょうか)。また、対応できているSQL(?)の情報も公開されていました。
また、このときPythonが手元にない環境だったので、先に作ったcygwin環境のシェル+curlでお手軽に試せることも分かりました。
curl -X POST -d accountType=HOSTED_OR_GOOGLE -d Email=tf0054@gmail.com -d Passwd=xxx -d service=ndev -d source=google-bigquery-manualimport-1 -H "Content-Type: application/x-www-form-urlencoded" https://www.google.com/accounts/ClientLogin
xxxの部分はご自分のpwに変えて下さい。このcurlコマンド実行で得られるレスポンスにある、AUTH=yyyのyyy部分ががTokenになります。けっこう長いです。
②各種操作用シェルスクリプトを保存
Librariesのページにあるシェルをファイルに落とします。
check_import_status.sh
createtable.sh
delete_table.sh
import.sh
query.sh
このとき、curlからhttpsアクセスをするので、(私の環境では)各シェルの中にあるcurlのオプションに、"-k"を足す必要がありました。
③SSAのサンプルデータをダウンロード&配置
本家には日本からアクセスできなさそう?なので、ココからダウンロードし、以下絵の感じに「tf0054/yob1880.txt」として管理画面からupload。
④テーブルを作成
$ ./createtable.sh tf0054/tables/babynames
上のGoogleStorage管理画面キャプチャのとおりですが、ここの階層にあるディレクトリは自動的に掘られるようです(上記tablesなど)。
⑤データをインポート
$ ./import.sh tf0054/tables/babynames tf0054/yob1880.txt
これまでにGoogleStorageへ上げたデータがあれば、それをBigQueryで見られるようにする(BigQueryの管理下に置く)ことができました。
⑥インポート処理状況を確認
$ ./check_import_status.sh tf0054/tables/babynames b85867e864bf1259
先のインポートコマンド投入時にもらえるレスポンスに、ジョブIDがもらえるのでそれを最終パラメータにします。
⑦セレクトしてみる
$ ./query.sh "SELECT name,count FROM [tf0054/tables/babynames] WHERE gender = 'F' ORDER BY count DESC LIMIT 5;"
テーブル名はカギ括弧に括って記述し、さらにGoogleStorage的にはフルパスで参照する感じになりました。
レスポンスがJSONでもらえるだけど、見た目には分かりずらい。Parseエラーなどになっていないことを随時確認しましょう。例えば⑦の結果は以下です。
{"result":{"kind":"bigquery#queryResults","fields":[{"id":"name","type":"string"},{"id":"COUNT","type":"integer"}],"rows":[{"f":[{"v":"Mary"},{"v":"7065"}]},{"f":[{"v":"Anna"},{"v":"2604"}]},{"f":[{"v":"Emma"},{"v":"2003"}]},{"f":[{"v":"Elizabeth"},{"v":"1939"}]},{"f":[{"v":"Minnie"},{"v":"1746"}]}]}}
何やら可能性を感じますが、Hadoop上のHIVE、みたいな位置づけですね(でも、だとするとGoogleStorageがHadoopにあたるものとなる、、、のでしょうか)。また、対応できているSQL(?)の情報も公開されていました。
火曜日, 11月 02, 2010
JavascriptでHIVEを操作する(JDBC@Thrift)
rhinoを使って、JavascriptからHIVEを操作してみるテスト。普通にできた(あたりまえ)。
一応、走らせるときのbuild.xmlもサンプルとして。
HIVEの力なんだけど、とても簡単にM/Rが動くのは素晴らしい、と思います。
/*
http://wiki.apache.org/hadoop/Hive/HiveClient#Thrift_Java_Client
*/
importPackage(java.sql);
importPackage(java.lang);
Class.forName("org.apache.hadoop.hive.jdbc.HiveDriver");
var tableName = "testHiveDriverTable";
// make connection
var conn = DriverManager.getConnection("jdbc:hive://localhost:10000/default", "", "");
var stmt = conn.createStatement();
// init
stmt.executeQuery("drop table " + tableName);
stmt.executeQuery("create table " + tableName + " (key int, value string)");
// show tables
var sql = "show tables '" + tableName + "'";
print("Running: " + sql);
var resp = stmt.executeQuery(sql);
if (resp.next()) {
print(resp.getString(1));
}
// describe table
sql = "describe extended " + tableName;
print("Running: " + sql);
resp = stmt.executeQuery(sql);
while (resp.next()) {
print(resp.getString(1) + "\t" + resp.getString(2));
}
// load data into table
var filepath = "/tmp/a.txt";
sql = "load data local inpath '" + filepath + "' into table " + tableName;
print("Running: " + sql);
resp = stmt.executeQuery(sql);
// select * query
sql = "select * from " + tableName;
print("Running: " + sql);
resp = stmt.executeQuery(sql);
while (resp.next()) {
print(String(resp.getInt(1)) + "\t" + resp.getString(2));
}
// regular hive query
sql = "select count(1) from " + tableName;
print("Running: " + sql);
resp = stmt.executeQuery(sql);
while (resp.next()) {
print(resp.getString(1));
}
一応、走らせるときのbuild.xmlもサンプルとして。
<path id="clspath">
<fileset dir="build/jar">
<!-- メインの読込み -->
<include name="*.jar"/>
</fileset>
<fileset dir="${hive.dir}">
<!-- HIVEのjarを読込み -->
<include name="**/*.jar"/>
</fileset>
<fileset dir="${hadoop.dir}">
<!-- Hadoopのjarを読込み -->
<include name="*.jar"/>
</fileset>
</path>
<!-- blog-gae>ant rhino-run -Dfile.encoding=UTF-8 -Dscript="env0.js" -->
<target name="rhino-run">
<echo message="Running ${script}"/>
<java fork="yes"
classname="org.mozilla.javascript.tools.shell.Main"
failonerror="true">
<classpath>
<path refid="clspath" />
<pathelement path="js.jar" />
</classpath>
<arg value="${script}" />
</java>
</target>
HIVEの力なんだけど、とても簡単にM/Rが動くのは素晴らしい、と思います。
日曜日, 10月 31, 2010
cygwinでHadoop(v0.21.0)+HIVEを動かす
FileのAppendもできるようになったとのこともあり、Hadoopがv0.21.0な状態でHIVEを使いたかったのだが、やってみると結構苦労したのでメモ。cygwinをまじめに使うのも初めてで、、これも意外に大変。。
①HIVE-1612に掲示されたパッチを対応するtrunkソースに当てる。
②よしもふさんのページを参考にql/exec/Utilities.javaのgetMapRedWorkメソッドを修正(多少本体側も変更されているので以下参考)。
③ql/Context.javaのgetLocalScratchDirメソッドを修正(以下ひどい修正なので参考までに)。
①HIVE-1612に掲示されたパッチを対応するtrunkソースに当てる。
②よしもふさんのページを参考にql/exec/Utilities.javaのgetMapRedWorkメソッドを修正(多少本体側も変更されているので以下参考)。
public static MapredWork getMapRedWork (Configuration job) {
MapredWork gWork = null;
try {
synchronized(gWorkMap) {
gWork = gWorkMap.get(getHiveJobID(job));
}
if(gWork == null) {
synchronized (Utilities.class) {
if(gWork != null)
return (gWork);
Path planPath = new Path(HiveConf.getVar(job, HiveConf.ConfVars.PLAN));
FileSystem fs = planPath.getFileSystem(job);
InputStream in = fs.open(planPath);
MapredWork ret = deserializeMapRedWork(in, job);
gWork = ret;
gWork.initialize();
gWorkMap.put(getHiveJobID(job), gWork);
}
}
return (gWork);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException (e);
}
}
③ql/Context.javaのgetLocalScratchDirメソッドを修正(以下ひどい修正なので参考までに)。
public String getLocalScratchDir(boolean mkdir) {
try {
FileSystem fs = FileSystem.getLocal(conf);
URI uri = fs.getUri();
return "/tmp";
/*
return getScratchDir(uri.getScheme(), uri.getAuthority(),
mkdir, localScratchDir);
*/
} catch (IOException e) {
throw new RuntimeException (e);
}
}
金曜日, 9月 10, 2010
Solrクエリのパーサ2
以下を忘れいたので、GINによるSolrクエリパーサを改訂しました。
・NOT
・レンジクエリ(pub_date:[2009-10-01T00:00:00Z+TO+*])
・NOT
・レンジクエリ(pub_date:[2009-10-01T00:00:00Z+TO+*])
// Solrクエリパーサの作成(LL) v0.2
// パーサジェネレータを読込み
load('work\\mod_gin.js');
// パーサを作成(EBNFで文法を定義)
// JS_STRINGはダブルクウォートで括らなくてはならないのでTermを定義して使う
// Termで+記号は定義せず+desc/+ascを別書きとする(+OR+等とかぶるため全体がターム扱いとなる)
// Rangeには[]で括って作られるレンジクエリ用の記述を入れて別出し
var calc = new Gin.Grammar({
Solr: / Object ( '&' Object )* /,
Object: / ( Value '=' Factor ) /,
Factor: / Value ( ',' Factor | '+AND+' Factor | '+OR+' Factor | '+NOT+' Factor )* /,
Value: / Term | '(' Factor ')' /,
Term: / <[a-zA-Z0-9_.]+> ':[' Range ']' | <[a-zA-Z0-9_.:]+(\+desc|\+asc)?> /,
Range: / <[a-zA-Z0-9_.+*\-]+> /
}, "Solr", Gin.SPACE);
// 検証するSolrクエリ
var strInput = 'start=0&q=AREA_CD:050+OR+DISP:1+AND+HAN_CD:011)+AND+EKI_CD:26610+AND+pub_date:[2009-10-01T00:00:00Z+TO+*]&sort=EKI_CD+asc,HAN_CD+desc&wt=javabin&rows=0&version=2.2';
// パーサのデバッグを行うときは以下のコメントを復活
var input = [
// 'q=((AAA:1+AND+BBB:2))&start=1',
// '"q"=(("AAA:x"+AND+"BBB:2")+AND+"CCC:3")&"start"=1&"var"=1.1',
// '"q"=("DDD"+OR+("AAA"+AND+"BBB")+AND+"CCC")&"sort"="test+desc","kkk+asc"&"start"=1',
strInput
];
// 検証!
for (var i = 0; i < input.length; i++) {
var match = calc.parse(input[i]);
if (match && match.full){
print("AST: " + match.value.toSource());
}else{
print(input[i]);
print("Sentence was wrong.");
}
}
月曜日, 8月 09, 2010
Solrクエリのパーサ1
"Days on the Moon"さんのブログを参考に、Solrのクエリを整形(Lint)する目的でパーサを作る実験中です。
以下、なんとなく通った版です(GINはmod_gin.jsとリネームし、rhinoで起動)。
以下、なんとなく通った版です(GINはmod_gin.jsとリネームし、rhinoで起動)。
// Solrクエリパーサの作成(LL) v0.1
// パーサジェネレータを読込み
load('work\\mod_gin.js');
// パーサを作成(EBNFで文法を定義)
// JS_STRINGはダブルクウォートで括らなくてはならないのでTermを定義して使う
// Termで+記号は定義せず+desc/+ascを別書きとする(+OR+等とかぶるため全体がターム扱いとなる)
// Termには数字は入ってこない想定(Valueに$REALがあるため)
var calc = new Gin.Grammar({
Solr: / Object ( '&' Object )* /,
Object: / ( Value '=' Factor ) /,
Factor: / Value ( ',' Factor | '+AND+' Factor | '+OR+' Factor )* /,
Value: / $REAL | Term | '(' Factor ')' /,
Term: / <[a-zA-Z0-9:_.]+(\+desc|\+asc)?> /
}, "Solr", Gin.SPACE);
// 検証するSolrクエリ
var strInput = 'start=0&q=(AREA_CD:050+OR+DISP:1+AND+HAN_CD:011)+AND+EKI_CD:26610&sort=EKI_CD+asc,HAN_CD+desc&wt=javabin&rows=0&version=2.2';
// パーサのデバッグを行うときは以下のコメントを復活
var input = [
// 'q=((AAA:1+AND+BBB:2))&start=1',
strInput
];
// 検証!
for (var i = 0; i < input.length; i++) {
var match = calc.parse(input[i]);
if (match && match.full){
print("AST: " + match.value.toSource());
}else{
print(input[i]);
print("Sentence was wrong.");
}
}
月曜日, 7月 19, 2010
rhinoでe4xを使う
Windowsのantでスクリプト実行をしていたのだけど、何故かant経由だとe4xがうまく使えなかった。javaコマンドで直接起動する分には問題ない。
以下そのときのantのターゲット。
そのときのe4xテストスクリプトは以下。
エラーは以下のとおり。
これは(やはり)classpathの問題のようで、以下のスクリプト(コチラを参考)でclasspathを出してみると、
antのものばかりで、肝心のjs.jarが出ない。
そこで、上記ターゲットにantを実行しているjvmとは別のvmで実行するようforkを入れると、
うまくjs.jarが動くようになり、
e4xが正常に動作した。
検索すると意外にrhinoのバージョン問題か、などいろいろでるけど、1.7R1で正常に使えています
D:\work\blog-gae>java -cp war\WEB-INF\lib\js.jar org.mozilla.javascript.tools.shell.Main work\e4x.js
以下そのときのantのターゲット。
<target name="rhino-run">
<property name="classes" value="war/WEB-INF/lib/js.jar:war/WEB-INF/lib/mailapi.jar:war/WEB-INF/lib/smtp.jar"/>
<echo message="Running ${script}"/>
<java classname="org.mozilla.javascript.tools.shell.Main"
classpath="${classes}"
failonerror="true">
<arg value="${script}" />
</java>
</target>
そのときのe4xテストスクリプトは以下。
// var a = require("test0.js");
var i = '<a><b><c>aaaa</c></b><b><c>bbbb</c></b></a>';
var xml = new XML(i);
//print(xml.a.b.c[1].toString());
//print(xml.a.b.c[1]);
// 何故か先頭要素を省いたものが正解
print(xml.b.c[1]);
エラーは以下のとおり。
D:\work\blog-gae>ant rhino-run -Dscript="work\e4x.js"
Buildfile: build.xml
rhino-run:
[echo] Running work\e4x.js
[java] js: uncaught JavaScript runtime exception: ReferenceError: "XML" is not defined.
[java]
BUILD FAILED
D:\work\blog-gae\build.xml:141: Java returned: 3
Total time: 1 second
D:\work\blog-gae>
これは(やはり)classpathの問題のようで、以下のスクリプト(コチラを参考)でclasspathを出してみると、
var strConf = "java.class.path";
var cwd = environment[strConf];
cwd = cwd.replace(/;/g, "\n");
print(strConf + " = " + cwd);
antのものばかりで、肝心のjs.jarが出ない。
D:\work\blog-gae>ant rhino-run -Dscript="work\env0.js"
Buildfile: build.xml
rhino-run:
[echo] Running work\env0.js
[java] java.class.path = D:\work\apache-ant-1.7.1\bin\..\lib\ant-launcher.j
ar
[java] d:\Documents and Settings\00962724\.ant\lib\ivy-2.0.0-rc2.jar
[java] D:\work\apache-ant-1.7.1\lib\ant-antlr.jar
[java] D:\work\apache-ant-1.7.1\lib\ant-apache-bcel.jar
[java] D:\work\apache-ant-1.7.1\lib\ant-apache-bsf.jar
snip
[java] D:\work\apache-ant-1.7.1\lib\xercesImpl.jar
[java] D:\work\apache-ant-1.7.1\lib\xml-apis.jar
[java] C:\Program Files\Java\jdk1.6.0_18\lib\tools.jar
BUILD SUCCESSFUL
Total time: 1 second
D:\work\blog-gae>
そこで、上記ターゲットにantを実行しているjvmとは別のvmで実行するようforkを入れると、
<target name="rhino-run">
<property name="classes" value="war/WEB-INF/lib/js.jar:war/WEB-INF/lib/mailapi.jar:war/WEB-INF/lib/smtp.jar">
<echo message="Running ${script}">
<java fork="yes" classname="org.mozilla.javascript.tools.shell.Main" classpath="${classes}" failonerror="true">
<arg value="${script}">
</arg>
</java>
</echo></property></target>
うまくjs.jarが動くようになり、
D:\work\blog-gae>ant rhino-run -Dscript="work\env0.js"
Buildfile: build.xml
rhino-run:
[echo] Running work\env0.js
[java] java.class.path = D:\work\blog-gae\war\WEB-INF\lib\js.jar
[java] D:\work\blog-gae\war\WEB-INF\lib\mailapi.jar
[java] D:\work\blog-gae\war\WEB-INF\lib\smtp.jar
BUILD SUCCESSFUL
Total time: 3 seconds
D:\work\blog-gae>
e4xが正常に動作した。
D:\work\blog-gae>ant rhino-run -Dscript="work\e4x.js"
Buildfile: build.xml
rhino-run:
[echo] Running work\e4x.js
[java] bbbb
BUILD SUCCESSFUL
Total time: 1 second
D:\work\blog-gae>
検索すると意外にrhinoのバージョン問題か、などいろいろでるけど、1.7R1で正常に使えています
火曜日, 5月 18, 2010
Mahotをウェブアプリで使う
Jteamというサイトに、サンプルアプリが公開されていたのでメモ。
- sampleのzipファイル(taste-getting-started.zip)をダウンロードして展開
- Mavenを使って"mvn install"
- ミネソタ大学のgrouplensというラボから評価用データセット(ml-data_0.zip)を入手
- どこかに立てたMySQLにテーブルを作る(taste-getting-started\src\main\resources\sql\initialize_movielens_db.sqlにて)
・この中でmovielensという名前でデータベースやユーザが作られる
・しかしこのとき作られるユーザはlocalhost縛りになるので注意('@%'に要変更)
・ロードするu.itemは変なディレクトリにあることになっているので要変更 - taste-getting-started\src\main\webapp\WEB-INF\persistence-context.xmlの接続情報を変更
- Mavenを使って"mvn jetty:run"
月曜日, 3月 22, 2010
appenginejsのアプリ初期化過程
最初のリクエストが飛んできたときに、appenginejsに同梱されているJSGIServlet.javaで行われていることをまとめておきます。
- コンテキストが作られる
- narwhalの(engine個別の/ここではrhinoの)bootstrap.jsが実行される
ここでNARWHAL_HOMEはWEB-INF/packages/narwhalと決め打ち
その中で(NARWHAL_HOMEの)narwhal.jsが実行され環境が作られる - jackのservletハンドラ(proessメソッド)がrequireされる
アプリは(serviceメソッドから呼ばれ)このハンドラ内で実際に実行される - アプリのWEB-INF/src/jackconfig.jsがrequireされる
(web.xmlの)appとenvironmentに合わせた変数がexportされる - 上記exportされた変数(environmentとapp)を使ってアプリが初期化される
月曜日, 3月 08, 2010
hotデプロイなGAE開発!
appenginejsでは、war\WEB-INF\jackconfig.jsの中でlocalとhostedという2つの稼働方法をexportしている。
そして、同ディレクトリにあるweb.xmlの中でenvironmentという名前で指定して利用するのだが、これをlocalにしておけば、ソース修正が(jettyの再起動を行わずとも)反映できるようになる。
なお、jackconfig.jsというファイル、何かの設定ファイルのようだけどなんのことはなく、このファイルが、jack経由でJettyで実行されているだけである(このファイルのexports.app)。
そして、同ディレクトリにあるweb.xmlの中でenvironmentという名前で指定して利用するのだが、これをlocalにしておけば、ソース修正が(jettyの再起動を行わずとも)反映できるようになる。
なお、jackconfig.jsというファイル、何かの設定ファイルのようだけどなんのことはなく、このファイルが、jack経由でJettyで実行されているだけである(このファイルのexports.app)。
日曜日, 3月 07, 2010
appenginejsでtimezone
blog-gaeでのtimezone設定のやり方が分からず、ずっと時間表示がUTCだった。
調べてみると起動するservletであるjack-servletを最新ソースではこの設定が可能になっていたので差し替え。
ソースを落としたら、D:\work\blog-gaeに展開して、
調べてみると起動するservletであるjack-servletを最新ソースではこの設定が可能になっていたので差し替え。
ソースを落としたら、D:\work\blog-gaeに展開して、
ant runserverとするだけで、開発用サーバが8080ポートで上がります。ちなみに、WEB-INF\packages\narwhal\lib\sandbox.jsの6行目にある以下を修正すれば、配置場所は変更可能です(Windowsのディレクトリ記述がうまくパースできないようなので、このファイルは無理に改造しています)。
var zzWork = 'd:\\work\\blog-gae\\war\\WEB-INF\\';
土曜日, 3月 06, 2010
火曜日, 2月 23, 2010
appenginejsでrunInTransaction!!
appenginejsで、どうしても出来なかった同一トランザクションでのdb登録がやっとできた。GAEでは同一トランザクションでのデータ更新は、
・同じエンティティグループのエンティティ群しかダメ
・(そのためには先に作ってる同一に入れたい)エンティティからキーをもらう必要あり
このための記述が、昔はModelをnewするときの第一引数にキーを渡す方式だった(よう)だが、
×(昔)var objCom = new Comment(msg.key(),{
msgRef:msg.key(),
content: "This is test comment"
});
今のバージョンではプロパティにparentと切ってそこにキーを渡すことになっている。
○(今)var objCom = new Comment({
parent:msg.key(),
msgRef:msg.key(),
content: "This is test comment"
});
これで、以下のような一括更新が成功するようになる!!
ちなみに、
agl0ZjAwNTQtMDZyDQsSB01lc3NhZ2UYCQw
というキーを与えたら、
agl0ZjAwNTQtMDZyGgsSB01lc3NhZ2UYCQwLEgdDb21tZW50GAoM
というキーを持った(同一エンティティグループに入った)エンティティができている。
・同じエンティティグループのエンティティ群しかダメ
・(そのためには先に作ってる同一に入れたい)エンティティからキーをもらう必要あり
このための記述が、昔はModelをnewするときの第一引数にキーを渡す方式だった(よう)だが、
×(昔)var objCom = new Comment(msg.key(),{
msgRef:msg.key(),
content: "This is test comment"
});
今のバージョンではプロパティにparentと切ってそこにキーを渡すことになっている。
○(今)var objCom = new Comment({
parent:msg.key(),
msgRef:msg.key(),
content: "This is test comment"
});
これで、以下のような一括更新が成功するようになる!!
db.runInTransaction(function() {
db.put([objCom,msg]);
});
ちなみに、
agl0ZjAwNTQtMDZyDQsSB01lc3NhZ2UYCQw
というキーを与えたら、
agl0ZjAwNTQtMDZyGgsSB01lc3NhZ2UYCQwLEgdDb21tZW50GAoM
というキーを持った(同一エンティティグループに入った)エンティティができている。
土曜日, 2月 13, 2010
App Engine JavaScript SDK
GAEのアプリをJavascriptで書けるSDK"appenginejs"というのがあるのだけど、公開されているサンプルアプリ(blog-gae)をWindowsで動かすのにえらく苦労したのでメモ。
"app is not function"とかエラーがでるがこれはウソ。
で、やることは大きく2つ。
パスがうまく取り込めないので置換する(配置場所に合わせて適宜変えて下さい)。
変更するファイルは"blog-gae\war\WEB-INF\packages\narwhal\lib\sandbox.js"の1つだけ。
"\blog-gae\war\WEB-INF\src"は本来classesへのシンボリックリンク。
なのだけど、Windowsなのでこれはclassesをsrcという別名でコピーしておく。
これで、起動はやはり重いけど動くことは動く。日本語も使えている様子。
"app is not function"とかエラーがでるがこれはウソ。
で、やることは大きく2つ。
パスがうまく取り込めないので置換する(配置場所に合わせて適宜変えて下さい)。
変更するファイルは"blog-gae\war\WEB-INF\packages\narwhal\lib\sandbox.js"の1つだけ。
loader.find = function (topId) {
if(topId.match(/\-gae/)){
var zztmp = new Array();
//zztmp = topId.match(/([^\\]+)$/);
zztmp = topId.match(/INF\\(.+)$/);
topId = 'd:\\work\\blog-gae\\war\\WEB-INF\\'+zztmp[1];
}
...
self.find = function (topId) {
if(topId.match(/\-gae/)){
var zztmp = new Array();
zztmp = topId.match(/INF\\(.+)$/);
topId = 'd:\\work\\blog-gae\\war\\WEB-INF\\'+zztmp[1];
}
...
"\blog-gae\war\WEB-INF\src"は本来classesへのシンボリックリンク。
なのだけど、Windowsなのでこれはclassesをsrcという別名でコピーしておく。
これで、起動はやはり重いけど動くことは動く。日本語も使えている様子。
月曜日, 1月 11, 2010
[haXe] aswingを使う(ウィンドウ内に描画)
同様にhaXeにてaswingのウィンドウ内に絵を描く(linetoなど)には、FrameのContainerをとって、そこに、絵が描かれたGroudDecoratorをBackgroundDecoratorとしてセット、するみたいです。。。中国のaswingサイトに例がありました。
import flash.display.Sprite;普通にSpriteをJFrameなどにaddChildもできますが、これでは折りたたんだときに表示が残るので。。
import org.aswing.ASColor;
import org.aswing.Component;
import org.aswing.Container;
import org.aswing.JFrame;
import org.aswing.graphics.Graphics2D;
import org.aswing.graphics.IBrush;
import org.aswing.graphics.SolidBrush;
class FillTest extends Sprite
{
public function new()
{
super();
var frame:JFrame = new JFrame(this);
frame.setSizeWH(400, 370);
var c:Container = frame.getContentPane();
c.setBackgroundDecorator(new MyCanvas());
frame.show();
}
}
import org.aswing.GroundDecorator;
import flash.display.Shape;
import org.aswing.Component;
import org.aswing.graphics.Graphics2D;
import org.aswing.geom.IntRectangle;
import flash.display.DisplayObject;
import org.aswing.graphics.IBrush;
import org.aswing.graphics.SolidBrush;
import org.aswing.ASColor;
import org.aswing.graphics.GradientBrush;
import flash.geom.Matrix;
class MyCanvas implements GroundDecorator {
private var shape:Shape;
private var H_GAP:UInt;
private var V_GAP:UInt;
private var WIDTH:UInt;
private var HEIGHT:UInt;
public function new()
{
shape = new Shape();
H_GAP = 10;
V_GAP = 10;
WIDTH = 100;
HEIGHT = 100;
}
public function updateDecorator(com:Component, g:Graphics2D, bounds:IntRectangle):Void {
var g2:Graphics2D = new Graphics2D(this.shape.graphics);
g2.clear();
var rectBounds:IntRectangle = new IntRectangle();
//fill solid rect
rectBounds.x = bounds.x + H_GAP;
rectBounds.y = bounds.y + V_GAP;
rectBounds.width = WIDTH;
rectBounds.height = HEIGHT;
var solidBrush:IBrush = new SolidBrush(ASColor.RED);
g2.fillRectangle(solidBrush, rectBounds.x, rectBounds.y, rectBounds.width, rectBounds.height);
//fill liner grandient rect
rectBounds.y += HEIGHT; // move shape rect
rectBounds.y += V_GAP;
var colors:Array<UInt> = [0x000000, 0xFF0000, 0x00FF00, 0x0000FF, 0x000000];
var alphas:Array<Int> = [0, 1, 1, 1, 0];
var ratios:Array<Int> = [0x00, 0x3F, 0x7E, 0xBD, 0xFF];
var matrix:Matrix = new Matrix();
matrix.createGradientBox(rectBounds.width, rectBounds.height, 0, rectBounds.x, rectBounds.y);
var linear:IBrush = new GradientBrush(GradientBrush.LINEAR, colors, alphas, ratios, matrix);
g.fillRectangle(linear, rectBounds.x, rectBounds.y, rectBounds.width, rectBounds.height);
//fill radial grandient
rectBounds.y += HEIGHT; // move shape rect
rectBounds.y += V_GAP;
matrix.createGradientBox(rectBounds.width, rectBounds.height, 0, rectBounds.x, rectBounds.y);
var radial:IBrush = new GradientBrush(GradientBrush.RADIAL, colors, alphas, ratios, matrix);
g.fillRectangle(radial, rectBounds.x, rectBounds.y, rectBounds.width, rectBounds.height);
}
public function getDisplay(c:Component):DisplayObject {
return this.shape;
}
}
[haXe] aswingを使う
haXe(v2.04)でaswing(1_5というパック?)を使う例。
/**
* Sample002.as
* Thanks!! -> http://www.matzmtok.com/blog/?p=18
*/
import org.aswing.event.InteractiveEvent ;
import org.aswing.ASColor;
import org.aswing.Container;
import org.aswing.EmptyLayout;
import org.aswing.JSlider;
import org.aswing.JWindow;
import org.aswing.SoftBoxLayout;
import org.aswing.ButtonGroup;
import org.aswing.BoxLayout;
import org.aswing.BorderLayout;
import org.aswing.JLabel;
import org.aswing.JFrame;
import org.aswing.JPanel;
import org.aswing.JButton;
import org.aswing.JRadioButton;
import org.aswing.geom.IntPoint;
import org.aswing.geom.IntDimension;
class Sample002 extends JWindow{
private var rbX:JRadioButton;
private var rbY:JRadioButton;
private var group:ButtonGroup;
private var zzlayout:BoxLayout;
private var frame:JFrame;
public var bg:ButtonGroup;
public var title:String;
public function new() {
super(flash.Lib.current, true);
title = 'Example of BoxLayout';
initUI();
}
private function initUI():Void {
setBackground(new ASColor(0xffffff));
var rbPanel:JPanel = new JPanel();
rbX = new JRadioButton('BoxLayout.X_AXIS');
rbY = new JRadioButton('BoxLayout.Y_AXIS');
zzlayout = new BoxLayout();
rbX.addSelectionListener(onRadioChanged);
rbY.addSelectionListener(onRadioChanged);
rbX.setSelected(true);
group = new ButtonGroup();
group.append(rbX);
group.append(rbY);
rbPanel.append(new JLabel('方向'));
rbPanel.append(rbX);
rbPanel.append(rbY);
rbPanel.setLocation(new IntPoint(0, 0));
rbPanel.setSize(new IntDimension(400, 30));
getContentPane().append(rbPanel);
var gapPanel:JPanel = new JPanel();
gapPanel.append(new JLabel('間隔'));
var slider:JSlider = new JSlider(JSlider.HORIZONTAL, 0, 30, 0);
slider.addStateListener(onSliderChange);
gapPanel.append(slider);
gapPanel.setLocation(new IntPoint(0, 20));
gapPanel.setSize(new IntDimension(400, 30));
gapPanel.setBackground(new org.aswing.ASColor(0xFF00FF)); // 効かない?
gapPanel.setToolTipText("xxxTIPxxx");
getContentPane().append(gapPanel);
frame = new JFrame(this, title);
getContentPane().append(frame);
frame.getContentPane().setLayout(zzlayout);
frame.setLocation(new IntPoint(30, 50));
frame.setSize(new IntDimension(250, 150));
// たくさん並べるとよしなに幅や高さを等分してくれる
frame.getContentPane().append(new JButton('Button 1'));
frame.getContentPane().append(new JButton('Button 2'));
frame.getContentPane().append(new JButton('Button 3'));
frame.getContentPane().append(new JButton('Button 4'));
frame.show();
}
public function onRadioChanged(e:InteractiveEvent):Void {
var rb:JRadioButton = cast(e.target,JRadioButton);
switch(rb){
case rbX:
zzlayout.setAxis(BoxLayout.X_AXIS);
case rbY:
zzlayout.setAxis(BoxLayout.Y_AXIS);
default:
trace("cannot change layout!");
}
updateLayout();
}
public function onSliderChange(e:InteractiveEvent):Void {
var s:JSlider = cast(e.target,JSlider);
zzlayout.setGap(s.getValue());
updateLayout();
}
private function updateLayout():Void {
frame.getContentPane().setLayout(zzlayout);
}
}
これを、以下のように呼び出す。
import org.aswing.geom.IntPoint;
import org.aswing.geom.IntDimension;
class Main
{
static function main()
{
var app:Sample002 = new Sample002();
app.setLocation(new IntPoint(0, 0));
app.setSize(new IntDimension(500, 500));
app.show();
// flash.Lib.current.addChild(new Sample002());
}
}
ちなみに、haxeコマンドに与えるcompile.hxmlはこんな感じ。
-swf-header 500:500:20:FFFFFF
-swf Main.swf
-cp hxclasses
-swf-lib concat.swf
-swf-version 9
-main Main
水曜日, 1月 06, 2010
[haXe] haxelibが変?
haXeでは、swfにしたアセットライブラリは、"-swf-lib"オプションに指定するのだが、これが1つしか受け付けられない様子。画像ファイルのswfとflコンポーネントのswfを同時に使いたかったので、この2つのアセットswfを纏める方法。
以下xmlを用意(ここではconcat.xmlとした)。
そして、swfmillを実行。
意外に簡単!xmlのwidthとかは適当で良いようです。
以下xmlを用意(ここではconcat.xmlとした)。
<movie width="320" height="240" framerate="12" version="9">
<frame><library>
<clip id="foo" import="spectrum.swf"/>
<clip id="foo" import="component.swf"/>
</library></frame>
</movie>
そして、swfmillを実行。
> swfmill.exe simple concat.xml concat.swf
意外に簡単!xmlのwidthとかは適当で良いようです。
登録:
投稿 (Atom)