ボルダリングカフェ グリーンアローMYO-DENの紹介

この記事は #kabepy Advent Calendar 2012 の16日目です。僕の知る限りだと、東西線沿線にはボルダリングジムが二つ存在します。今回はその中の一つ「ボルダリングカフェ グリーンアローMYO-DEN」をご紹介します。

ボルダリングカフェ グリーンアローMYO-DENについて

アクセス

東西線妙典駅から徒歩5分程度の場所にあります。東西線は他線との接続が悪く、アクセスがあまり良くありませんが、僕は通勤経路なので助かってます。コンビニは駅内のファミリーマートと、ジムに行く途中のローソンの二店舗があります。

ボルダリングカフェ グリーンアローMYO-DENの外観 f:id:kiris60:20121216181330j:plain

営業時間

  • 平日 14:00〜21:00
  • 土日祝日 11:00〜21:00
  • 定休日 毎週月曜日(祝日の場合はその翌日)

料金

施設利用料

  • 会員登録費 1,000円
  • 1日利用料 2,000円
  • ナイト利用料(18:00〜) 1,500円
  • 1時間利用(平日10:00〜18:00) 1,000円 チョーク・シューズのレンタル無料

平日割引(1日利用料500円引き・ナイト利用料300円引き)

  • メンズDAY(火・木曜日)
  • レディースDAY(水・金曜日)

レンタル(いずれも初回会員登録時に限り無料)

  • シューズ 300円
  • チョーク 200円

設備

壁は全部で9面、角度は87°〜120°まで。傾斜は5°刻みで設けられているのでボルダリングの初心者でも楽しめます。

■店内。中央には飲食が出来るテーブルがあり、よくお菓子が置かれている。

f:id:kiris60:20121216181632j:plain

■普段ノートパソコンを持ち歩くので鍵付きロッカーは地味に嬉しい

f:id:kiris60:20121216181906j:plain

更に「BIG WAVE」や「天空の壁」と呼ばれる多面体が設置された壁も存在します。

■多面体壁の一つ「BIG WAVE」。こいつが中々手強い。

f:id:kiris60:20121216182442j:plain

魅力

アットホームな店内

このジムの面白い所は、他のどのジムよりもファミリーを意識した所です。 例えばこのジムには「キッズスペース」と呼ばれるエリアが存在します。

■キッズスペースに続く階段。外見の段階でワクワク感が半端無い。

f:id:kiris60:20121216181651j:plain

キッズスペースの中は象の滑り台や、ハンモック、キッズウォールなどが設置され、秘密基地のようになっています。

■俺も子供の頃に遊びに来たかった

f:id:kiris60:20121216194817j:plain

通常の壁にも「キッズ課題」が設置されており、親子でも楽しめるのがこのジムの魅力です。(大抵子供の方が上手い)

課題表

店内では無料で課題のチェック表が配られています。 モチベーションの向上にも繋がりますし、久しぶりに来てもどれが登れたかを忘れる心配がありません。 これは素晴しいアイディアなのでぜひ全てのジムで実施して欲しい。

■6級・7級は埋まってきたので次は4級・5級課題を攻めて行きたい

f:id:kiris60:20121216235943p:plain

カフェ

ボルダリングカフェという名の通り、店内ちょっとした売店があります。コーヒーやシェイクやペットボトルの他にも、お菓子・カロリーメイトといった長時間登る際のちょっとした補給に便利です。

■ドリンクメニュー。バナナシェイクが美味しかった。

f:id:kiris60:20121216202935j:plain

まとめ

ボルダリングジムはそれぞれ個性がありますが、その中でもボルダリングカフェ グリーンアローMYO-DENはかなりユニークな店だと思います。場所だけが少々ネックですが、興味を持って頂けたならば今度一緒に行きましょう。

■ジムに行く途中にあるイタリアンのお店が美味くてお勧め

f:id:kiris60:20121216203411j:plain

店舗情報

Annotation ProcessorでSenchaのModelを自動生成する

@kawanoshinobu は普段はとっても温厚そうです。
でもキレるとPCの電源を引き抜いたりします。こわいです。そんな @kowanoshinobu に 「ワレ、いつもSenchaを教えてやっとるやんけ。なんか書けや!」と言われました。こわいです。

