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

2015年4月7日火曜日

System properties を sbt run 時に切り替え

Typesafe Config の設定ファイルを sbt run 時に指定したく、"-Dconfig.file=develo.conf" の指定時に嵌った・・・

System properties の切り替えのみの構成で、

./
 |
 +--build.sbt
 |
 +--project
 |  |
 |  +--build.properties
 |
 +--sampleconfig1
 |  |
 |  +--build.sbt
 |  |
 |  +--src
 |     |
 |     +--main
 |        |
 |        +--scala
 |           |
 |           +--SampleConfig1.scala
 |
 +--sampleconfig1.conf
 |
 +--sampleconfig2
 |  |
 |  +--build.sbt
 |  |
 |  +--src
 |     |
 |     +--main
 |        |
 |        +--scala
 |           |
 |           +--SampleConfig2.scala
 |
 +--sampleconfig2.conf

./build.sbt
name := "sampleconfig"

lazy val sampleconfig1 = project

lazy val sampleconfig2 = project

./project/build.properties
sbt.version=0.13.8

./sampleconfig1/build.sbt
name := "sampleconfig1"

./sampleconfig2/build.sbt
name := "sampleconfig2"

./sampleconfig1/src/main/scala/SampleConfig1.scala
object SampleConfig1 extends Application {
  println("SampleConfig1: " + System.getProperty("config.file"))
}

./sampleconfig2/src/main/scala/SampleConfig2.scala
object SampleConfig2 extends Application {
  println("SampleConfig2: " + System.getProperty("config.file"))
}

まずは sammpleconfig1 から、引数指定で試す。

# sbt
> project sampleconfig1
> run -Dconfig.file=sampleconfig1.conf
SampleConfig1: null

取れない・・・
つぎに、javaOptions を指定。

 ./sampleconfig1/build.sbt
name := "sampleconfig1"

javaOptions in Runtime += "-Dconfig.file=sampleconfig1.conf"

取れない・・・
 ぐぐってみると、initialize を使ってる書き込みがあったので使用してみる。

 ./sampleconfig1/build.sbt
name := "sampleconfig1"

initialize ~= { _ =>
  if (new java.io.File("sampleconfig1.conf").exists)
    System.setProperty("config.file", "sampleconfig1.conf")
}

# sbt
> project sampleconfig1
> run
SampleConfig1: sampleconfig1.conf

おっ取れてる!
うまくいったということで暫くこれでやっていたが、設定ファイルが読めない時が・・・

実は initialize を使用した後に、sampleconfig2 のプロジェクトを追加していて

 ./sampleconfig2/build.sbt
name := "sampleconfig2"

initialize ~= { _ =>
  if (new java.io.File("sampleconfig2.conf").exists)
    System.setProperty("config.file", "sampleconfig2.conf")
}

# sbt
> project sampleconfig2
> run
SampleConfig2: sampleconfig2.conf

この時点では取れているのだが、再度 sampleconfig1 を実行すると

# sbt
> project sampleconfig1
> run
SampleConfig1: sampleconfig2.conf

と他のプロジェクトの設定が取れてしまっていた・・・

なので実行前に設定するタスクを入れることに。

 ./sampleconfig1/build.sbt
name := "sampleconfig1"

val setProperty = TaskKey[Unit]("setProperty") in Compile

setProperty := {
  if (new java.io.File("sampleconfig1.conf").exists)
    System.setProperty("config.file", "sampleconfig1.conf")
}

run in Compile <<= run in Compile dependsOn (setProperty)

 ./sampleconfig2/build.sbt
name := "sampleconfig2"

val setProperty = TaskKey[Unit]("setProperty") in Compile

setProperty := {
  if (new java.io.File("sampleconfig2.conf").exists)
    System.setProperty("config.file", "sampleconfig2.conf")
}

run in Compile <<= run in Compile dependsOn (setProperty)
# sbt
> project sampleconfig1
> run
SampleConfig1: sampleconfig1.conf
> project sampleconfig2
> run
SampleConfig2: sampleconfig2.conf

これでうまくいった

in Compile を in Runtime にするとうまくいかない・・・難しい・・・

