2010/08/01

WSJSでGAE/Jに携帯サイトを作ってみた

GAE版の WSJS で携帯用の交通費申請のデモを作ってみた。

大体こんな感じの画面がちゃんと携帯でも出て来ます。


一応、申請データをサーバ側でBigtableに保存したりしているので一覧も出せます。



でも携帯からGAEにログインしようとするとPC用の画面が
出ちゃってグチャグチャなんだよね..
ログイン自体は出来るんだけど..


ここから実際の画面が見れます。AU以外は確認してないけど。
http://wsjs-gae.appspot.com/keitai-demo/menu.html


ソースはここから keita-demo のディレクトリで見れます。
http://wsjs-gae.appspot.com/_wsjs_/develop/index.html



2010/07/25

今更「金持ち父さん、貧乏父さん」を読んだ

資産運用とか全然興味が湧かないので意識改革になるかと思い、
今更だが「金持ち父さん、貧乏父さん」を読んでみた。

読み物としては結構面白い。
経済とか興味無くても読める様になっている。

思いっきり要約してしまうと
- 資本主義は頭の良い奴が頭の悪い奴から金を搾取するシステムである。
- だから、頭の良い方に回らないと一生、貧乏(奴隷)のままだよ。
と言うことらしい。

ちなみに頭の悪い方の代表は「サラリーマン」ね。
頭の良い方はいろんな投資で資産を増やしまくってる人。

当り前ちゃー、当り前なんだけど..

一つ気に入らないのは本文には「搾取」なんて言葉は出て来ない事。
綺麗事ばかり並べてみんなが自分と同じようにすればお金持ちになれるよーって感じ。
みんなが投資家になっちゃったらだれが衣食住の物資を生産するんだ?とか言う視点は無い。

結局、トータルすると読みやすいだけでエゴ丸出しの投資本と変わらない。
読んで無駄って程じゃ無いけど、すごく為になったって気もしないかな。



2010/07/18

Bigtableのoffset/limitの罠

Bigtableには offset/limit の機能がある。
これは SQL に有る OFFSET/LIMIT と全く同じ機能だ。

Bigtable には 1000 件以上データを1度に取得出来ない
という制限が有り、おいらはこれを offset/limit で回避
する事ができると思い込んでいた。

しかし offset(1000) とするとこんなエラーが..

offset may not be above 1000

どうやら offset/limit は 1000 件制限の中から一部を
切り取ってくるだけの機能らしい。

意味ねーじゃん!!

一応この辺にページングの仕方は書いてあるが..

http://code.google.com/intl/ja/appengine/articles/paging.html
| まとめ
| 要約すると、ページングを行うには、一意の値を持つプロパティが
| エンティティごとに必要です。このプロパティは、ページングの方
| 向にインデックス登録されている必要があります。また、必ず N+1
| エンティティをフェッチし、「次の」ページのデータが十分にある
| ことを確認します。プロパティが一意でない場合は、1 つのカウン
| タ、またはユーザーなどの属性上でシャードされたカウンタを使用
| して、そのプロパティを一意にします。

つまり予めページング用の index をプロパティとして組み込んでおけ
って事らしい。
メンドクセー、つか、ソート条件が複合化したらどうすんの?
何十もの index を用意するのか?

1000 件制限は使えなさすぎる。
これ何とかしないと GAE の普及は無いと思うわ..




2010/07/11

WSJS for GAE のスナップを公開します。

ずっとほったらかしになっていた WSJS ですが GAE 版を公開します。
ただしドキュメントや設定ファイルは中途半端です。

プラットフォームは今後 GAE に絞って行こうかと思ってます。
読み込みのモッサリ感はいかんともしがたいのですが最近のトレンド
だし WSJS と相性の良い部分が結構あるので..


基本的には zip を展開して war/ をデプロイすれば使えるはずです。
詳しくはreadme.txtを見て下さい。
動かねー とか有ったらこの記事にコメントしてください。


ダウンロードは
http://wsjs.dip.jp:8008/release/index.html
から
wsjs-gae-SNAP-20100711.zip
をクリックして下さい