そんな訳で

この記事はSencha Advent Calendar 2012の11日目です。JavaScriptはそんなに好きではないので、そんなに好きではないJavaの話をします。

SenchaにはModelというクラスが存在します。その名の通りMVCのModelです。SenchaではModelクラスを使ってJSONのデシリアライズをします。

クライアントサイドから見ればModelでも、サーバーサイドから見ればViewです。サーバーサイドでもやっぱりModelっぽいクラスを作って、それをJSONにシリアライズします。Mapだと型がObjectになるから嫌いです。

サーバーサイドでModelを作ってクライアントサイドでModelを作りました。困りました。DRYじゃないです。DRYじゃないのは悪です。例外も一杯ありますがこれは悪です。

なのでAnnotation Processorを使って、JavaのModelからSenchaのModelを自動生成してみることにしました。

Annotation Processorって何?

JDK 6から入ったコンパイル時にJava構文木を弄ったりする仕組みです。例えば僕が好きなLombokというライブライリはこれでgetterやsetterを自動生成したりしています。

Rubyとかでツールを作っても良いのですが、ツールだと自動生成し忘れる可能性があります。それにAnnotation Processorなら構文木を直接触れるので、「staticなフィールドは自動生成しない」とか「Javaの継承関係をSenchaでも維持する」とか色々出来ます。ScalaなんかだとCompiler Pluginというもっと強力な仕組みも存在します。

あ、ちなみにこのエントリではAnnotation Processor使ってこんなこと出来るよーに留めるので詳しい解説はしません。Annotation Processorについての解説はこちらの連載がとても詳しく書いてます。

いきなり完成系

例えばこんなJavaのModelを作ってコンパイルすると

package localhost.sample;

import localhost.annotation.Model;

@Model(namespace="localhost.generate", dir="web/smartdevice/localhost/generate/";
public class Sample {
	private String name;
	private int value;

        @Field(exclude=true)
        private String excludedField;
}

勝手にこんなJavaScriptファイルを作ります。

// auto-generated at Fri Nov 09 22:30:37 JST 2012

/**
 * CAUTION: 
 *   Don't modify this file!
 *   This file is automatically generated.
 */
Ext.define('localhost.generate.AbstractSample', {
    extend: 'Ext.data.Model',
    config: {
         fields: [
             { name: 'name', type: 'string' },
             { name: 'value', type: 'int' },
         ]
    }
});

実際はこのクラスを継承して使います。変更を加え先から自動生成で上書かれてしまっては困りますもんね。コンフリクトするのでバージョン管理もしません。

アノテーションを作る

Annotation Processorなのでアノテーションが無いと始まりません。今回は@Modelと@Fieldの二つのアノテーションを作ります。@Modelはクラスに付けれて@Fieldはフィールドに付けることが出来ます。@Fieldは省略可能です。

@Model

package localhost.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Model {
        // クラス名のprefix
	String prefix() default "Abstract";
        // 出力先
	String dir();
        // 名前空間
	String namespace();
}

@Field

package localhost.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Field {
    // フィールド名(省略時はJavaのフィールド名)
    String name() default "";
    // データの種類(省略時はJavaのフィールドの型から自動判別)
    String type() default "";
    // 除外フラグ
    boolean exclude() default false;
}

Annotation Processorを作る

AbstarctProcessorを継承したクラスを作ります。
こいつがエントリーポイントです。

package localhost.apt;

@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("localhost.annotation.Model")
public class SenchaModelGen extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv) {
        for (TypeElement annotation : annotations) {
            for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
                // @Modelが付いてるクラスだけを取ってこれた

                assert element instanceof TypeElement;
                TypeElement typeElement = (TypeElement) element;

                try {
                    // @Modelを取得
                    Model model = element.getAnnotation(Model.class);
                    
                    // ここにSenchaのModelを生成する処理を書く
                } catch (Exception e) {
                    this.processingEnv.getMessager().printMessage(Kind.ERROR, e.getMessage(), typeElement);
                }
            }
        }
        return true;
    }

