火曜日, 12月 21, 2010

mahoutでkmeans!

以下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月 07, 2010

mahoutでTSP!

mahoutの中には、TSP(Travelling Salesman Problem / 巡回セールスマン問題)のが入っています。一応GUIも付いてます。でも、なぜかsrc版からコンパイルしないと動きません。


うーん。。地味。もっと派手なのかと思ったのですが(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"さんの情報を参考にしました。ありがとうございます!
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(?)の情報も公開されていました。

火曜日, 11月 02, 2010

JavascriptでHIVEを操作する(JDBC@Thrift)

rhinoを使って、JavascriptからHIVEを操作してみるテスト。普通にできた(あたりまえ)。

/*
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メソッドを修正(多少本体側も変更されているので以下参考)。

 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+*])

// 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で起動)。

// 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コマンドで直接起動する分には問題ない。

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というサイトに、サンプルアプリが公開されていたのでメモ。
  1. sampleのzipファイル(taste-getting-started.zip)をダウンロードして展開
  2. Mavenを使って"mvn install"
  3. ミネソタ大学のgrouplensというラボから評価用データセット(ml-data_0.zip)を入手
  4. どこかに立てたMySQLにテーブルを作る(taste-getting-started\src\main\resources\sql\initialize_movielens_db.sqlにて)
     ・この中でmovielensという名前でデータベースやユーザが作られる
     ・しかしこのとき作られるユーザはlocalhost縛りになるので注意('@%'に要変更)
     ・ロードするu.itemは変なディレクトリにあることになっているので要変更
  5. taste-getting-started\src\main\webapp\WEB-INF\persistence-context.xmlの接続情報を変更
  6. Mavenを使って"mvn jetty:run"
意外に簡単なのですが、あるべきMoviePage.htmlが入ってなく、、、かつ映画名が良く分からないので、それがレコメンドとして正しいのかの判断が不明です。

月曜日, 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)。

日曜日, 3月 07, 2010

appenginejsでtimezone

blog-gaeでのtimezone設定のやり方が分からず、ずっと時間表示がUTCだった。
調べてみると起動する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

blog-gae

appenginejsのサンプルが動いたので、ソースを上げました。
GAEって、普通にzipファイルを上げたらダウンロードできるようになるんですね。

火曜日, 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"
});

これで、以下のような一括更新が成功するようになる!!
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つだけ。

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;

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;
}
}
普通にSpriteをJFrameなどにaddChildもできますが、これでは折りたたんだときに表示が残るので。。

[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とした)。
<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とかは適当で良いようです。