ラベル Java の投稿を表示しています。 すべての投稿を表示
ラベル Java の投稿を表示しています。 すべての投稿を表示

2016年4月20日水曜日

Jar の META-INF/MANIFEST.MF に書いた Class-Path が効かない件

案外ググったりしてもぴったりの記事には遭遇しなかったので、ブログに書いておきます。

まずやりたかったことですが、Javaで書いたコードをjarにまとめて、それをjava -jar (やダブルクリック等)で開けるようにしようと思ったのですが、その際別のライブラリがjarで提供されているので、そのjarもclasspathに追加した状態で起動したい、ということでした。これ自体は META-INF/MANIFEST.MF に書けばなんら問題なくできます。できるはずでした。

まずそのまとめるjarのほうですが、mavenを使って書いていたので、pom.xmlにこのように書けば main メソッドの指定は簡単にできます。
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <configuration>
        <archive>
          <manifest>
            <mainClass>jartest.Main</mainClass>
          </manifest>
        </archive>
      </configuration>
    </plugin>
  </plugins>
</build>
これでビルドして実行すると
$ java -jar jartest-0.0.1.jar 
Hello!
当然ですが、問題ありません。

次に外部 jar の呼び出しですが、その前にこちらの jar の main メソッドをこのように書いておきます。
public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println("Loaded:" + Class.forName("jartest2.EndPoint").getName());
    }
}
呼び出される側は jartest2.EndPoint というクラスを持っています。この時点で
$ java -cp jartest-0.0.1.jar:jartest2-0.0.1.jar jartest.Main
Loaded:jartest2.EndPoint
となります。これを jartest2 のほうを classpath に指定しなくても java -jar jartest-0.0.1.jar で起動できるようにしたいというのが今回の目的なわけです。
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <configuration>
        <archive>
          <manifest>
            <mainClass>jartest.Main</mainClass>
          </manifest>
          <manifestEntries>
            <Class-Path>jartest2-0.0.1.jar</Class-Path>
          </manifestEntries>
        </archive>
      </configuration>
    </plugin>
  </plugins>
</build>
このように pom.xml を変更すればいけるはずです。実際はこちらの jar は他にも依存してるライブラリがあって、ただそちらは maven で取得できるライブラリなので、dependencies に記述し、よくある方法にて maven-assembly-plugin のほうに上のような記述をします。さていざ実行。
$ java -jar jartest-0.0.1-jar-with-dependencies.jar 
Exception in thread "main" java.lang.ClassNotFoundException: jartest2.EndPoint
 at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
 at java.lang.Class.forName0(Native Method)
 at java.lang.Class.forName(Class.java:264)
 at jartest.Main.main(Main.java:5)
!?
とりあえず jartest.Main.main() までは来ているので MANIFEST.MF が全くダメなわけではないということはわかります。中身を見てみると、
$ tail -3 META-INF/MANIFEST.MF 
Main-Class: jartest.Main
Class-Path: jartest2-0.0.1.jar
と、正しく指定されているように見えます。

正直相当はまりました。しょうもないtypoしてるのかと思ってコピペでクラス名を書いてみたり、ファイル名もコピペしてみたり。はたまた開発環境に使っているMac特有の問題なのじゃないかと思ってWindows機でテストしてみたり・・・。

そしていろいろやった後も解決せず、jar を展開してぼーっと眺めていたら
$ ls META-INF/
INDEX.LIST    LICENSE.txt    MANIFEST.MF    maven/        services/
ん?MANIFEST.MF以外にもいろいろあるな?まさかな、と思いつつ MANIFEST.MF だけにしてみたところ・・・
$ java -jar jartest-0.0.1-jar-with-dependencies.jar 
Loaded:jartest2.EndPoint
おおお!動きましたね!

結論から言うとこの INDEX.LIST が悪さをしていたようです。そもそもこれは何かというのを知らなかったですが、Jarファイルの仕様にしっかり書かれています。でも今までこんなファイルを作るなんて指定はどこにもしてなかったけど・・・?原因はこれでした。
    <dependency>
      <groupId>io.undertow</groupId>
      <artifactId>undertow-core</artifactId>
      <version>1.0.16.Final</version>
    </dependency>
私が作っていた java application は embedded web server を実装するのに undertow を使っていました。そしてそれを dependency にして jar-with-dependency で1つの jar にしていたわけですが、もともと undertow-core 用に書かれた INDEX.LIST が jar-with-dependency によって1つの jar に収められたことにより生じた問題だったようです。いやはやなんとも難しい・・・。

というわけで解決方法としては INDEX.LIST ファイルを jar に含めない、またはちゃんと INDEX.LIST ファイルを生成する、のどちらかだと思うのですが、undertow が使っている INDEX.LIST の内容は特になくても問題ない(ように思える)ので、含めない方法をとることにしました。maven-assembly-plugin の jar-with-dependency の指定のところを外部の assembly.xml に記述することとし、そこに以下のような記述を行います。
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
  <id>jar-with-dependencies</id>
  <formats>
    <format>jar</format>
  </formats>
  <includeBaseDirectory>false</includeBaseDirectory>
  <dependencySets>
    <dependencySet>
      <outputDirectory>/</outputDirectory>
      <useProjectArtifact>true</useProjectArtifact>
      <unpack>true</unpack>
      <scope>runtime</scope>
      <unpackOptions>
        <excludes>
          <exclude>META-INF/INDEX.LIST</exclude>
        </excludes>
      </unpackOptions>
    </dependencySet>
  </dependencySets>
</assembly>
これで無事 INDEX.LIST を含まずに dependency を含んだ jar が生成されました。めでたしめでたし。

2015年9月4日金曜日

艦これ+航海日誌環境における猫事件についての調査

ブログ書くの相当久しぶりになりました。最近趣味では新しいことがあまりできてなかったので書くネタがなかったのですね。もっと時間を作らないと。

さて今回のネタはもともと技術的な話ではなかったのですが、結局は技術的な話になったのでここに書くことにしました。内容は艦隊これくしょん(艦これ)の通信についての話なので、興味のある方だけ読んでみてください。