で、例えばModelを作る条件は
「アクセス修飾子がPublicで具象クラスであること」とか

        public boolean isGenerable() {
            if (this.element.getKind() != ElementKind.CLASS) {
                return false;
            }
            if (!this.element.getModifiers().contains(Modifier.PUBLIC)) {
                return false;
            }
            if (this.element.getModifiers().contains(Modifier.ABSTRACT)) {
                return false;
            }
            return true;
        }

フィールドの型は「boolean型なら'boolean'で、byte型やshort型やint型やchar型だったら'int'」だとか

        private String getFieldType() {
            if (field != null && !field.type().isEmpty()) {
                return field.type();
            } else {
                TypeKind typeKind = this.element.asType().getKind();
                switch(typeKind) {
                case BOOLEAN:
                    return "boolean";

                case BYTE:
                case SHORT:
                case INT:
                case LONG:
                case CHAR:
                    return "int";

                case FLOAT:
                case DOUBLE:
                    return "float";

                default:
                    return "";
                }
            }
        }

構文木とか大袈裟なことを言っていますが、ひとつひとつの処理は簡単です。そんなことをしながら最終的なJavaScriptファイルを作成します。

        public void generate(PrintWriter writer) {
            this.generateModelHeader(writer, this.model, this.element);

            for (VariableElement fieldElement : ElementFilter.fieldsIn(this.element.getEnclosedElements())) {
                FieldGenerator generator = new FieldGenerator(this.processingEnv, this.model, fieldElement);
                if (generator.isGenerable()) {
                    generator.generate(writer);
                }
            }

            this.generateModelFotter(writer, model, element);
        }

        private void generateModelHeader(PrintWriter writer, Model model, TypeElement element) {
            String namespace = this.getNameSpace();
            String modelName = this.getModelName();
            String absoluteModelName = namespace + (namespace.isEmpty() ? "" : ".") + modelName;

            writer.println("// auto-generated at " + new Date());
            writer.println("");
            writer.println("/**");
            writer.println(" * CAUTION: ");
            writer.println(" *   Don't modify this file!");
            writer.println(" *   This file is automatically generated.");
            writer.println(" */");
            writer.println("Ext.define('" + absoluteModelName + "', {");
            writer.println("    extend: 'Ext.data.Model',");
            writer.println("    config: {");
            writer.println("         fields: [");
        }

        private void generateModelFotter(PrintWriter writer, Model model, TypeElement element) {
            writer.println("         ]");
            writer.println("    }");
            writer.println("});");
        }

わりと愚直ですね。そのうちvelocityテンプレートとかにするつもりです。

仕上げ

最後にsrc/META-INF/javax.annotation.processing.Processorというファイルを作って、その中にAnnotation Processorの在処を書きます。

localhost.apt.SenchaModelGen

後はjar化したものをクラスパスに通してコンパイルすれば、先程のJavaScriptファイルが自動生成されます。

まとめ

雰囲気だけを伝えるつもりだったのでかなーり端折りました。結局言いたかったのは「同じこと二度書くの面倒だよねー」と、それに対するJavaでの一つのアプローチ方法の紹介です。こういったものはどんどん自動化して楽したいですね。

遊びで作ったものなのでまだまだ未完成品ですが、ソースコード一式をgithubに上げておきます。将来的にはサーバサイドのバリデーションからクライアントサイドバリデーションを作ったり、逆にSenchaのModelの永続化命令をJava側のModelで受けたりしたいですね。

というわけで、明日は shuhei aoyama さんで「Proxyのはなし」です。

ボルダリングの危険性について

この記事は #kabepy Advent Calendar の九日目です。

お前誰よ

twitter id: @kiris です。Pythonボルダリング部の「Python書かない」担当です。

内容

ボルダリングの話をするとこういった質問をされることが良くあります。

「でもボルダリングって危なくないんですか?」

確かに誰も趣味や遊びで危険な目に会いたくは無いでしょう。なので今回はボルダリングにはどのような危険性があるかを解説したいと思います。

ボルダリング部の始動