2014年12月7日日曜日

Play Framework でデシリアライズ時に ClassNotFoundException

Play Framework ではセッションデータは Cookie に保存されるため、大きなデータは保存できない。
なので、Cookie には ID のみを保存して実際のデータはシリアリアズして DB に保存するように、
変更したところ嵌った・・・

Map や List など標準クラスでは問題ないのだが、独自クラスを保存すると ClassNotFoundException が発生する・・・

using(new ObjectInputStream(new ByteArrayInputStream(bytes))) { r =>
    r.readObject().asInstanceOf[HashMap[String, java.io.Serializable]]
  }
}
java.lang.ClassNotFoundException: com.example.MyClass
  at java.net.URLClassLoader$1.run(URLClassLoader.java:372) ~[na:1.8.0_20]
  at java.net.URLClassLoader$1.run(URLClassLoader.java:361) ~[na:1.8.0_20]
  at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_20]
  at java.net.URLClassLoader.findClass(URLClassLoader.java:360) ~[na:1.8.0_20]
  at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_20]
  at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_20]
  at java.lang.Class.forName0(Native Method) ~[na:1.8.0_20]
  at java.lang.Class.forName(Class.java:340) ~[na:1.8.0_20]
  at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:626) ~[na:1.8.0_20]
  at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1613) ~[na:1.8.0_20]
  at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518) ~[na:1.8.0_20]
  at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774) ~[na:1.8.0_20]
  at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351) ~[na:1.8.0_20]
  at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371) ~[na:1.8.0_20]
どうもクラスローダが違うようだ。

調べてみたところ、似たような質問が上がっていて、 解決策が出ていた。
class LocalInputStream(in: InputStream) extends ObjectInputStream(in) {
  override protected def resolveClass(desc: ObjectStreamClass): Class[_] =
    Class.forName(desc.getName, false, this.getClass.getClassLoader)
}
using(new LocalInputStream(new ByteArrayInputStream(bytes))) { r =>
    r.readObject().asInstanceOf[HashMap[String, java.io.Serializable]]
  }
}
これでデシリアライズできるようになった。

2012年12月27日木曜日

sbt で設定ファイルを本番用に切り替え

Scala の勉強をかなりさぼっていたわけで・・・
構文も大分忘れている状態なんですが、またちょっとはじめることに。
というか実践。

sbt を使ってちょっとしたバッチを開発時に jar に含める設定ファイル(ログレベル設定等)を、開発、ステージング、本番用に切り替えたくなった。そして嵌った・・・約 2 日・・・

とりあえず設定ファイルの構成は、Java の時にやっていた構成で、
+ src
  + main (開発)
    + scala
    + resources
      + logback.xml
      + ...
  + product (本番)
    + resources
      + logback.xml
  + staging (ステージング)
    + resources
      + logback.xml
の構成で、product/resources, staging/resources には上書きするもののみを置くこととする。

始める sbt のスコープ軸を見ると、タスク毎に設定が出来そう。ということで、最初のアプローチは、pack-staging, pack-product のタスクを生成して、タスクのスコープで unmanagedResourceDirectory を追加しようとした。が、そこから呼び出す copy-resources は copy-resources in Compile になってしまい挫折・・・これに 1 日以上費やしたわけだが・・・結局わからなかった・・・

で、maven の profile 的なことができないかと検索してみると参考になるサイトがあった。
ここを参考に、
case class Profile(name: String)

object Profile {
 val staging = Profile("staging")
 val product = Profile("product")

 implicit def toOption(profile: Profile): Option[Profile] = Option(profile)
}
として、
  unmanagedResourceDirectories in Compile <<= (sourceDirectory, selectedProfile, unmanagedResourceDirectories in Compile)
    ((dir, env, res) => env.map((dir / _.name / "resources")).toSeq ++ res),
  unmanagedResources in Compile~= (_.groupBy((_: java.io.File).getName).map(_._2.head).toSeq),
リソースディレクトリの設定時に振り分け。さらに、リソースファイル設定時に一意になるようグループ化しているようなしていないような・・・いまいち何をやっているかわからないが完成・・・ 