私が艦これを始めたのは相当前になります。最初はただの興味本位で始めたのですが、基本運ゲーでも結構いろいろ考えることが必要なことがわかり、最初はアナログな試行錯誤をしていたところ@sanae_hirotakaさん作の航海日誌という大変便利なソフトに出会い、より楽しくプレイさせていただけるようになりました。作者さんがソースを公開してくださっているので、私の専門のJavaで書かれていることもあり、ほんの少しですがpull request等で開発に協力させていただいたりもしていました。

ところが事件が起きた(起き始めた)のは2015年の夏イベント(通称夏イベ)が開始されてからです。今まではほとんど猫った(エラーが起きた)ことのない平和な我がタウイタウイ泊地だったのですが、夏イベ開始のサーバーメンテナンス明けからちらほら猫るようになりました。最初はサーバーが混んでるのかなと思っていましたが、空いてそうな時間帯でもなることがあり、変だなと思い始めました。しかしネット上では特に騒がれている風もなく・・・。

すると艦これをやってる知り合いから「どうも航海日誌を使ってると猫るって話になってるぞ」との知らせが。そんな馬鹿なと思いツイッターで「航海日誌 猫」とかで調べてみると確かにそういう事象にあっている方もいるようで、ただ実行環境によるのか、サーバーによるのかは不明にせよ、全く起きない方もいるのも事実。 しかし調べていたら、こんなに便利に使わせてもらってるソフトをしかもタダで使わせてもらってるのに、「使えねー」とか「ふざけんな」とか言われてるのは納得いかないので調査してみようと思ったのがこの記事を書いている理由です。

するとその知り合いからさらに「特にテザリングでやってるとしょっちゅう猫る」との知らせが。それをヒントに、まずどういう環境で出やすいのかを考えました。家ではあまり出ない、テザリングで出やすい、と考えると、やはり遅い回線だと出やすいのではないかと考えました。そこで私はiijmioのSIMでiPhone4Sを運用しているのですが、このiijmioのSIMはアプリで通信速度を低くすることができます(正確には高速通信は月3Gまでとなっているので、高速通信をオンオフできます)。なので高速通信をオフにした、たしか200kbps程度の通信速度でテザリングして艦これしてみました。

すると


でました、猫。ログインしてすぐというわけではないですが、任務をクリックしたり母港に戻ったりとか通信を何度もしていると、私の環境では数分で起きます。これは何かありそうですね。そう思って航海日誌を通さずに艦これしてみると、不思議なことに全く猫は起きません。なぜでしょう。

悩んでても解決できないので、Wireshark を使ってパケットキャプチャをしてみました。そして猫が出るまで通信を繰り返します。そして猫が出た時と出なかった時を比べます。すると
うまく行った時はこんな感じです。これは任務一覧を表示した時ですね。POSTのメッセージに対して ACK が返り、その後 200 の応答が返ってきます。普通です。ところが猫が出た時は(キャプチャミスってボケてます。すみません。)

のように、[FIN, ACK] が返ってきています。FIN は通信終了なので切断するという意味なので、航海日誌が使っている jetty の ProxyServlet は EOFException を投げます。結果ブラウザから見れば通信エラーとなり、猫が出るという仕組みでした。

しかしこれだったら普通に航海日誌抜きでやっても同じなのでは?と思って試してみたところ・・・
おお、これは・・・。どういうからくりかはわかりませんが、上のようにAPIの呼び出しが切断された時はどうもリトライしにいくようですね。これは任務の受諾をやめた時に呼ばれるAPIなので、連続で来るはずはないリクエストになります。通常は stop が来た後 questlist が来るはずのところを2回 stop が来ているので明らかにリトライしているようです。

なので ProxyServlet 経由でも全く同じ状況、FIN を返すことができれば勝手にリトライしに来ると考えられます。しかしそれを ProxyServlet を使ってやるのは難しそうに思います。試しに ReverseProxyServlet を無理やり extends して EOFException が出た時にリトライするサーブレットを書いてみたところ、私の環境では猫は出なくなりました、があまりに無理やり過ぎるので pull request はしないほうがよいかなと思って保留しています。

なぜ通信が切断されるようになったか、という点については、どうもこのメンテナンスでサーバーによっては HTTP 1.1/ keep alive が使われるようになったそうです。とはいえメンテナンス前が keep alive じゃなかったかどうかは全く知らないですけどね。その実装の影響でこうなったのかもしれません。想像でしかないですが。

結論としては、
- なぜか環境によっては艦これサーバーからAPIリクエストの通信が切断されることがある
- ブラウザから直結している場合は自動的にリトライする
- 航海日誌やその他プロキシの実装によってはその切断が別のエラーとして処理されると猫る
ということのようです。なので実装系が違う他のソフトでも同様な挙動をしていれば、航海日誌と同じように猫りやすくなったソフトもあると考えられます。というわけで、この猫事件(?)の真相は「艦これの微妙な(怪しい)挙動」が根本原因であって、航海日誌その他プロキシの実装のバグと呼ぶのはフェアじゃないと思いますね。


2014年10月27日月曜日

undertow を使ってみる

ひょんなことから自分のJavaアプリケーションにWebサーバーというかサーブレットコンテナの機能が少しあったら便利だなと思って調べてみたところ、undertowというのがお手軽に使えそうに見えたので試してみました。

1. インストール


そのJavaアプリケーションはmavenを使っていたので、依存関係を追加するだけです。
  <dependency>
   <groupid>io.undertow</groupid>
   <artifactid>undertow-core</artifactid>
   <version>1.0.16.Final</version>
  </dependency>
いやぁ、この手の依存解決ツールって楽ですよねぇ。今さら ant とか書きたくないです。

2. 使う


トップページの下のほうに Show me the code というのがあるので、そのコードをそのままコピー&ペーストします。

Undertow server = Undertow.builder()
 .addHttpListener(8080, "localhost")
 .setHandler(new HttpHandler() {
  @Override
  public void handleRequest(final HttpServerExchange exchange) throws Exception {
   exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
   exchange.getResponseSender().send("Hello World");
  }
 }).build();
server.start();
これだけです?w

3. 使ってみる