私達Pythonボルダリング部が活動を開始したのは2011年12月22日、今から一年ほど前のことです。

f:id:kiris60:20121209171842p:plain
http://connpass.com/event/177/

メンバーの大半が未経験者でしたが、誰も怪我などをすることもなく、つつがなく活動を終えました。この時はまだ。

依存症(初期症状)

最初の活動から一ヶ月が過ぎると、部員達の間では「自主練」と称して、ボルダリングの活動が徐々に増えるようになりました。

しかし、ボルダリングには強い中毒性を持ち、慢性的な依存症を誘発します。そしてそれはPythonボルダリング部の部員も例外では有りませんでした。

部員達にボルダリング依存症の初期症状が表れ出したのです。


禁断症状


生活態度の変化


多幸感とその反動による鬱症状


「この段階で辞めれば良いじゃないか」と思うかもしれません。しかし依存症は、依存に陥りやすい脳内麻薬分泌を正常に制御できない状況が引き起こした「病気」です。そのため、本人達が依存症を自覚することは出来ても、それを改善することは容易なことではありません。

依存症(中期症状)

ボルダリングへの依存は回数を重ねる度に更に増して行きます。そしてそのままボルダリングを続けると、より重度症状が発症します。

強い禁断症状
@shomah4a
f:id:kiris60:20121209172029p:plain


思考滅裂


体の痙攣


モヒカン化


これらは全てボルダリング中毒がもたらす主要な依存症状です。ここまで来るともはや自らの力で改善することは不可能であり、まともな社会生活を行なうのも困難になってきます。

依存症(末期)

そしてその後、Pythonボルダリング部はどうなってしまったのか。

その結果がわかるのがこちらです。

http://connpass.com/event/960/
f:id:kiris60:20121209172043p:plain

┌(┌ ^o^)┐カベェ

結論

「でもボルダリングって危なくないんですか?」

ボルダリングは大変危険です」

「JavaScriptテクニックバイブル」という冒険の地図

JavaScriptテクニックバイブル」って何?

8/31日に発売する本です。8/31日に発売する本です。8/31日に発売する本です。

JavaScriptテクニックバイブル ~効率的な開発に役立つ150の技

JavaScriptテクニックバイブル ~効率的な開発に役立つ150の技

僕も一部書かせて頂きました。

この本の内容について詳しく知りたい方はこのエントリーの他にも、既に@cimadaiさんや@masahitoさんが振り返り記事を公開しているのでそちらもご覧頂ければと思います。

どんな本なの?

まずはこの本の概要を3000行ほど使って説明しようと思ったのですが、@masahitoさんがわかりやく4行で説明してくれていたので引用することにします。

Webアプリケーションを作るときの杖(not 魔法の杖) or 冒険の地図というのがコンセプトの一部です。

JavaScriptの知識はWebを検索すればすぐに見つかるようになりました。

しかし、見つかるものはすでに古くなっていたり、いい記事にたどり着くまでに時間がかかる事も多く、

こういうのをなんとかしたいというのがこの本の目的です。

JavaScriptの前身であるLiveScriptが1995年に開発されて以降、ライブラリの発展、ECMA-262の策定、サーバーサイドJavaScriptのブームなど様々な道が作られてきました。

その一方で、長い歴史と共に古くなってしまった道も多く存在します。私の同僚は「Webの歴史は互換性維持の歴史」だと言いました。JavaScriptもまた、多くの古いものを残しながら進化を続けてきた言語です。

僕は入り組んだ場所で目的地に辿りつく為には地図が必要だと思います。それも出来るだけ新しく、広域を描いた地図が。

JavaScriptテクニックバイブル」は「今のJavaScript」をもとに作られた一冊の地図です。

本の内容

JavaScriptテクニックバイブル」は全十一章で構成されています。

一章から二章では「JavaScriptの開発環境」や「デバッグの仕方」などの基本的な歩き方から学ぶことが出来ます。また三章の「テストの仕方」を学ぶことで、より安全に歩く為の方法を知ることが出来ます。