記号がさっぱりわからん・・・Perl とまでは言わんが・・・演算子でなくメソッドなので色々できるわけだが、C++ みたいに標準的な演算子のみオーバーライドできればいいのに・・・ ライブラリ毎に覚えるのは大変だ・・・ 標準的な意味合いの記号があるのだろうか。

 とりあえず sbt の API ドキュメントより、Setting[T] の <<= メソッドを見てみると trait DefinableSetting[S] に定義があり、
final def <<=(app: Initialize[T]): Setting[T]
なるほど、Initialize[T] を受け取る。 unmanagedResourceDirectories は SettingKey[Seq[File]] なので、右辺の
(sourceDirectory, selectedProfile, unmanagedResourceDirectories in Compile)
    ((dir, env, res) => env.map((dir / _.name / "resources")).toSeq ++ res)
は、Initialize[Seq[File]] を返すのであろう。

で、右辺部分だが、
(sourceDirectory, selectedProfile, unmanagedResourceDirectories in Compile)(なんかの処理)
つまり、
(タプル).apply(なんかの処理)
のように見えるが、タプルに対してそんなことできるのか・・・おそらくは暗黙の型変換・・・
というわけで、ソースコードを implisit で grep すると、Structure.scala で
implicit def t3ToApp3[A,B,C](t3: (Initialize[A], Initialize[B], Initialize[C]) ): Apply3[A,B,C] = new Apply3(t3)
というのを発見。さらに、Apply3 をみると、
def apply[T](z: (A,B,C) => T) = Project.app( k3(t3) )( hf3(z) )
となっている。さらに、object Project の API ドキュメントに、Init[Scope] に app の定義があり
def app[HL <: HList, T](inputs: KList[Initialize, HL])(f: (HL) ⇒ T): Initialize[T]
なので、Initialize[Seq[File]] が返りそう。なんか予想が多いが・・・

次に、なんかの処理部分は、Apply3 の apply の引き数なので、
(A,B,C) => T
最初のタプルの設定の値がそれぞれ渡り、T つまり、Seq[File] を返す関数を渡せばよさそう。
unmanagedResourceDirectories が SettingKey[Seq[File]] なので、res は Seq[File] とおもわれる。
そして、生成したパスと res を連結したものが返ると。

最終的に返った、Initialize[Seq[File]] で unmanagedResourceDirectories in Compile を設定しているようだ。
とりあえず、今回はここまで。
追うのだけでも大変・・・自分でカスタマイズ出来る気がしない・・・Scala は嫌いじゃないけど慣れないと、読むのも困難だな・・・

2011年11月9日水曜日

Scala の勉強(インストール)

前にちょっと調べた程度で終わっていた Scala を勉強しなおそうかと思う今日この頃。
はっきり言って全然覚えてないので初歩からスタートすることに。

まずは環境を作るところからか。(いつものように Cygwin 上でやってみる)
  1. http://www.scala-lang.org/ の Download Scala より Windows の最新版 scala-2.9.1.final.zip をダウンロード。
  2. インストール場所へ展開。
  3. 環境変数 SCALA_HOME と PATH を設定。
とりあえずこんな感じか。

emacs 用に scala-mode も付属している。さらに ENSIME なるものもあるようなので入れてみる。
  1. ${SCALA_HOME}/misc/scala-tool-support/emacs へ移動。
  2. make ELISP_COMMAND=emacs のパス
  3. https://github.com/aemoncannon/ensime/downloads より ENSIME をダウンロード。
  4. インストール場所へ展開。
  5. .emacs あたりに設定。(yasnippet も使用)
    (when (require 'scala-mode-auto nil t)
      (add-hook 'scala-mode-hook
                '(lambda ()
                   (yas/minor-mode-on)))
      (when (require 'ensime nil t)
     (add-hook 'scala-mode-hook 'ensime-scala-mode-hook))
      )
    
eclipse プラグインも入れておきたいがこれはそのうち。

参考にするサイトとして、
あたりを調べつつやっていこうかと。
は初心者向けらしい。英語なんで少しずつだがここからやってみるか。