こちらに実際デプロイ済みの環境があります。
http://wsjs-gae.appspot.com/
「開発ページへ」をクリックして下さい。
書き込めませんがモッサリ感は味わえるかと思います。



2010/07/04

Javaのアノテーションでsetter/getterを自動生成してみた

この記事の内容は古くなりました。
  • JDK8 からは新しい API の Annotation Processor しか使えません。
  • 新しい API のサンプルは こちら からどうぞ。

おいらは基本 Eclipse 使わないので Bean の getter/setter とか結構かったるい。

なので自前のアノテーションを定義して自動生成する方法を考えてみた。

イメージとしてはフィールドのみを宣言した XxxCore.java にクラスアノテーション @AutoBean を設定すると自動的に XxxCore を継承して getter/setter を持った XxxBean.java が生成される。

具体的にはこんな感じ。

TestCore.java:

package test; import kotemaru.autobean.annotation.*; @AutoBean(bean="test.TestBean") public abstract class TestCore { protected String firstName; protected String lastName; protected int age; protected String email; protected String tel; }

TestBean.java:

package test; public class TestBean extends test.TestCore { public java.lang.String getFirstName() {return this.firstName;} public void setFirstName(java.lang.String firstName) {this.firstName = firstName;} public java.lang.String getLastName() {return this.lastName;} public void setLastName(java.lang.String lastName) {this.lastName = lastName;} public int getAge() {return this.age;} public void setAge(int age) {this.age = age;} public java.lang.String getEmail() {return this.email;} public void setEmail(java.lang.String email) {this.email = email;} public java.lang.String getTel() {return this.tel;} public void setTel(java.lang.String tel) {this.tel = tel;} }

実装方法