メインとなる四章から九章では、JavaScriptの根幹でありながら実はちゃんと理解されていない「this」や「オブジェクト」や「スコープ」といった説明から始まり、「DOMの操作」や「パフォーマンス」「ネットワーク」などの多岐に渡るトピックを扱っています。

最後の十章と十一章では最新の道とも言える「HTML5」や「JavaScriptライブラリ」について解説します。

これらの内容が、皆さんの旅路にとっての最良の地図になれるのかはわかりませんが、ポケットに一冊忍ばせて頂けたら幸いです:)

最後に

テクニックバイブルシリーズは、Emacsユーザーなら誰もがきっと一度はお世話になっており僕の憧れでもある、@rubikitchさんによる「Emacsテクニックバイブル」によって始まったシリーズです。

そのテクニックバイブルシリーズに末席ながら携わたことを大変光栄に思うと共に、このようなご切っ掛けを作ってくれた@cimadaiさん、@masahitoさん、@rokujyouhitomaさん及び、技術評論社の皆様に心より御礼申し上げます。

Play 2.0 コードリーディング - リクエストを受ける(前)

一人Play 2.0 コードリーディングの第二弾です。

今回はリクエストを受けとった時にどのような処理をするかを追ってみました。
そもそも発端はPlay2.0ではリクエストをどのように処理させているのか気になり、
意図的に例外を発生させてみました。

すると、出力されたスタックトレースは以下のようなものだったのです。