では早速リクエストを投げてみます。
$ curl localhost:8080
Hello World$ 
これだけです?w(再

たったこれだけですがサーブレットコンテナとして既に動いているわけですね。これはなかなか楽かも。

当然ですがこのままでは localhost からしかつながりません。 addHttpListener のところで "localhost" を "0.0.0.0" とかにすれば任意のマシンから接続できます。

2013年10月15日火曜日

play-pac4j : Play! Framework 2.2 で OAuth(失敗 -> 2.1 では成功)

ちょっと前のエントリで Play! Framwork で twitter4j を使うのに SecureSocial というのを使ってみましたが、別のものということで今度は pac4j というのを使ってみます。定義によると
pac4j is a Profile & Authentication Client for Java (it's a global rebuilding of the scribe-up library)
だそうです。それをさらに Play! Framework から利用しやすいように play-pac4j というものがあるようですね。早速使ってみます。

まずは dependency の解決です。上のガイドに従うと、Twitter を使うためには、play-pac4j_java と pac4j-oauth が必要なようですね。Maven の central repo では最新はそれぞれ 1.1.1 と 1.4.1 です。以下のように build.sbt に追加します。(このファイル、2.x では project/Build.scala だったはずなんですが、変わったんですね・・・)

libraryDependencies ++= Seq(
  javaJdbc,
  javaEbean,
  cache,
  "org.twitter4j" % "twitter4j-core" % "3.0.4",
  "org.pac4j" % "play-pac4j_java" % "1.1.1",
  "org.pac4j" % "pac4j-oauth" % "1.4.1"
)  

前回やったように、IntelliJ IDEA を一度止めて play idea してから開きなおすのが手っ取り早いようです。ではコードの変更です。
  1. コントローラーが継承しているクラスを Controller から JavaController に
  2. Global.onStart() で TwitterClient を登録
  3. ログインを要求したいコントローラーメソッドに @RequiresAuthentication を追加
  4. routes に callback を追加
具体的には
1
import org.pac4j.play.java.JavaController;

public class Application extends JavaController {
2
public class Global extends GlobalSettings {
    @Override
    public void onStart(Application application) {
        final TwitterClient twitter = new TwitterClient(CONSUMER_KEY, CONSUMER_SECRET);
        final Clients clients = new Clients("http://localhost:9000/callback", twitter);
        Config.setClients(clients);
3
    @RequiresAuthentication(clientName="TwitterClient")
    public static Result top() {
4
# For pac4j
GET   /callback               org.pac4j.play.CallbackController.callback()
POST  /callback               org.pac4j.play.CallbackController.callback()
GET   /logout                 org.pac4j.play.CallbackController.logoutAndRedirect()
おお、結構作業量が少なくていい感じ。早速実行してみましょう。
[info] play - Application started (Dev)
Uncaught error from thread [play-akka.actor.default-dispatcher-7] shutting down JVM since 'akka.jvm-exit-on-fatal-error' is enabled for ActorSystem[play]
java.lang.AbstractMethodError
あっさりオワタw
まだ 2.2 がサポートされてないんですね。この方も嘆いてますが、Play! Framework ってバージョン間の互換性がなさすぎる・・・。

仕方ないので Play! Framework を 2.1.5 にしてみます。2.1.5 で play new して作った雛形から、Build.scala をコピーして、build.sbt 内に加えた変更を移動します。あとは念のため diff で差分が出た build.properties とか plugins.sbt とかを直していざテスト。

おっ!
うまくいきました。これは結構簡単。

最後にお約束の twitter4j のインスタンス作り。
    @RequiresAuthentication(clientName="TwitterClient")
    public static Result login() throws TwitterException {
        final TwitterProfile profile = (TwitterProfile) getUserProfile();
        TwitterFactory factory = new TwitterFactory(new ConfigurationBuilder().setOAuthConsumerKey(CONSUMER_KEY).setOAuthConsumerSecret(CONSUMER_SECRET).build());
        Twitter tw = factory.getInstance(new AccessToken(profile.getAccessToken(), profile.getAccessSecret()));
これでうまくいきました。

2013年9月30日月曜日

Heroku を試す(7)~ Play! Framework 2.2 を IntelliJ IDEA で動かしてみる

実は前の記事はだいぶ前に書いていて公開し忘れていたものでした。このたびもう一度使ってみようと思ったので、新しいエントリを書く前にとりあえず公開したものです。で、もう一度やろうとしたらいろいろバージョンも変わっていたので変化部分を。

まず前回はあきらめた git のバージョンですが、ちゃんとバージョンをあげておきました。

$ git --version
git version 1.7.11.3

これなら文句は言われまいw IntelliJ IDEA を起動しましたが、確かに何も警告は出ませんでした。勝った!(←何に?w)これで IDEA のほうも最新バージョンで進めます。

Play! Framework のほうですが、最新は 2.2.0 になっていました。なにやら嫌な予感が・・・。Play! Framework 自体は好きなんですが、バージョンアップ時の挙動が結構変わるのが難儀するんですよね。まあいいか、早速プロジェクトを作ってみます。


orz
こんな予想的中してもうれしくないw まだIDEAのプラグインが対応してないのかな。ここは気を取り直して、Play側から作って IDEA 対応を試みます。

$ play new sample-app
$ cd sample-app
$ play
[sample-app] $ idea

これを IDEA から開くと、無事開けました。

前回なぜはまらなかったのか不明ですが、デフォルトでは起動すると 9000 番ポートにつなぎに行きます。しかし他のアプリケーションで使っていると当然失敗します。直接コマンドラインから指定するときは

$ play run --http.port=9001

のようにすればいいのですが、IDEA からの起動だと play に直接コマンド渡せるわけではないので。結論としては Run/Debug Configurations のところで、Play 2 の起動構成を出し、 JVM Options に -Dhttp.port=9001 のように足せばポートが指定できます。

これで起動、デバッグ、あとは heroku へのデプロイ等は問題なくできました。しかし初回の push は長い・・・。いろんなライブラリの解決してるからっぽいですけど。あとはデータベースへの接続やバージョン管理(GAEで version.application.appspot.com で試せるやつのような)のやり方を学ばねば。

2013年9月29日日曜日

Heroku を試す(6)~ Play! Framework 2.0 を動かしてみる

さてなんとなく感じがつかめてきたところで今度は Play! Framework を使ってみようと思います。せっかくなのでちゃんと対応している(はずの)IntelliJ IDEA を使ってみましょう。Eclipse でやると結局のところ Java 以外のところはテキストエディタとして使うことが多くていまいち便利になった感がないので。

早速起動してプロジェクトを作成・・・ってあれ?前はここに Play! って出てた気がしたんですが・・・。恐らく完全な気のせいで、プラグインを入れなければいけないようです。それじゃ Eclipse と同じとかは思わないことにしますさくっと入れますw すると、おお、確かにでてきましたね!

実は最初何度やってもバージョンを変えてもうまくいかなくて相当悩んだんですが、ここに問題があがっていてこれかと思って普通にコマンドラインから play new してもエラーになったんです。なんだろうと思ったら Play! Framework 自体を /opt (つまり書き込み不可な場所)に入れていたのが原因だそうで。どうせ読み書き可能にしなきゃいけないのならユーザーのディレクトリに入れて指定したところうまくいきました。この思想がいいかどうかは別として。

さてこれを Heroku に上げてみましょう。Heroku のほうPlay! Framework のほうと両方に異なる文書がありますが、スクロールバーが短い(w) Play! のほうにしたがってみます。

まずは Store your application in git の部分。git コマンドをたたけばいいわけですが、IDEA から行きましょう。VCS > Import into Version Control > Create Git Repository で・・・

orz

厳しいっすw

git は rpm 経由で入れていて上げるのはまあまあ面倒なので、12.0.1 を再度引っ張り出してきて構成しました。するとコマンド自体は動いていたのか、既に git の管理下になっているようです。

では heroku の設定を。
$ heroku create sdk0815-sample2
Creating sdk0815-sample2... done, region is us
http://sdk0815-sample2.herokuapp.com/ | git@heroku.com:sdk0815-sample2.git
Git remote heroku added
これでリモートがセットされたはず。 しかし
$ git push heroku master
Warning: Permanently added the RSA host key for IP address '50.19.85.154' to the list of known hosts.
error: src refspec master does not match any.
error: failed to push some refs to 'git@heroku.com:sdk0815-sample2.git'
うーむ。これは何のエラーだろう。いろいろ調べたらどうやら何も push するものがないときに出るエラーみたいですね。 git commit したら確かにコミットされたので commit まではされてなかったんですね。
#IDEA からやるべきだったんですがついコマンド打っちゃいました。プロジェクトを右クリックで Git > Commit Directory... で全部 commit できます。

さて今度は push したらずっと待たされました。いけー。どりゃー。おおおお!

やったー!・・・ってあれ、こんな殺風景なはずは・・・w

どうもフレームワーク動いてないですね、これ。前に作ったときにあった Procfile にあたるものが必要なのかも。と思ったらちゃんと書いてありました。 これを作って再度挑戦です。また待たされました。いつ終わるのかな~。終わった!

変化なしw

よく考えてみると、この "Your new application is ready." という文字列って、ソースコードの中では
@(message: String)

@main("Welcome to Play 2.1") {
    
    @play20.welcome(message, style = "Java")
    
}

となっていて、文字列はコントローラである Application.java から送られてきています。ということはフレームワークは動いているということかな? @play20 というカスタムタグ(?)が動いていないようだと。

まあちょっと玉虫色な感じもしますが、とりあえず動かせるところまではもってこれました。実際に何か作るかは意欲と時間とネタ次第です。

2013年8月2日金曜日

IntelliJ IDEA で maven の dependency を解決する

ほんのちょっとした Tips 的ですが、しばらく悩んだので。

IntelliJ IDEA で Maven Module を指定して Project を作成します。ここまではよし。しかし実際に作成された pom.xml に dependency を追加する方法がわかりませんでした。すると、いくつか方法はあるようですが、そのうちの1つはエディターから Alt+Enter 等で出てくるメニューから "Add Maven Dependency..." を選べばよいと。ふむふむ。出てくるダイアログで文字列検索すれば・・・
 でてこない。 orz
全く出てこないわけではなく、何も入れてないときは ant とか commons なんとかとかそのへんは出てるんですよね。うーむ。どうもローカルにあるリポジトリだけを見てるっぽいですね。

そんなときは File > Settings から Maven > Repositories を選ぶと以下のような画面になるので、central repo を選んで Update を押せばよいようです。
その後同じ操作をすると、
めでたしめでたし。

2013年6月26日水曜日

Play! Framework で twitter4j を使う

久しぶりにちょっと時間ができたので遊んでみました。

表題のとおりです。とりあえず OAuth でログインできるところまで行きたいですね。

まずは twitter4j を追加します。これまでは jar をコピーしていたのですが、そんなことするよりずっと簡単な方法があります。Managed Dependency という項に書かれているように Build.scala に依存を書けば勝手に解決してくれます。フレームワークって大事ですね。
  val appDependencies = Seq(
    "org.twitter4j" % "twitter4j-core" % "3.0.3"
  )
これだけ・・・。便利になりました。

早速コードを書き始めると・・・おや、Play! を再起動しても IntelliJ IDEA では解決してくれないですね。うーむ。File > Invalidate Caches なんかもやってみましたがだめです。どうやら
$ play idea
を実行しないとだめみたいです。このへんはもう少し便利になってくれるとうれしいですけどね。まあこれで特に問題はないです。

では OAuth のページを作成しましょう。

と思ったらなんか便利そうなものを見つけました。 ここに書かれている SecureSocial なるものを使ってみますか。また dependency をつけます。
  val appDependencies = Seq(
    "org.twitter4j" % "twitter4j-core" % "3.0.3",
    "securesocial" %% "securesocial" % "master-SNAPSHOT"
  )

  val main = play.Project(appName, appVersion, appDependencies).settings(
    // Add your own project settings here
    resolvers += Resolver.url("sbt-plugin-snapshots", new URL("http://repo.scala-sbt.org/scalasbt/sbt-plugin-snapshots/"))(Resolver.ivyStylePatterns)
  )
この時点では Play! 2.1 には spapshot を使えとあるのでそうしてみました。これで同じように play idea も実行して解決させておきます。routes は書いてあるものそのままで。play.plugins からは不要な Provider を消します。
9994:securesocial.core.DefaultAuthenticatorStore
9995:securesocial.core.DefaultIdGenerator
9997:securesocial.controllers.DefaultTemplatesPlugin
9998:service.InMemoryUserService
10000:securesocial.core.providers.TwitterProvider
こんな感じで。あ、sample から InMemoryUserService を持ってきました。動作確認に使えます。次に securesocial.conf を作って application.conf から include します。
securesocial {
  onLoginGoTo=/
  onLogoutGoTo=/login
  ssl=false

  twitter {
     requestTokenUrl="https://twitter.com/oauth/request_token"
     accessTokenUrl="https://twitter.com/oauth/access_token"
     authorizationUrl="https://twitter.com/oauth/authenticate"
     consumerKey="****"
     consumerSecret="****"
  }
}
いよいよ実行です・・・。とその前に、ログインしてないとだめなメソッドに @SecureSocial.SecuredAction アノテーションをつけます。
    @SecureSocial.SecuredAction
    public static Result index() {
        Identity user = (Identity) ctx().args.get(SecureSocial.USER_KEY);
        final String userName = user != null ? user.fullName() : "guest";
        return ok(index.render("Logged on as " + userName + "."));
    }
では実行です。/login にアクセスすると
まずはこのようにログインの画面が出ます。ここで t のアイコンをクリックするといつものツイッターの認証画面に飛ばされます。そこで認証を行うと
のようになります。やったー。・・・ツイッターだけだと正直楽になったかどうか微妙ですがw ほかのサービス(facebook とか)でもログインできるのでその手のサービスを作りたいときは便利なのは間違いないですね。

さてこれでログインはできましたが、twitter4j が使えなければ意味がないわけで。ログインした後に実行されるメソッドの中身を変えます。

        String ret = "Unknown";
        Identity user = (Identity) ctx().args.get(SecureSocial.USER_KEY);
        if (user != null) {
            Twitter twitter = new TwitterFactory().getInstance();
            twitter.setOAuthConsumer(Play.application().configuration().getString("securesocial.twitter.consumerKey"),
                    Play.application().configuration().getString("securesocial.twitter.consumerSecret"));
            AccessToken accessToken = new AccessToken(user.oAuth1Info().get().token(), user.oAuth1Info().get().secret());
            twitter.setOAuthAccessToken(accessToken);
            User u = twitter.verifyCredentials();
            ret = "Logged on as " + u.getName() + "(@" + u.getScreenName() + ") followed by " + u.getFollowersCount() + " followers.";
        }
        return ok(index.render(ret));
実行すると・・・
 できたー。

2013年5月24日金曜日

Heroku を試す(5)~コードを変更してデプロイしてみる

ずいぶん間があいてしまいました。興味の向く先がいろいろ変わるのはいいのか悪いのかわからないですが・・・。前々回の続きです。

しかしこのページ、殺風景ですね。
と思ったら
 あらら、エラーになってますね。早速直してみましょう。

直すためにはどうやってこのコードが動いているのかを知る必要があります。ソースコードを開いてみます。右にあるように、Main.java と HelloServlet.java があります。どう見ても Main が怪しい(w)ので開いてみると、思いっきり main メソッドがありますね。へぇ、これって任意の(サーブレットコンテナだけでなく)Java コードを実行できちゃったりするんですかね?それはそれで面白いような怖いような・・・。

ひとつ疑問なのは、この main メソッドをどうやって見つけているのか、ということ。これがわからないと間違ってどこかのクラスに main メソッドを作ってしまうととたんにアプリが動かなくなったりということもありうるのでは。ファイルを眺めていると大体のファイルは予想がつくものですが、中に Procfile なるファイルがあります。これは何だろう。
web:    java $JAVA_OPTS -cp target/classes:target/dependency/* com.example.Main
ほほう。どうやらこれがその答えだったようですね。なので一安心w

で、本題の修正です。もう twitter.github.com は twitter.github.io に変わっているために殺風景なページになっているのでしょうね。ローカルで編集して試します。Main クラスを Java Application として起動します。Eclipse 内では m2e のおかげでクラスパス等は解決されているはずです。 起動しました。8080 にアクセスしたらちゃんと同じページが出てきます。よしよし。Edit > Find/Replace で github.com を github.io に Replace します。保存してリロードすると・・・
めちゃめちゃ今どきになったじゃないですかw

さてこの変更をコミットしましょう。チュートリアルに戻って、まずは変更をコミットです。それから Push to Upstream をクリックしてしばし待ちます。すると無事サイトのほうも更新されます。なるほど。

2013年4月24日水曜日

Heroku を試す(4)~アプリを作成

さていよいよアプリを作ってみましょう。プロジェクトを作成する画面に Create Heroku App from Template なるメニューがあるのでそれを選びます。

次の画面では・・・何を選びますかね。Getting Started には別にサンプルを作るところまで書かれているわけではないので何も指定はないようです。一番シンプルそうなのは・・・ Embedded Jetty-Servlet Application でしょうか。それを選んで適当に名前をつけて進み・・・ませんw

こんどはこんなエラーが。あれー、ちゃんとセットしたはずなんだけどな。
仕方ないので、前のステップに戻って、Create an SSH Key と Adding your ... のところをやってみます。その後もう一度作ろうとすると、今度は「その名前は既にあるぜー」と言われてしまいます。なので
C:\>heroku apps
=== My Apps
sdk0815-sample


C:\>heroku apps:destroy sdk0815-sample

 !    WARNING: Potentially Destructive Action
 !    This command will destroy sdk0815-sample (including all add-ons).
 !    To proceed, type "sdk0815-sample" or re-run this command with --confirm sdk0815-sample

> sdk0815-sample
Destroying sdk0815-sample (including all add-ons)... done
そして再挑戦すると・・・できたー!
って×出てるしw メッセージを見ると
maven-dependency-plugin (goals "copy-dependencies", "unpack") is not supported by m2e. pom.xml /sdk0815-sample line 48 Maven Project Build Lifecycle Mapping Problem
ふむ・・・。m2e は私が勝手に入れてたプラグインであって、別にここでは必須ではないですよね。ということはエラーが出てても大丈夫か?とりあえずこのまま進めーw

今この状態はどうなってるのか気になるので、Window -> Show View -> Other... で My Heroku Applications ビューを開きます。そこに出てきたアプリを右クリックして Open を選ぶと・・・
キター!

2013年4月16日火曜日

Heroku を試す(3)~ Eclipse を使って

ではこちらに従って Eclipse を使ってアプリケーションを作ってみます。
You can build/deploy and manage your Heroku applications right from within Eclipse using the Heroku Eclipse plugin.
とありますのでプラグインがあるんですね。要件は
ということで Eclipse 3.7 以上を用意しましょう。私は 4.2 で行きます。 更新サイトを追加してインストールするだけ。Eclipse を再起動します。

次はプリファレンスに移動して Email と Password を入れて Login を押せば・・・、あれ。

なぜかエラーが出てますね・・・。いろいろ調べてましたが最終的にはエラービューに行ってみたところ、
Secure storage was unable to retrieve the master password. If secure storage was created using a different Windows account, you'll have to switch back to that account. Alternatively, you can use the password recovery, or delete and re-create secure storage.
なるエラーが出ていました。うーむ。そもそもそのファイルがいつ作られたかも定かではないぐらいなので、消して再起動してみました。すると無事 Login を押したら API Key がセットされました。

次にその下にある Load SSH Key をクリックして、その1で作った .pub をロードします。これで準備完了かな?


2013年4月10日水曜日

Heroku を試す(2)~ Java を使う

さて私の専門(?)は Java なので Java のチュートリアルに進みます。ほかの言語のもたくさんありますね。Java はこちら。要件としては
  • Basic Java knowledge, including an installed version of the JVM and Maven 3.
  • Your application must run on the OpenJDK version 6, or 7 (8 is also available in beta).
  • A Heroku user account. Signup is free and instant.
ふむ。とりあえず Java の知識は当たり前として Maven もこの前いじりましたね。ほんの少しで恐縮ですがw OpenJDK で動くものでないといけないと。アカウントはついこの前作りました。というわけで準備はOK。

さて早速・・・と思ったら
If you’re an Eclipse user you can skip these steps and get started inside of an Eclipse workspace. See Getting Started with Heroku & Eclipse.
Eclipse 用のやり方は別にあるんですね。せっかくなので手に入れた IntelliJ IDEA で行こうかと思ってたんですが、まずは勝手知ったる Eclipse で始めてみます。というわけでそちらのページに従って進めるとします。

2013年4月8日月曜日

Heroku を試す(1)~ Getting Started

#f1yosou のサイトにアクセスしたことのある方なら既に感じられてるかもしれませんが、Google App Engine 上で作っているサイトは結構アクセスしたときのレスポンスが悪いですよね。これはしばらくアクセスがないと Idle 状態になるためです。もちろんお金を払えばインスタンスを常時起動しておけるので早くなるわけですが、別にあのサイトで1円も稼いでないわけでそれはちょっと・・・。

というわけで、ほかの PaaS サービスはどうなのか興味がありまして。最近よく耳にする(気がする) Heroku を試してみます。それで備忘録的にここに手順を書いていきます。私は何事もやってみないと身につかないタイプなので、Getting Started を探しましょう。あ、ありました。ここですね。これに従って進めましょう。

まずは Sign Up から。Eメールアドレスを入れるとメールが届くので、そのリンク先でパスワードを設定するだけです。特に名前とか聞かれないんですね。ログインすると比較的素っ気無い感じのページにログインできます。なんかコマンドっぽい(本当にコマンドかも?)ものが出てるところがちょっと玄人っぽくていいかもw

次は Heroku Toolbelt というものをインストールします。これはなんだろう。
Foreman, an easy option for running your apps locally; and Git, the revision control system needed for pushing applications to Heroku.
とありますね。ということはアプリを管理するツールと git が入るんですね。そういや deploy するのに git を使うってありましたので Linux でやってみようかと思ったけど Windows 用の Toolbelt をダウンロードしてしまったのでとりあえず Windows でやってみます。ダウンロードした exe を起動するだけでするっと入ります。

次はログインですか。早速試してみましょう。
C:\>heroku login
Enter your Heroku credentials.
Email: ********@*****.***
Password (typing will be hidden):
Could not find an existing public key.
Would you like to generate one? [Yn] Y
Generating new SSH public key.
 !    Could not generate key: 'ssh-keygen' は、内部コマンドまたは外部コマンド、
 !    操作可能なプログラムまたはバッチ ファイルとして認識されていません。
あれれ失敗しますね。インストーラーのところで ssh も入れるってあったはずですが・・・。うーむ。仕方ない、とりあえず Cygwin のパスを通して進んでみます。
C:\>set PATH=C:\cygwin\bin;%PATH%

C:\>heroku login
Enter your Heroku credentials.
Email: ********@*****.***
Password (typing will be hidden):
Could not find an existing public key.
Would you like to generate one? [Yn] Y
Generating new SSH public key.
Uploading SSH public key C:/Users/*****/.ssh/id_rsa.pub... done
Authentication successful.
できました。既に key があればそれを使うのもできそうですね。

次はいよいよサンプルアプリを作成してみます。

2013年3月22日金曜日

GAE/J + JDO で Single Property Index を作らせない

表題のとおりですがようやくたどり着いたのでメモ。

サイト自体にも書いていますが、#f1yosou Google App Engine/Java (以下 GAE/J)を使っています。もともと3年前(もう3年になりますか・・・)にはじめたときには結構余裕だったのですが、Googleさんの緩やかな締め付けwで無料での利用部分がどんどん制限されてきて、最初のころにはタコなコードを書いていても余裕があったのですが、最近はかなり厳しくなってきています。これまでもそれなりに使用量を抑制しつつなんとかごまかしてきました。

そんな最近の #f1yosou サイトのもっとも厳しい項目は、Datastore Write Operations の項目です。これは文字通り Entity を Datastore に書き込むと消費するやつですね。これが特に #f1yosou タグのついたツイートを解析して結果を書き込む処理を経るとかなり消費されます。処理としては
  • 該当するツイートの保存
  • 同一の予想をしたツイートのカウントを行い、その結果を保存
ということをしていますが、どうも後者の処理がかなりの書き込みを生じているようです。そもそもこの Datastore Write とは Entity を1つ書き込むと1回なのか?というところから調べ始めたところ、以下の記事がよくまとまっておりました。
EntityのPropetryの構成よって、Datastore writeの数は変わる!
なるほど。そもそもプロパティの数によって増えるのもあるんですがそれは実際に値をしまってるので仕方ないとして、各プロパティについて勝手に自動的に作成される Index の書き込みのほうが問題ですね。そもそも使ってないのに昇順・降順で2つ作られてるみたいですし。これを削減できればだいぶ効きそうです。ただし
Single Property Indexを作らないとComposite Indexも作成されない?
という考慮点もあるみたいなのでなんでもかんでも削れるわけではないようです。

と、ここまできて JDO ではどうやって指定するんだろう・・・と思って調べてもなかなかでてきません。いろいろググっていたらようやく Entity#setUnindexedProperty() なるものがでてきました。しかしこれも Low Level API。。。

まあそれでもいいか、と思ってやり方を探っていたらとうとう発見しました。まんまの質問がありましたw なぜこれに最初に行き当たらなかったのか不思議なぐらいそのままです。要するに
@Extension(vendorName = "datanucleus", key = "gae.unindexed", value="true")
を索引がいらないプロパティにつけるだけです。あら簡単。

早速実験してみます。1回処理すると以下のようになる Entity を用意しました。
 このクラス定義のところにある correct というプロパティに Index を作らないようにします。
 @Persistent
 @Extension(vendorName = "datanucleus", key = "gae.unindexed", value="true")
 private Boolean correct;
とすると、
のように2減りました。この2というのが昇順と降順で1つずつの結果のようです。
#上では赤丸がついている Entity にのみ処理をしました。

これで削減できるかな?

2013年3月18日月曜日

Eclipse + Maven その2

続きです。これだけでも十分便利なんですが、よくできてんなと思ったところをもう少し。今回想定しているのは前回作ったプロジェクトで Selenium を使ったコードを書いて隠蔽し、それを別のプロジェクトから利用するというシナリオです。

まず sample1 の App クラスを変更しておきます(別に main のままでもできますが、雰囲気を出すために)。そして後述する実験のために一度 sample1 を install しておきます。sample1 の pom.xml を右クリックして、Run As -> Maven install... を実行します。
 sample1 と同じように sample2 プロジェクトも作ります。
で、pom.xml を開き、dependency のところで sample1 を追加します。
すると
のようになります。アイコンでわかるとおりプロジェクト参照になってます。なので普通に sample1 のクラスが使えるので
とやって実行しただけでブラウザが上がってきます。

これだけでも十分便利なんですが、よくできてんなと思ったのはこの先です。さきほどプロジェクト参照になってると書きましたので、sample1 の App のほうを

のように変更して sample2 の Main を実行すると
と Yahoo! が開きます。ここで sample1 を閉じます。
すると先ほどプロジェクト参照になっていたものが最初にインストールしたほうに自動的に変更されます。
なので実行すればちゃんと動作しますが、開くのは Google のほうです。つまり、インストールされていればその依存を書くだけで使えますが、プロジェクトがあればそちらのコード変更を即座に反映した開発が可能ということです。これは便利ですね。

2013年3月16日土曜日

Eclipse + Maven その1

ひょんなことから Selenium を使う機会がありまして。それ自体はいいんですが前提のライブラリが多いこと多いこと・・・。これをマネージするのは正直つらそう。そう思いましてせっかくなので Maven を使ってみました。これは便利ですね。なので備忘録をかねて少々ご紹介。

まずは m2e を入れておきます。するとプロジェクトを作成するときに Maven Project が選べるようになります。
  先へ進んで最後に Group Id と Artifact ID を入れると


こんな感じにプロジェクトが作成されます。
左上に「M」がついてるので Maven 用に構成されたプロジェクトであることがわかります。pom.xml も作成されているので開くとエディターが開きます。
今回は Selenium を使いたいので依存関係を定義します。Dependencies のタブを開いて Add をクリックします。ダイアログが開くので、フィルターのところに selenium-java と入力して selenium-java を選んで

追加します。すると

このように自動的に classpath にも追加されます。こうなれば

のようにコードを書いて実行すると
 と動くわけです。

2013年2月10日日曜日

824年に一度

ご無沙汰しております。ツイッター等で既にご存知かもしれませんが、出張からは無事帰ってきました。体調的にはかなり悪くて楽しめたとはいえない出張でしたけどね・・・。出張で土日つぶしてるのに代休とかも取れないとか労 (ry

そんなわけで(?)あまり遊ぶ時間もなく書くネタもなかったのですが、少しだけ。今日ツイッターでこんなツイートが回ってきました。(以下の内容のため一応元は伏せます。)
今月は土曜日、日曜日、月曜日が5日ずつある、824年に一度しかない風水で言う金袋という椿事で・・・(以下略)
ほほう、そんなめでたい月なのですか、と1秒ぐらい思いましたが、今月って2月ですよね。週の連続した3つの曜日が5日ずつあるためには、少なくとも31日ある月じゃないと成り立たない。 しかも土、日、月であるためには当然1日は土曜日である必要がある。今月は1日は金曜日だし、2月なので31日なわけもなく、どちらも満たしてないでしょうよ。

と思って基本スルーなのですが、そもそも感覚的に「1日が土曜日で始まる31日まである月」が824年に一度なわけないのでは?と思いますよね。長い期間で計測したとして、ざっと1日が土曜になる確率は1/7として、そのうち31日である月は1,3,5,7,8,10,12月で7月あるので7/12です。掛け合わせると7/84になり、月のうち実に8.3%の確率で起きます。年単位に直すならば12を掛けて100%、つまりほぼ毎年起きます!?(実際には1/7のとことかがぶれるので起きない年もあるはずです。)

なのでベタに今年から100年前まで数えてみました。もちろん手・・・ではなくコンピューターでw
import java.util.Calendar;


public class CalendarTest {
 public static void main(String[] args) {
  Calendar cal = Calendar.getInstance();
  cal.set(Calendar.MONTH, Calendar.DECEMBER);
  cal.set(Calendar.DATE, 1);
  final int end = cal.get(Calendar.YEAR)-100;
  int count = 0, satisfied = 0;
  int year;
  while ((year = cal.get(Calendar.YEAR)) > end) {
   count++;
   do {
    if (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY &&
      cal.getActualMaximum(Calendar.DAY_OF_MONTH) == 31) {
     satisfied++;
     cal.set(Calendar.MONTH, Calendar.JANUARY);
    }
    cal.add(Calendar.MONTH, -1);
   } while (year == cal.get(Calendar.YEAR));
  } 
  System.out.println(satisfied + " years satisfied in " + count + " years");
 }
}
答えは
86 years satisfied in 100 years
という感じでした。つまりそんな月が起きない年は100年中14年、むしろない年のほうがはるかに珍しいですね、といいつつ14%じゃそれほど珍しくもないw

しかし何をどうすると824年なる数字が出てくるんですかね。むしろその数字の原因を探したかったんですが、特にそんなヒントは得られず。適当に選ばれたのかな。

2013年1月3日木曜日

#f1yosou : HRD への移行

というわけで、HRD への移行を行ってみます。基本はガイドに従えばいいはず・・・。

TaskStatusRemarks
Duplicating Your ApplicationDone
Deploying Your New HRD ApplicationDone
Using the Migration ToolDone結構待ちます。

あれ、結構簡単でした?w

しかし実際は index の定義をちゃんと書いてるわけではない(Eclipse 上で実行していると自然と生成される定義を使っていた)ので、最初に新しいアプリケーションのほうに deploy したら索引が全くない状態になってしまいました。なので開発環境で適当に実行して生成された定義を使ってもう一度 deploy。今度はうまくいきました。

果たして migration した元のアプリケーションは消してよいのか。消さないと10個までというアプリケーション数の制限を圧迫するんだけど・・・。

同様にチェックしたDMログ(仮)は実はバグがあってだいぶ前から動いてなかったっぽい。けど何も言われてないってことはそろそろ消してもいいかな?政治家的にいえば「一定の役割は果たした」のでw

#f1yosou : 現状の説明

みなさんご存知の通り #f1yosou の中の人をやっているわけですが、今年もやるためには以前ツイートしたように、
といった作業が必要になります。作業を分類すると

必須の作業
  • HRD 移行
  • Twitter API V1.1 対応
ほぼ必須の作業
  •  公式アカウントによる参加者のフォロー&TL読み込み(Twitter API V1.1でリスト関連の API が変更になったため、どのみち何らかの作業が必要なため)
できればやりたい作業
  • DMによるお題投票
  • ログインシステム廃止
といった感じです。

このうち Twitter API V1.1 対応については Twitter4J V3.0.3 への更新が完了したので基本問題ないはずです。なので残る作業は HRD 移行とフォロー&TL読み込みです。

ちなみにHRDなんじゃそれと言う方も多いと思うので一応説明しておくと、#f1yosou のサイトは Google App Engine for Java といういわゆる PaaS サービス(の無料部分を!)利用しています。しかし #f1yosou サイトを立ち上げた当時にはデータストアとして Master/Slave というものが使われていたのですが、それが deprecated となり、HRD (High Replication Datastore)に1年以内に移行する必要が出てきました。一応別のアプリケーションとして新たに同じコードを deploy したらちゃんと HRD 上でも動いたので移行作業さえ終われば問題はないはずですが・・・。面倒そうな雰囲気だけがありますw

さて今年の開催も含めてどうするか。悩みどころです。

2012年12月31日月曜日

HDDのチェック

フォロワーさんが CrystalDiskInfo のキャプチャをアップされていてそれを見てあわててディスクの状況を見てみようと思いまして。Linux だとどうするのか調べたところ、smartctl なるコマンドが有用だそうです。


# smartctl -a /dev/sda
smartctl 5.42 2011-10-20 r3458 [x86_64-linux-2.6.32-279.19.1.el6.x86_64] (local build)
Copyright (C) 2002-11 by Bruce Allen, http://smartmontools.sourceforge.net

=== START OF INFORMATION SECTION ===
Model Family:     Western Digital Caviar Green (Adv. Format)
Device Model:     WDC WD20EARS-00MVWB0

(snip)

ID# ATTRIBUTE_NAME          FLAG     VALUE WORST THRESH TYPE      UPDATED  WHEN_FAILED RAW_VALUE
  1 Raw_Read_Error_Rate     0x002f   200   200   051    Pre-fail  Always       -       0
  3 Spin_Up_Time            0x0027   253   205   021    Pre-fail  Always       -       1025
  4 Start_Stop_Count        0x0032   100   100   000    Old_age   Always       -       548
  5 Reallocated_Sector_Ct   0x0033   200   200   140    Pre-fail  Always       -       0
  7 Seek_Error_Rate         0x002e   200   200   000    Old_age   Always       -       0
  9 Power_On_Hours          0x0032   083   083   000    Old_age   Always       -       12921
 10 Spin_Retry_Count        0x0032   100   100   000    Old_age   Always       -       0
 11 Calibration_Retry_Count 0x0032   100   100   000    Old_age   Always       -       0
 12 Power_Cycle_Count       0x0032   100   100   000    Old_age   Always       -       523
192 Power-Off_Retract_Count 0x0032   200   200   000    Old_age   Always       -       32
193 Load_Cycle_Count        0x0032   011   011   000    Old_age   Always       -       567391
194 Temperature_Celsius     0x0022   109   099   000    Old_age   Always       -       41
196 Reallocated_Event_Count 0x0032   200   200   000    Old_age   Always       -       0
197 Current_Pending_Sector  0x0032   200   200   000    Old_age   Always       -       0
198 Offline_Uncorrectable   0x0030   200   200   000    Old_age   Offline      -       0
199 UDMA_CRC_Error_Count    0x0032   200   200   000    Old_age   Always       -       0
200 Multi_Zone_Error_Rate   0x0008   200   200   000    Old_age   Offline      -       0
とりあえずやばそうなのはない・・・よね?w
ほかのディスクも同様な結果でした。本当はこれを定期的に実行して値が変わったらお知らせとかしないといけないんでしょうね。