アノテーションの定義はこんな感じになる。
@Retentionや@Targetの意味は javadoc 参照。 package kotemaru.autobean.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.ElementType; @Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface AutoBean { boolean setter() default true; boolean getter() default true; String bean(); // class }

メソッドの宣言はアノテーションに対する引数になりコード生成時に参照できる。 default の宣言をすれば省略可能になる。
使い方:

@AutoBean(setter=bool,getter=bool,bean="クラス名")

アノテーション プロセッサ

コード生成をするには3つのクラスが必要になる。
  • Factory:AnnotationProcessorFactory インターフェースを実装する。
  • Processor: AnnotationProcessor インターフェースを実装する。
  • Visitor:SimpleDeclarationVisitor クラスを継承する。
Factory
FactoryはアノテーションとProceesorを関連付ける処理を書くだけである。

AutoBeanApFactory.java:

package kotemaru.autobean.apt; import java.util.Set; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import com.sun.mirror.apt.AnnotationProcessorFactory; import com.sun.mirror.apt.AnnotationProcessor; import com.sun.mirror.apt.AnnotationProcessors; import com.sun.mirror.apt.AnnotationProcessorEnvironment; import com.sun.mirror.declaration.AnnotationTypeDeclaration; public class AutoBeanApFactory implements AnnotationProcessorFactory { private static final String PKG = "kotemaru.autobean.annotation."; private static final String AUTO_BEAN = PKG+"AutoBean"; private Collection supportedAnnotationTypes = Arrays.asList( AUTO_BEAN ); private Collection supportedOptions = Collections.emptySet(); public Collection supportedAnnotationTypes() { return supportedAnnotationTypes; } public Collection supportedOptions() { return supportedOptions; } public AnnotationProcessor getProcessorFor(Set atds, AnnotationProcessorEnvironment env) { if (atds.contains(env.getTypeDeclaration(AUTO_BEAN))) { return new AutoBeanAp(env); } else { return AnnotationProcessors.NO_OP; } } }
Processor
プロセッサにはコンストラクタで AnnotationProcessorEnvironment が渡される。 これに処理対象クラスのリストが入っている。 実際の処理は process()メソッドで行う。 通常は env.getSpecifiedTypeDeclarations() でクラスのリストを得て処理を回す。

AutoBeanAp.java:

package kotemaru.autobean.apt; import java.util.Collection; import java.io.IOException; import java.io.PrintWriter; import com.sun.mirror.apt.AnnotationProcessor; import com.sun.mirror.apt.AnnotationProcessorEnvironment; import com.sun.mirror.apt.Filer; import com.sun.mirror.declaration.TypeDeclaration; import com.sun.mirror.declaration.FieldDeclaration; import com.sun.mirror.declaration.ConstructorDeclaration; import com.sun.mirror.declaration.Modifier; import com.sun.mirror.util.DeclarationVisitor; import com.sun.mirror.util.DeclarationVisitors; import com.sun.mirror.util.SimpleDeclarationVisitor; import com.sun.mirror.type.TypeMirror; import com.sun.mirror.util.*; import com.sun.mirror.type.*; import com.sun.mirror.declaration.*; import kotemaru.autobean.annotation.*; public class AutoBeanAp implements AnnotationProcessor { private final AnnotationProcessorEnvironment env; AutoBeanAp(AnnotationProcessorEnvironment env) { this.env = env; } public void process() { for (TypeDeclaration types : env.getSpecifiedTypeDeclarations()) { processType(types) ; } } private void processType (TypeDeclaration type) { try { String className = type.getSimpleName(); if (!className.endsWith("Core")) return; //2重処理回避用 BeanVisitor.process(type, env); } catch (IOException e) { e.printStackTrace(); } } }
Visitor
Visitor が実際にコードを生成するクラスになる。
visitFieldDeclaration(),visitMethodDeclaration() と言ったメソッドをOverrideすると 処理対象クラスのフィールドやメソッドの情報を引数に持ってコールバック してくれる仕掛けが用意されている。 DeclarationVisitor scanner = DeclarationVisitors.getSourceOrderDeclarationScanner(visitor, DeclarationVisitors.NO_OP); coreClassDecl.accept(scanner); 後は、AnnotationProcessorEnvironment から取り出した Writer に書き出して行くだけである。

BeanVisitor.java:

package kotemaru.autobean.apt; import java.util.Collection; import java.io.IOException; import java.io.PrintWriter; import com.sun.mirror.apt.AnnotationProcessor; import com.sun.mirror.apt.AnnotationProcessorEnvironment; import com.sun.mirror.apt.Filer; import com.sun.mirror.declaration.TypeDeclaration; import com.sun.mirror.declaration.FieldDeclaration; import com.sun.mirror.declaration.ConstructorDeclaration; import com.sun.mirror.declaration.Modifier; import com.sun.mirror.util.DeclarationVisitor; import com.sun.mirror.util.DeclarationVisitors; import com.sun.mirror.util.SimpleDeclarationVisitor; import com.sun.mirror.type.TypeMirror; import com.sun.mirror.util.*; import com.sun.mirror.type.*; import com.sun.mirror.declaration.*; import kotemaru.autobean.annotation.*; import java.util.*; public class BeanVisitor extends SimpleDeclarationVisitor { private static final String TMPL_HEADER = "// BeanVisitor generated.\n" +"package ${packageName};\n" +"public class ${className}\n" +" extends ${coreClassName}\n" +"{\n"; private static final String TMPL_GETTER = " public ${type} get${captalName}() {return this.${name};}\n"; private static final String TMPL_SETTER = " public void set${captalName}(${type} ${name}) {this.${name} = ${name};}\n"; private static final String TMPL_ABSTRACT_METHOD = " ${modifiers} ${returnType} ${name}(${parameters}){\n" +" throw new java.lang.UnsupportedOperationException(\"${name}\");\n" +" }\n"; private static final String TMPL_FOOTER = "}\n"; private AutoBean generatorAnno; private PrintWriter writer; protected BeanVisitor(AutoBean anno, PrintWriter writer) { this.generatorAnno = anno; this.writer = writer; } public static void process(TypeDeclaration coreClassDecl, env) throws IOException { AutoBean anno = (AutoBean) coreClassDecl.getAnnotation(AutoBean.class); String fullClassName = anno.bean(); if (fullClassName == null) return; String pkgName = coreClassDecl.getPackage().getQualifiedName(); String className = fullClassName; int pos = fullClassName.lastIndexOf('.'); if (pos > 0) { pkgName = fullClassName.substring(0,pos); className = fullClassName.substring(pos+1); } //String interfaceName = anno.api(); String coreClassName = coreClassDecl.getQualifiedName(); Filer filer = env.getFiler(); PrintWriter writer = filer.createSourceFile(pkgName+"."+className); BeanVisitor visitor = new BeanVisitor(anno, writer); Map map = new HashMap(); map.put("coreClassName",coreClassName); //map.put("interfaceName",interfaceName); map.put("className",className); map.put("packageName",pkgName); visitor.header(map); DeclarationVisitor scanner = DeclarationVisitors.getSourceOrderDeclarationScanner(visitor, DeclarationVisitors.NO_OP); coreClassDecl.accept(scanner); visitor.footer(map); writer.close(); } public void header(Map map) { writer.write(AptUtil.apply(TMPL_HEADER, map)); } public void footer(Map map) { writer.write(AptUtil.apply(TMPL_FOOTER, map)); } public void visitFieldDeclaration(FieldDeclaration d) { if (AptUtil.isPrivate(d)) return; if (generatorAnno.getter()) writer.write(AptUtil.apply(TMPL_GETTER, d)); if (generatorAnno.setter()) writer.write(AptUtil.apply(TMPL_SETTER, d)); } public void visitMethodDeclaration(MethodDeclaration d) { if (AptUtil.isPrivate(d)) return; if (!AptUtil.isAbstract(d)) return; writer.write(AptUtil.apply(TMPL_ABSTRACT_METHOD, d, Modifier.ABSTRACT)); } }

AptUtil.java:

package kotemaru.autobean.apt; import java.util.Collection; import java.io.IOException; import java.io.PrintWriter; import com.sun.mirror.apt.AnnotationProcessor; import com.sun.mirror.apt.AnnotationProcessorEnvironment; import com.sun.mirror.apt.Filer; import com.sun.mirror.declaration.TypeDeclaration; import com.sun.mirror.declaration.FieldDeclaration; import com.sun.mirror.declaration.ConstructorDeclaration; import com.sun.mirror.declaration.Modifier; import com.sun.mirror.util.DeclarationVisitor; import com.sun.mirror.util.DeclarationVisitors; import com.sun.mirror.util.SimpleDeclarationVisitor; import com.sun.mirror.type.TypeMirror; import com.sun.mirror.util.*; import com.sun.mirror.type.*; import com.sun.mirror.declaration.*; import kotemaru.autobean.annotation.*; import java.util.*; public class AptUtil { public static String apply(String templ, Map map) { Iterator<Map.Entry<String,String>> ite = map.entrySet().iterator(); while(ite.hasNext()){ Map.Entry<String,String> ent = (Map.Entry<String,String>) ite.next(); String key = "[$][{]"+ent.getKey()+"[}]"; String value = ent.getValue(); templ = templ.replaceAll(key, value); } return templ; } public static String apply(String templ, Declaration d) { return apply(templ, d, null); } public static String apply(String templ, Declaration d, Modifier ignore) { templ = templ.replaceAll("[$][{]modifiers[}]", getModifiers(d, ignore)); templ = templ.replaceAll("[$][{]name[}]", d.getSimpleName()); templ = templ.replaceAll("[$][{]captalName[}]", getCaptalName(d.getSimpleName())); if (d instanceof FieldDeclaration) { FieldDeclaration decl = (FieldDeclaration) d; templ = templ.replaceAll("[$][{]type[}]", decl.getType().toString()); } if (d instanceof MethodDeclaration) { MethodDeclaration decl = (MethodDeclaration) d; templ = templ.replaceAll("[$][{]returnType[}]", decl.getReturnType().toString()); } if (d instanceof ExecutableDeclaration) { ExecutableDeclaration decl = (ExecutableDeclaration) d; templ = templ.replaceAll("[$][{]parameters[}]", getParams(decl)); templ = templ.replaceAll("[$][{]arguments[}]", getArguments(decl)); templ = templ.replaceAll("[$][{]throws[}]", getThrows(decl)); } return templ; } public static boolean isPrivate(Declaration d) { Collection<Modifier> mods = d.getModifiers(); for (Modifier mod : mods) { if (Modifier.PRIVATE.equals(mod)) { return true; } else if (Modifier.PROTECTED.equals(mod)) { return false; } else if (Modifier.PUBLIC.equals(mod)) { return false; } } return false; } public static boolean isAbstract(Declaration d) { Collection<Modifier> mods = d.getModifiers(); for (Modifier mod : mods) { if (Modifier.ABSTRACT.equals(mod)) { return true; } } return false; } public static String getModifiers(Declaration d, Modifier ignore) { Collection<Modifier> mods = d.getModifiers(); if (mods.size() == 0) return ""; StringBuffer sbuf = new StringBuffer(mods.size()*20); for (Modifier mod : mods) { if (!mod.equals(ignore)) sbuf.append(mod); sbuf.append(' '); } sbuf.setLength(sbuf.length()-1); return sbuf.toString(); } public static String getParams(ExecutableDeclaration d) { Collection<ParameterDeclaration> params = d.getParameters(); if (params.size() == 0) return ""; StringBuffer sbuf = new StringBuffer(params.size()*20); for (ParameterDeclaration param : params) { sbuf.append(param.getType()); sbuf.append(' '); sbuf.append(param.getSimpleName()); sbuf.append(','); } sbuf.setLength(sbuf.length()-1); return sbuf.toString(); } public static String getArguments(ExecutableDeclaration d) { Collection<ParameterDeclaration> params = d.getParameters(); if (params.size() == 0) return ""; StringBuffer sbuf = new StringBuffer(params.size()*20); for (ParameterDeclaration param : params) { sbuf.append(param.getSimpleName()); sbuf.append(','); } sbuf.setLength(sbuf.length()-1); return sbuf.toString(); } public static String getThrows(ExecutableDeclaration d) { Collection<ReferenceType> params = d.getThrownTypes(); if (params.size() == 0) return ""; StringBuffer sbuf = new StringBuffer(params.size()*20); sbuf.append("throws "); for (ReferenceType param : params) { sbuf.append(param.toString()); sbuf.append(','); } sbuf.setLength(sbuf.length()-1); return sbuf.toString(); } public static String getCaptalName( String name ) { return name.substring(0,1).toUpperCase() + name.substring(1); } }

実行

コンパイルと実行には tools.jar が必要になるので注意。

コマンドラインからの実行はこんな感じになる。

apt -cp lib/tools.jar:build/classes\ -s build/src\ -d build/classes\ -factory kotemaru.autobean.apt.AutoBeanApFactory\ tests/src/test/TestCore.java Eclipce は設定で AutoBeanApFactory を追加すればいいんじゃないかと思う。

雑感

応用範囲は広そう。
類似パターンを水平展開するような大規模プロジェクトに応用すると劇的な効果が有りそうな気がするなー。

関連記事

  • Java Annotation Processor を eclipse で開発する。
  • アノテーション+Velocityでソースコードの自動生成
    ソース一式: apt-sample.zip

    参考URL:

  • http://www.nurs.or.jp/~sug/soft/super/jbean.htm
  • http://www.edu.tuis.ac.jp/~mackin/javadocs/jdk1_5/docs/ja/guide/apt/GettingStarted.html

  • 2010/06/27

    クライアント証明書を使ったSSH認証

    とある事情で ssh を外部に公開する必要がでてきた。
    でも 22 番ポートなんて一番突っ込まれ安いんで開けたくねー
    (実際、公開中に総当たり攻撃受けたし)

    さすがに単純な user/pass の設定では公開したく無かったので
    クライアント証明書を使うことにした。

    とりあえず sshd の設定変更。
    /etc/ssh/sshd_config の変更点は以下の通り。

    PasswordAuthentication no
    PubkeyAuthentication yes
    ChallengeResponseAuthentication no


    ChallengeResponseAuthentication は FreeBSD の場合だけ必要になる。
    (デフォルトの違いらしい)


    クライアント側で鍵を作る。

    $ cd ~/.ssh/
    $ ssh-keygen -b 1024 -t dsa -N 鍵パスワード -f key-ssh
    $ mv key-ssh identity
    $ chown 600 identity

    公開鍵が key-ssh.pub に出来ているはずなのでサーバ側に送る。


    サーバ側で公開鍵を承認する。

    $ cd ~/.ssh/
    $ cp key-ssh.pub authorized_keys
    $ chmod 600 ~/.ssh/authorized_keys
    $ chmod g-w ~ ~/.ssh

    パーミッションの変更を忘れると動かないので要注意。

    後は普通にクライアント側から ssh を使うと鍵のパスワードを聞かれるようになる。

    $ ssh ホスト名
    Enter passphrase for key '/home/user/.ssh/identity':

    パスワードを入力すればログイン。


    ちなみにクライアントにWindowsを使いたい場合は Putty とかがよさげ。
    - このへんから http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html
    - インストールは展開してコピーするだけ。
    - この中の puttygen.exe を起動して鍵を生成する。



    - まずは Generate をポチッと押す。



    - 次にマウスをグリグリする。



    - 生成された鍵をサーバの ~/.ssh/authorized_keys に追加する。



    - パスワードを入れて秘密鍵を保存する。
    -- ファイル名はここでは ssh-key.ppk とする。

    - 以下のようなバッチファイルを作って Putty を起動する。

    c:\xxxx\putty.exe -i c:\xxxx\ssh-key.ppk ユーザ名@ホスト名


    でいけるはず。


    手渡しとか安全な経路があれば秘密鍵をクライアントに渡してしまって
    も良いと思います。
    ちょっと手間はかかりますが安易なパスワードに対する不安を無くせる
    事を考えるとメリットの方が大きいかと。






    2010/06/19

    MiniPCの保証効かず..

    火事で壊れたMiniPCは保証期間内だったので
    修理に出して有ったのですがこんなお知らせが..



    ヽ(`д´)ノ ウワァァァン

    同じ部屋にあった他のPCは全部無事だったのにー

    汎用パーツで何とか復活... と思ったら電源、高っ!

    http://www.amazon.co.jp/JPS-SRD2D080SATA2-24/dp/B0035Z87C0

    8900円って、M/Bと合わせたらベアボーンとかわんねーじゃん。

    結局、泣々同じベアをもう1台購入 16770円。

    1000円安くなっていたのがせめてもの救いか..

    幸い、HDDとメモリは生きていたので30分で復旧。
    時間を買ったと思うしか無いですな。

    MiniPCベアは壊れたときにネックになるのは分かってい
    たのですがこんなに早く体験するとは思っていなかった。

    前のケースもったいないなー何か使えんかなー。







    2010/06/12

    火事その後

    事務所が使えないので各自、自宅で仕事をすることに...

    ばらばらに自宅で仕事なんかできんのかよ、とお思いでしょうが
    元々、会社のシステムが半分ぐらいGoogleAppsに移行
    していたので以外に出来ちゃってます。

    メール、文書の共有、日報の提出なんかが全部GoogleApps上で
    できて後はチャットしながらで仕事は進みます。

    困ったのはSVNと開発サーバくらいですね。
    SVNはGoogleCodeが有るんですがOSS用なんで基本公開されちゃう
    んですよね。
    結局、社長の自宅のGlobalIPで暫定運用してますが..

    個人的には自宅で仕事するのはイマイチですね。
    通勤時間が無いのは良いんですが気持が切り替わらないし
    夜までずるずると仕事しちゃいますし。
    曜日の感覚も良く分からなくなります。

    全焼したわけでは無いので復旧は以外に早く今月中には
    使えるようになるらしいです。

    まあ、何かと貴重な経験です。
    1回で十分ですが。



    プロフィール
    20年勤めた会社がリーマンショックで消滅、紆余曲折を経て現在はフリーランスのSE。 失業をきっかけにこのブログを始める。

    サイト内検索

    登録
    RSS/2.0

    カテゴリ

    最近の投稿

    リンク

    アーカイブ