play.core.ActionInvoker$$anonfun$receive$1$$anon$1: Execution exception [[Exception: null]]
        at play.core.ActionInvoker$$anonfun$receive$1.apply( ...
        at play.core.ActionInvoker$$anonfun$receive$1.apply( ...
        at akka.actor.Actor$class.apply( ...
        at play.core.ActionInvoker.apply( ...
        at akka.actor.ActorCell.invoke( ...
        at akka.dispatch.Mailbox.processMailbox( ...
Caused by: java.lang.Exception: null
        at controllers.Application$$anonfun$index$1.apply( ...
        at controllers.Application$$anonfun$index$1.apply( ...
        at play.api.mvc.Action$$anonfun$apply$4.apply( ...
        at play.api.mvc.Action$$anonfun$apply$4.apply( ...
        at play.api.mvc.Action$$anon$1.apply( ...
        at play.core.ActionInvoker$$anonfun$receive$1$$anonfun$4.ap ...

大本がakka.dispatch.Mailbox.processMailboxになっており、
Play2.0本体のスタックがほとんど出力されていません。
こりゃどういうことだ?と気になってソースを追ったのがこのエントリーです。

結論的を先に書いてしまうと、
Play2.0は以下の流れでリクエストを処理していました。

  1. リクエストを受けとるとNettyの仕組みを使って play.core.server.netty.PlayDefaultUpstreamHandlerがハンドリングする
  2. play.core.Serverを経由してplay.core.Router.RoutesがActionを決定する
  3. AkkaにActionを実行する為のメッセージが登録される
  4. AkkaからActionを実行する

これらの処理を順を追って説明したいと思います。
大半は自分のためのメモのため、ダラダラと長くなってしまったので前後編に分けます。


さて、Play2.0は内部的にはNettyを使用して動いています。
Play2.0自体はNettyのラッパーと言っても過言ではありません。
そのため、リクエストを受けとるとorg.jboss.netty.channel.SimpleChannelUpstreamHandlerのサブクラスでハンドリングします。

つまりそれは、play.core.server.netty.PlayDefaultUpstreamHandlerのことです。
リクエストを受けとるとPlayDefaultUpstreamHandlerのmessageReceivedメソッドが実行されます。

private[server] class PlayDefaultUpstreamHandler(server: Server, allChannels: DefaultChannelGroup) 
    extends SimpleChannelUpstreamHandler with Helpers 
    with WebSocketHandler with RequestBodyHandler {
  // Play2.0本体で、リクエストを受けとった時に一番最初に実行されるメソッド
  override def messageReceived(
      ctx: ChannelHandlerContext,
      e: MessageEvent) {
    e.getMessage match {
      case nettyHttpRequest: HttpRequest => {
        val keepAlive = isKeepAlive(nettyHttpRequest)
        val websocketableRequest = websocketable(nettyHttpRequest)
        var version = nettyHttpRequest.getProtocolVersion
        val nettyUri = new QueryStringDecoder(nettyHttpRequest.getUri)
        val parameters = Map.empty[String, Seq[String]] 
              ++ nettyUri.getParameters.asScala.mapValues(_.asScala)

        ...

        // Actionの取得
        val handler = server.getHandlerFor(requestHeader)

messageReceivedではリクエストヘッダーの解析等を行なった後、
server.getHandlerForでActionを取得する処理が行なわれます。

serverのクラスはplay.core.server.NettyServerクラスで、
getHandlerForの返り値の型はEither[Result, (Handler, Application)]です。
つまり、正常にActionが決定した場合rightでHandlerとApplicationのタプルを、
エラーが発生した場合はleftでResultを返すということです。
HandlerはActionがmix-inしているトレイトでメンバーを一切もたない、
マーカーの為の型です。


続いてgetHandleForの中身も見てみます。getHandleForの定義はplay.core.server.Serverトレイトにあります。

  def getHandlerFor(request: RequestHeader): Either[Result, (Handler, Application)] = {

    def sendHandler: Either[Throwable, (Handler, Application)] = {
      applicationProvider.get.right.map { application =>
        val maybeAction = application.global.onRouteRequest(request)

        // Actionが見つからなかった場合はNotFound用のHandlerを返す。
        (
          maybeAction.getOrElse(
            Action(BodyParsers.parse.empty)
              (_ => application.global.onHandlerNotFound(request))), 
            application
        )
    }
    def logExceptionAndGetResult(e: Throwable) = {
      // エラー発生時のハンドリング
      DefaultGlobal.onError(request, e)
    }

    sendHandler.left.map(logExceptionAndGetResult)
  }

getHandlerForのポイントは以下の三つです。

  1. play.core.ApplicationProviderからApplicationを取得しています。
  2. Actionの取得処理をapplication.global.onRouteRequest()に移譲しています。
  3. エラーのハンドリングをgetHandlerForで行なっています。Actionが見付からなかった場合はNotFoundを返すHandlerを返し、途中で例外が発生した場合はDefaultGlobal.onErrorを使ってInternalServerErrorのResultを返します。

applicationProviderの実際の型はPlayを実行しているコンテキストから、以下の三つのサブクラスの中から一つが選ばれます。

  1. StaticApplication - 本番稼動などで使用するApplicationProvider
  2. TestApplication - テスト用のApplicationProvider
  3. ReloadableApplication - Dev Mode用のリロードが出来るApplicationProvider

application.global.onRouteRequestではconfig/routesをもとに、
実際にActionを取得する処理を行なっていますが、続きは後編で解説します。

Play 2.0 コードリーディング - プラグインの読み込み

最近はボルダリングに嵌っています。

提示された課題に対して、自身が持つリソースを使っていかに解決するかを
考えるのはプログラミングに近い楽しさだなぁ、とは思っていません。


先日、「Play Framework 2.0 ソースコードリーディングの会」でなんか話せって言われました。

http://partake.in/events/310d72cb-b5b2-4503-98c7-df5887582c27

話す内容はまだ決っていません。

そんなわけでPlay 2.0のコードをぼちぼち読み始めているのですが、
折角なのでその内容を残しておこうと思いました。
一人Play Framework 2.0 ソースコードリーディングの会結成の瞬間です。

記念すべき第一回はModule(Plugin)の読み込みです。
いきなりマニアックな部分から始まっているのは僕がたまたま興味があったからです。

Moduleを自作する方法についてはfitsさんの記事など参照してください。

http://d.hatena.ne.jp/fits/20120324/1332584629


さて、Moduleの読み込みはplay.api.Applicationが行なわれます。
まず始めに、Application.pluginClassesメソッドを使用して設定ファイルを読み込みます。

class Application {
  private[api] def pluginClasses: Seq[String] = {

    import scalax.file._
    import scalax.io.JavaConverters._
    import scala.collection.JavaConverters._

    val PluginDeclaration = """([0-9_]+):(.*)""".r

    val pluginFiles = 
      classloader.getResources("play.plugins").asScala.toList ++ 
      classloader.getResources("conf/play.plugins").asScala.toList

    pluginFiles.distinct.map { plugins =>
      (plugins.asInput.slurpString.split("\n").map(_.trim))
          .filterNot(_.isEmpty).map {
        
        case PluginDeclaration(priority, className) => 
          (priority.toInt, className)
      }
    }.flatten.sortBy(_._1).map(_._2)
  }

設定ファイルのフォーマットは 450:plugins.SamplePlugin のように、
読み込み順のプライオリティとクラス名をコロン区切りにしたもので、
正規表現を使ってこれをパースし、プライオリティ順に並びかえています。
実際にpluginClassesを使っているのは、同じくApplicationのpluginsを定義する所です。

class Application {
  val plugins: Seq[Plugin] = Threads.withContextClassLoader(classloader) {
    pluginClasses.map { className =>
      try {
        val plugin = classloader.loadClass(className)
              .getConstructor(classOf[Application])
              .newInstance(this).asInstanceOf[Plugin]
        if (plugin.enabled)
          Some(plugin)
      } catch {
        case e: java.lang.NoSuchMethodException => {
          val plugin = classloader.loadClass(className)
                .getConstructor(classOf[play.Application])
                .newInstance(new play.Application(this))
                .asInstanceOf[Plugin]
          if (plugin.enabled)
            Some(plugin)
        }
      }
    }.flatten
  }
}

pluginClassesで作ったクラス名のインスタンスを
ClassLoaderとリフレクションを使って順番に生成しています。

NoSuchMethodExceptionが発生した時は、
引数をplay.Applicationに変更して再実行していますが、
play.Applicationはjavaで書かれており、
恐らくjavaで書かれたModuleを読み込むためのものだと思います。


ここまででModuleの読み込みは完了です。

後は実際に、 play.api.Play.start や play.api.Play.stop によるハンドラの実行や、

object Play {
  def start(app: Application) {
    // First stop previous app if exists
    stop()

    _currentApp = app

    app.plugins.foreach(_.onStart)

    app.mode match {
      case Mode.Test =>
      case mode => 
        Logger("play").info("Application started (" + mode + ")")
    }
  }

  def stop() {
    Option(_currentApp).map {
      _.plugins.foreach { p =>
        try { p.onStop } catch { case _ => }
      }
    }
    _currentApp = null
  }
}

play.api.Application.plugin を使ったインスタンスの取得が行なわれます。

class Application {
  def plugin[T](implicit m: Manifest[T]): Option[T] =
    plugin(m.erasure).asInstanceOf[Option[T]]

  def plugin[T](pluginClass: Class[T]): Option[T] =
    plugins
      .find(p => pluginClass.isAssignableFrom(p.getClass))
      .map(_.asInstanceOf[T])
}

Play2.0でmoreStylesとmoreScripts

環境

  • Play framework 2.0 RC1(trunk)

Scala Templateの練習。
機能が用意されていなくてもScalaコードが書けるので簡単に実現できますね。

javaScripts.scala.html

@(scripts: Seq[String])

@scripts.map { script =>
  <script src="@routes.Assets.at(script)" type="text/javascript"></script>
}

styleSheets.scala.html

@(styleSheets: Seq[(String, String)])

@styleSheets.map { case (media, styleSheet) =>
  <link rel="stylesheet" media="@media" href="@routes.Assets.at(styleSheet)">
}

main.scala.html

@(moreStyles: Seq[(String, String)] = Nil, moreScripts: Seq[String] = Nil)(content: Html)
<!DOCTYPE html>
<html>
  <head>
    <title>sample</title>
    <link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.png")">

    @styleSheets(
      List(
        ("screen", "stylesheets/main.css")
      ) ++ moreStyles
    )

    @javaScripts(
      List(
        "javascripts/jquery-1.7.1.min.js"
      ) ++ moreScripts
    )
  </head>
  ...

index.scala.html

@main(
  moreScripts = List("javascripts/moreScript.js")
) {
  hello!
}

デザイナにとっては見た目がやさしくないかもしれないけど便利。