2016年12月11日

RoslynのAanlyzerでC#のコードを分析するライブラリ作ってみたよ

これは、C# Advent Calendar 2016の12月11日(11日目)の記事なのだ。

昨日は、@tanaka_733さんのC# 7 on Linux
今日は、RoslynのAnalyzerを使ってみた話を書くよ。

☆☆☆コーディングスタイルを検証する仕組みへの不満☆☆☆

突然だけど、わたしのコーディングスタイルって、C#とJavaの中間みたいな書き方なんだよね。
例えばこんな感じ。

public class Project {
// フィールドだいたいは大文字ではじめるからC#風?
private string Field;
// メソッドはだいたい小文字ではじめるからJava風?
// 『{』は行末に書くからJava風?
public void doSomething() {
string f = this.Field;
}
}

で、VisualStudioってIntelliSenceが優秀だからか、
あまりコーディングスタイルを細かくチェックする仕組みがない気がする。
コーディングスタイルっていうか、静的解析?みたいな分類になるのかな?

いや、ほら、JavaのCheckstyleみたいに細かいチェックをするものって、
StyleCopとかFxCopとかだと思うんだけど、
これって、あんまり自由にカスタマイズする感じじゃない気がするの。

で、いろいろサイトめぐっていたら、
StyleCopがRoslyn使ってる〜とか言う話をとか見て、興味がわいてきた感じ。
ないなら、自分で作っちゃえば良いんだよね。

☆☆☆RoslynなAnalyzerの特徴☆☆☆

細かいこと書き出すと、ホントに書きたいことから遠ざかりそうだから、
基本的な作り方は、neueccさんの記事を参考にしてもらう感じで。
ここでは概要だけにとどめてさくさく具体的な話へいこ〜。

えっと、RoslynのDiagnosticAnalyzerってクラスを使うと、C#のコードの解析を簡単に作れる。
特に、次の点がとっても便利っ。もちろん、もっといっぱい便利な特徴はあるんだけど。
1.字句解析とか構文解析とか諸々省略して、SyntaxTreeがそろった状態から作業が始められる。
2.テンプレートプロジェクト?があるから、すぐ作るのをはじめられる。
3.作ったのは、NuGetで簡単に適用できる。
4.レポートした結果がVisualStudioと連動して見られる。

さぁ、じゃぁ、もう、プロジェクト作った前提で、
どのくらい簡単にかけるのか見てみよ〜。

☆☆☆具体的な例☆☆☆

Initializeメソッドで具体的な検証アクションを登録しておくんだけど、
解析するフェーズはいくつかあって、以下の4つのアクションから選んで解析コードを書く感じ。

public override void Initialize(AnalysisContext context) {
context.RegisterSyntaxTreeAction((c) => { });
context.RegisterSymbolAction((c) => { }, SymbolKind.Assembly);
context.RegisterSyntaxNodeAction((c) => { }, SyntaxKind.None);
context.RegisterSemanticModelAction((c) => { });
}

わたしの場合、使うのは、RegisterSyntaxNodeActionとRegisterSyntaxTreeActionかな。

RegisterSyntaxNodeActionは、名前の通り、読みたいNodeを指定して、そこから解析をしていく時に使うもの。
例えば、if文の書き方を確認したい!とかはこっちを使うといい感じ。

RegisterSyntaxTreeActionは、SyntaxTreeを自分で頭から読んでいくもの。
特に空白系を確認したい場合はこっちを使うといい感じ。

RegisterSyntaxNodeActionは、どうしてもNodeを基準にしないといけないから、Node無関係に空白を解析するのには向かないんだよね。

ちょっとサンプルを書くと、ブロックの手前にスペースが1個あるかどうかの確認はこんな感じ。

public override void Initialize(AnalysisContext context) {
context.RegisterSyntaxNodeAction((c) => { analyze(c); }, SyntaxKind.Block);
}
private static void analyze(SyntaxNodeAnalysisContext context) {
SyntaxNode node = context.Node;
SyntaxToken firstToken = node.GetFirstToken();
SyntaxToken previousToken = firstToken.GetPreviousToken();
SyntaxTriviaList trivias = previousToken.TrailingTrivia;
// 存在しない場合も0になる。
int triviasLength = trivias.FullSpan.End - trivias.FullSpan.Start;
if((triviasLength == 1) && trivias.First().IsKind(SyntaxKind.WhitespaceTrivia) &&
(previousToken.Parent.SyntaxTree.GetText().GetSubText(trivias.FullSpan).ToString() == " ")) {
// ブロックの直前にスペースが1個ある。
} else {
// Ruleはテンプレ生成したときにあるやつなので説明省略で。
Diagnostic diagnostic = Diagnostic.Create(Rule, node.GetLocation(), "メッセージ");
context.ReportDiagnostic(diagnostic);
}
}

う〜ん、簡単だね!
いや、まぁ、解析のロジック自体は、もちろん、みんなガンバレ!って話になるわけだけど、
構成は……簡単でしょ?

もう一種類も。
これは、行末に空白があったらダメだよっていうやつ。

public override void Initialize(AnalysisContext context) {
context.RegisterSyntaxTreeAction((c) => { analyze(c); });
}
private static void analyze(SyntaxTreeAnalysisContext context) {
SyntaxNode root = context.Tree.GetCompilationUnitRoot(context.CancellationToken);
IEnumerable trivias = root.DescendantTrivia();
{
SyntaxTrivia previousTrivia = default(SyntaxTrivia);
foreach(SyntaxTrivia trivia in trivias) {
if(trivia.IsKind(SyntaxKind.EndOfLineTrivia) && previousTrivia.IsKind(SyntaxKind.WhitespaceTrivia)) {
if(trivia.FullSpan.Start == previousTrivia.FullSpan.End) {
Diagnostic diagnostic = Diagnostic.Create(Rule, trivia.GetLocation(), "メッセージ");
context.ReportDiagnostic(diagnostic);
}
}
previousTrivia = trivia;
}
{
SyntaxTrivia trivia = trivias.LastOrDefault();
if(trivia.IsKind(SyntaxKind.WhitespaceTrivia) && (trivia.FullSpan.End == root.FullSpan.End)) {
Diagnostic diagnostic = Diagnostic.Create(Rule, trivia.GetLocation(), "メッセージ");
context.ReportDiagnostic(diagnostic);
}
}
}
}

いける!いける!
StyleCopとか、何を検証しているのかわからないけど、結構カオスなコード書いてあるように見えるんだけど、
ちょっとしたの書くなら、検証する内容がはっきりイメージできれば、結構、簡単にかけるよ!
……はっきり……イメージできれば……ね……。

☆☆☆作ってみたライブラリ☆☆☆

ということで、作ったライブラリは、NuGetにおいてあるから、良ければ試してみてね。
『ECheckStyle』で登録してあるから検索すれば出てくると思うよ。
NuGetパッケージのサイト
プロジェクトサイト
折角だから、今日アップデートしておいたっ。

☆☆☆注意点☆☆☆

作る上での注意点とかを簡単にあげておくと、
1.解析でできるSyntaxTreeが一意ではない。
  解析ロジック考える時に、いろいろなサイトでSyntax Visualizer見ると良いよって書いてあるんだけど、
  これ、実は、エディタ開いた状態で行われる解析と、開いてない状態で行われる解析で、作られるSyntaxTreeが違うの。
  あ、エディタ上の色分けもSyntax Visualizerのとは違うッぽいからこれも参考にするとダメみたいだよ。
  理由は知らないけど、そのせいでだいぶ苦労したっ。特にコメント回りっ!
2.CodeFixを作る難易度が高い。
  Roslynの良いところは、実は、解析の実装が簡単なだけじゃなくて、CodeFixも準備できることにもあると思うの。
  おかしいところをまとめてぽちぽち直せるのは凄いと思う。でも、これ、作るの結構辛いっ。
  解析のロジックを考えるのは、まぁ、変な構文が入ってきても、スルーするだけだと思うんだけど、
  CodeFixはそれもまとめて直してあげないといけないからね。CodeFix作り込むのはかなり難易度高いね。
3.空白の作成で改行あたりが消える。
  CodeFixと言えば、空白の調整とか、自前でやろうとすると、まぁ、空白を作成することになるんだけど、
  SyntaxFactory.ParseLeadingTriviaとSyntaxFactory.ParseTrailingTriviaってあって、
  後者は改行を作れないっぽいって言うか、作ろうとすると静かに消えるから注意なのだ。
  どこにもそのあたりの説明書いてない感じで微妙なんだけど。
4.NuGetで入れたAnalyzerの削除で依存ライブラリが消える。
  StyleCopもそうなんだけど、設定とかの読み込みとかに、Json.NETかな、使っているケースが多いみたい。
  EcheckStyleもそうなんだけどね。
  で、これ、NuGetで入れるときは自動で依存しているライブラリを入れるScriptが動くみたいなんだけど、
  削除する時には、削除するScript勝手に動くみたいで、どうも依存関係が確認されないで消されるみたい。
  うーんと、つまり、StyleCop入れて、ECheckStyle入れて、StyleCop消すと、
  Json.NETのライブラリが消えて、ECheckStyleが動かなくなる。っていうこと。
  この辺は、どうするのがいいのかな……。
5.ファイルの除外について
  これは、ECheckStyleだと設定ファイルでサポートしているから、別に注意って言うわけじゃないんだけど、
  この辺で解析対象の除外について議論されているみたいなんだよね。
  どうなるのかわからないけど、
  解析するライブラリは複数あるわけで、それぞれ、かけたい範囲かけたくない範囲っていうのはあるはずで、
  Roslyn側で全体まとめてかけるかけないって言うのは、ちょっと違うんじゃないかなぁ?とか思ったりしている。
  まぁ、ここでは、ライブラリ作る側でその辺も考えようねって言うだけなんだけどね。

☆☆☆まとめ☆☆☆

いろいろ微妙な点も書いたけど、やっぱり便利だよ。ホントに。
ただ、対象がC#とVBだけなのが淋しいかな。

アプリつくってたらさ、
Webアプリケーションだったら、HTMLとかTypeScriptも書くし、
クライアントだったら、XAMLとかも書くわけじゃない?

この辺もサポートしてくれたら、とっても良いと思うんだけどなぁ(゜▽、゜

posted by すふぃあ at 12:50| Comment(0) | TrackBack(0) | 雁字

2015年12月02日

GlassFish上で動くWebアプリをGradleとNetBeansで開発するための準備

これは、Java EE Advent Calendar 2015 - Qiitaの2日目(12月2日)の記事だよ。

昨日は、キクタロー(@kikutaro_)さんJava EEでMicroserviceを実現するKumuluzEE #javaee #Qiita ね。
明日は、Kohei Nozaki(@lbtc_xxx)さんが書くみたい。

今日は、Gradleを使って、
GlassFish上で動くWebアプリをNetBeansで開発するときのプロジェクトを作ってみようと思いま〜す。
Webアプリは、CDIとJPA使う想定で、ユニットテストも出来るようにしたいかも。

でも、中身はなしでっ。
開発する環境を用意するのを中心にやることにするのだ。
あと、ユニットテストでCDIとかJPAを使う具体的な方法は別の記事を参照ってことで、

環境の前提はこんな感じでいくね。
1.Windows 8.1(まだ10にしてない)
2.JDK 1.8(手元で使ってるのはupdate66)
3.NetBeans 8.1(JavaEE開発用)
4.GlassFish 4.1(ドメインとデータソースの設定済み)(GlassFish 4.1.1でもほとんどおなじ)

流れは、こんな感じで。
1.GlassFishのサーバーとドメインをNetBeansに設定
2.NetBeansのGradle Supportモジュールをインストール
3.Gradleのプロジェクトを作ってビルドファイルをかきかきφ(。。
4.簡単な拡張とか

と言うことで。

☆☆☆1.GlassFishのサーバーとドメインをNetBeansに設定☆☆☆

ドメインの作成、DBのライブラリの設定、データソースの設定は終わってる前提だから、
NetBeansに登録して使えるようにするところから。

サーバーの追加でGlassFishを選んで……、
ここではGlassFish 4.1って名前にするね。この名前は後で使うよ。
javaee_2015_01.png
javaee_2015_02.png

GlassFishのインストール先と、ドメインを指定して完了〜。
javaee_2015_03.png
javaee_2015_04.png

こんな感じになってるはず。
javaee_2015_05.png

☆☆☆2.NetBeansのGradle Supportモジュールをインストール☆☆☆

いれるのは、これね、これ。
https://github.com/kelemen/netbeans-gradle-project

NetBeansのメニューバーから、ツール→プラグインと選んで、
プラグインの検索とインストール、更新が出来るダイアログを開くよ。
javaee_2015_11.png

Gradleって検索すると『Gradle Support』って言うが見つかるからそれ。
もう1個のはよく知らない。とりあえず今回はいらないのでどっちでも。
何が出来るようになるんだろうね。

インストールしようとすると
『GroovyおよびGrails』も一緒に入れないとなのでまとめて入れちゃう。
GradleがGroovyで動くからだね。
javaee_2015_12.png

NetBeansの再起動を要求されるから再起動すればGradleを使う準備は完了!

☆☆☆3.Gradleのプロジェクトを作ってビルドファイルをかきかきφ(。。☆☆☆

さぁ、新規プロジェクトを作るよ。
今回は、Single Gradle Projectで。
複雑なプロジェクト構成を組む場合は、Gradle Root Projectね。
javaee_2015_21.png

適当な名前でプロジェクトを作成〜。
javaee_2015_22.png

Java用のbuild.gradleファイルが出来ているはず。
javaee_2015_23.png

でもこのファイルは、Webアプリ用じゃないから、
ばっさりと書き換えちゃう。こんな感じに。

// GradleでGlassFishにアクセスするためのプラグイン用意してみたからそれ使うのだ。
// http://memory.empressia.jp/article/167469186.html
buildscript {
repositories {
maven { url "http://www.empressia.jp/maven/"; }
}
dependencies {
classpath group: "jp.empressia", name: "jp.empressia.gradle.plugin.glassfish", version: "0.2.2";
}
}

// warをパッケージする。
apply plugin: "war";
// GlassFish用のタスクを追加する。
apply plugin: "glassfish";
// NetBeansのGlassFish設定を読み込める様にする。
apply plugin: "glassfish.netbeans";

// Java8で。
sourceCompatibility = 1.8;
targetCompatibility = 1.8;

// Mavenな感じと同じようにclassesの中にリソースが配置されるようにする。
sourceSets {
main.output.resourcesDir = main.output.classesDir;
test.output.resourcesDir = test.output.classesDir;
}

// アプリの依存性解決用のリポジトリにMavenセントラル使うよ。
repositories {
mavenCentral();
}

// 依存関係。必要なの追加で。
dependencies {
providedCompile(group:"javax", name:"javaee-web-api", version:"7.0");
}

// NetBeans GlassFishプラグインの設定。NetBeansに登録したGlassFishサーバーを使う設定をしているよ。
// 記事の前半で設定したサーバーの名前を設定するのだ。
// glassfish.netbeans.serverName = "GlassFish Server 4.1";って書いてもokだからね。
glassfish {
netbeans {
serverName = "GlassFish Server 4.1";
}
}

// JavaはUTF-8でいいよね。
tasks.withType(JavaCompile) {
options.encoding = "UTF-8";
}

// リソースは、classes側に移動するようにしたからデプロイでリソースを個別に動かす必要はないからスルーする設定。
tasks.deployResources {
exclude "**/*"
}

これで基本的な設定は終わり〜。

開始ボタン、ビルドボタン、とか普通に動くはず。
ブラウザは自動では開かないから、自分で開くっ。一度開けばいいし、良いよね。

あと、ログ見たいときは、サービスのサーバーからドメイン・サーバー・ログの表示を選べばでてくるよ。
これも一度だけ選べばずっと出てるかな。

あ、メインメニューのツール→オプションの設定に、Compile on saveがあるから、使ってみるといいかも。
javaee_2015_91.png

プロジェクトのプロパティ設定でApply Code Changesを『${project}:deploy』がおすすめかな。
javaee_2015_92.png

☆☆☆4.簡単な拡張とか☆☆☆

☆ユニットテストでCDIとJPAを使う設定

CDIを実際に動かすには、コンテナ作るか用意しないとだと思うけど、
基本的な設定はこんな感じ?

dependenies {
// testRuntimeでも良いのもあるかも。
testCompile(group:"junit", name:"junit", version:"4.12");
testCompile(group:"org.hamcrest", name:"hamcrest-library", version:"1.3");
testCompile(group:"org.jboss.weld.se", name:"weld-se", version:"2.2.2.Final");
testCompile(group:"org.eclipse.persistence", name:"eclipselink", version:"2.5.2-RC1");
testCompile(group:"javax.transaction", name:"javax.transaction-api", version:"1.2");
}

tasks.test {
doFirst {
// test側のbeans.xmlに書かれた内容をユニットテストの時に読み込まれるようにbeans.xmlをmainのclassesにコピーする。
// 要らなければスルーで。
copy { from "${sourceSets.test.resources.srcDirs.find({true})}/META-INF/beans.xml" into "${sourceSets.main.output.resourcesDir}/META-INF/" }
// コネクションはテスト側にあっても取れるけど、Entityはmain側にないと読み込めないから、persistence.xmlをmainのclassesにコピーする。
// あと、persistence.xmlの競合を避けるために、テスト側からpersistence.xmlを削除する。
copy { from "${sourceSets.test.resources.srcDirs.find({true})}/META-INF/persistence.xml" into "${sourceSets.main.output.resourcesDir}/META-INF/" }
delete "${sourceSets.test.output.resourcesDir}/META-INF/persistence.xml"
}
// JPAをJavaAgentで使う〜。
jvmArgs "-javaagent:${configurations.testCompile.files.find({ file -> file.name.equals('eclipselink-2.5.2-RC1.jar') })}"
}

// ビルドする場合は、testをはじめる前にパッケージ(とデプロイ)をしないと、テスト直前でリソースを書き換えているのが問題になっちゃうから、依存関係を追加しておく。
if(this.gradle.startParameter.taskNames.contains("build")) {
// tasks.test.dependsOn(tasks.assemble);っていう方法も。
tasks.test.dependsOn(tasks.war);
tasks.test.dependsOn(tasks.deploy);
}

余談だけど、CDIってなんかパッケージの外から設定上書きできないのかなぁ?
Alternativeとか、外から切り替えたいんだけど、できないよね。
そのうちできるようになるのかなぁ……?それとも、そういうものじゃないのかな……?

☆LESSを使う設定

LESS便利だよね。と言うか、CSSが不便すぎて……。
パスを直接ビルドファイルに書くのいやだし、NetBeansからパスを読み込むのを用意してみたよ。
ビルドスクリプトに追加で。

buildscript {
repositories {
maven { url "http://www.empressia.jp/maven/"; }
}
dependencies {
classpath group: "jp.empressia", name: "jp.empressia.gradle.plugin.netbeans.configuration", version: "0.0.6";
}
}
apply plugin: "netbeans.configuration";

// タスクを追加するよ。
// タスクの定義で『<<』で作ってる例あるけど、タスクの定義に『<<』は追加したらだめだよね。
// タスクの定義ちゃんとしてインクリメンタルビルドしないとね。
task processLess(type: Copy) {
from webAppDir
into webAppDir
include "**/*.less"
eachFile { file ->
exec {
commandLine "${netbeans.configuration.lessPath}", "--compress", "${webAppDir}/${file.path}", "${webAppDir}/${file.path.substring(0, file.path.length() - '.less'.length())}.min.css"
}
file.exclude();
}
}

// タスクの依存関係はこんな感じ?
// コンパイル系の作業の中にLESSコンパイルもいれるよ。
tasks.classes.dependsOn(tasks.processLess);

他にも、netbeans.configuration.***Pathっていう形でいろいろ取れるようにしてあるよ。

  • sassPath
  • gruntPath
  • gulpPath
  • karmaPath
  • expressPath
  • nodePath
  • npmPath

バージョンとか指定したい場合は

netbeans {
configuration {
version = "8.1";
}
}

とか

netbeans {
configuration {
appDataDirectoryPath = "C:/Users/[UserName]/AppData/Roaming/NetBeans";
version = "8.1";
}
}

とか

netbeans {
configuration {
directoryPath = "C:/Users/[UserName]/AppData/Roaming/NetBeans/8.1/config";
}
}

とかで指定できるよ。

☆JavadocとMavenでのアップロードは……。

まぁ、いいよね。
今回は、スルーで。

☆最終的なbuild.gradle

こんな感じになるかな?

buildscript {
repositories {
maven { url "http://www.empressia.jp/maven/"; }
}
dependencies {
classpath group: "jp.empressia", name: "jp.empressia.gradle.plugin.glassfish", version: "0.2.2";
classpath group: "jp.empressia", name: "jp.empressia.gradle.plugin.netbeans.configuration", version: "0.0.6";
}
}

apply plugin: "war";
apply plugin: "glassfish";
apply plugin: "glassfish.netbeans";
apply plugin: "netbeans.configuration";

sourceCompatibility = 1.8;
targetCompatibility = 1.8;

sourceSets {
main.output.resourcesDir = main.output.classesDir;
test.output.resourcesDir = test.output.classesDir;
}

repositories {
mavenCentral();
}

dependencies {
providedCompile(group:"javax", name:"javaee-web-api", version:"7.0");
testCompile(group:"junit", name:"junit", version:"4.12");
testCompile(group:"org.hamcrest", name:"hamcrest-library", version:"1.3");
testCompile(group:"org.jboss.weld.se", name:"weld-se", version:"2.2.2.Final");
testCompile(group:"org.eclipse.persistence", name:"eclipselink", version:"2.5.2-RC1");
testCompile(group:"javax.transaction", name:"javax.transaction-api", version:"1.2");
}

glassfish {
netbeans {
serverName = "GlassFish Server 4.1";
}
}

tasks.withType(JavaCompile) {
options.encoding = "UTF-8";
}

task processLess(type: Copy) {
from webAppDir
into webAppDir
include "**/*.less"
eachFile { file ->
exec {
commandLine "${netbeans.configuration.lessPath}", "--compress", "${webAppDir}/${file.path}", "${webAppDir}/${file.path.substring(0, file.path.length() - '.less'.length())}.min.css"
}
file.exclude();
}
}
tasks.classes.dependsOn(tasks.processLess);

tasks.deployResources {
exclude "**/*"
}

tasks.test {
doFirst {
copy { from "${sourceSets.test.resources.srcDirs.find({true})}/META-INF/beans.xml" into "${sourceSets.main.output.resourcesDir}/META-INF/" }
copy { from "${sourceSets.test.resources.srcDirs.find({true})}/META-INF/persistence.xml" into "${sourceSets.main.output.resourcesDir}/META-INF/" }
delete "${sourceSets.test.output.resourcesDir}/META-INF/persistence.xml"
}
jvmArgs "-javaagent:${configurations.testCompile.files.find({ file -> file.name.equals('eclipselink-2.5.2-RC1.jar') })}"
}

if(this.gradle.startParameter.taskNames.contains("build")) {
tasks.test.dependsOn(tasks.war);
tasks.test.dependsOn(tasks.deploy);
}

これで、割と自然にGradle+NetBeans+GlassFishでWebアプリ作れるんじゃないかなぁって。

そうそう、NetBeansのGradleプラグイン作ってる人に聞いたんだけど、
非同期構成意識して作っているからプロジェクトの構成が古いのと違っていて、
古いNetBeansのNode追加するプラグインがGradleのプロジェクトには追加されないんだよね(Ver.1.3.7.2)。

対応してもらったバージョン作ってもらったから、良ければ直接ダウンロードして使ってみてね。
https://github.com/kelemen/netbeans-gradle-project
NetBeans81ブランチで試してたんだけど、今は、masterにも取り込まれたみたいだよ。

かなり、メールの応答が速かったから、とっても助かったのだ(゜▽、゜感謝だね

posted by すふぃあ at 09:07| Comment(0) | TrackBack(0) | 雁字

2015年11月09日

GradleからGlassFishにデプロイするためのプラグインをつくってみたよ

NetBeansつかってWebサイトつくってたんだけど、
Mavenでビルドしてると遅いよね!

なんか、MavenじゃなくてGradleっていうのを使うと、
インクリメンタルビルドっていうのができて速くなるみたい。

と言うわけで、Webサイト作るのに使ってみたんだけど、
GlassFishへのデプロイはデフォルトではサポートしていないみたい。
で、ないならプラグインで作れば良いよね!ってことでつくってみたよ。

☆☆☆準備編☆☆☆

build.gradleに下のように書けばok。

buildscript {
repositories {
maven { url "http://www.empressia.jp/maven/" }
}
dependencies {
classpath "jp.empressia:jp.empressia.gradle.plugin.glassfish:+";
}
}
apply plugin: "glassfish";

NetBeans使っている場合は、さらに下の1行を追加すると便利になるよ。

apply plugin: "glassfish.netbeans";

☆☆☆定義編☆☆☆

プラグインを適用すると、glassfishの設定をできるようになるよ。
netbeansの部分はglassfish.netbeansを適用したときだけね。
長いけど、別に、全部設定しなくても大丈夫。

glassfish {
// GlassFishの場所
homeDirectoryPath;
// GlassFishの起動に使用するJDKHomeの場所
JDKHomeDirectoryPath;
// ドメインのディレクトリ
domainsDirectoryPath;
// ドメイン名
domainName;
// 管理ポート
adminPort;
// deployでサーバーの開始を試みる時にデバッグにするかどうか
deployWithDebug;
// ビルドの時にデプロイするかどうか(デフォルトtrue)
deployOnBuild;
netbeans {
// NetBeansのサーバー設定の名前
serverName;
// NetBeansのJavaプラットフォーム設定の名前
JDKName;
// NetBeansのサーバー設定ファイル(/config/GlassFishEE6/Instances/.nbattrs)
configFilePath;
// NetBeansのVersion
version;
}
deploy {
// デプロイ用のクラスファイルの場所
String classesDirectoryPath;
// デプロイ用のリソースファイルの場所
String resourcesDirectoryPath;
// デプロイ用のWebPage系のファイルの場所
String webappDirectoryPath;
// デプロイ用のディレクトリ名
String directoryName;
// デプロイ用のコンテキストルート
String contextRoot;
}
}

☆☆☆設定サンプル編☆☆☆

netbeansを適用していないときは、GlassFishのホームディレクトリを設定してね。
gradle,propertiesあたりから設定しても良いかも。

glassfish {
homeDirectoryPath = "C:\\Program Files\\glassfish-4.1\\glassfish";
}

JDKは放っておくとGradleのを使うから、指定したい場合は、↓の感じ。

glassfish {
homeDirectoryPath = "C:\\Program Files\\glassfish-4.1\\glassfish";
JDKHomeDirectoryPath = "C:\\Program Files\\Java\\jdk1.8.*_**";
}

でも、やっぱり設定めんどくさいよね!
と言うわけで、netbeansを適用しておけば、NetBeansの設定から勝手にGlassFish探すようにしてあるよ。
このときは何もしなくても全自動〜。

サーバーの設定を複数しているなら、設定したサーバーの名前を付ければ大丈夫!

glassfish {
netbeans {
serverName = "GlassFish Server 4.1";
}
}

簡単だね!
あ、古いNetBeansに設定がある場合は、versionを指定してね。

glassfish {
netbeans {
serverName = "GlassFish Server 4.1";
version = "8.0.1";
}
}

サーバーのJDK設定がデフォルトだと、Graldeのを使おうとするから注意してね。
こんな風にも設定できるけど、サーバーの設定をちゃんとした方が良いかな。

glassfish {
netbeans {
serverName = "GlassFish Server 4.1";
JDKName = "JDK 1.8";
}
}

☆☆☆実行編☆☆☆

glassfishプラグインを適用すると、5つのタスクが追加されるよ。

  • deployClasses
  • deployResources
  • deployWebapp
  • deployLibraries
  • deploy

他にも、いくつかのタスクが書き換わるよ。
clean、run、debugとか。

これで、↓の感じで行けるはず。

// サーバー停止(デプロイディレクトリ削除)
gradle clean
// サーバー起動
gradle run
// サーバーdebug起動
gradle debug
// デプロイ
gradle deploy

タスクの関係はだいたいこんな感じ。
EmpressiaGradleGlassFishPlugin_Tasks.png

NetBeans使ってるなら、ビルドボタン、クリーンビルドボタン、実行、デバッグボタンがあるから簡単に連動すると思うよ。
ただ、サーバーのログが見たかったら、サービスのサーバーを選んで、ドメイン・サーバー・ログの表示とかやってね。
あ、NetBeans閉じてもサーバー止まらないかも……まぁ、いいよね(゜▽、゜

☆☆☆注意編☆☆☆

なるべく、無駄にdeployコマンドが発行されないようにしているんだけど、
そのせいで、deployコマンド自体で失敗すると、次からデプロイされなくなる……かも?
そんなときは、コンパイルが動くようにプログラムを一回書き換えるとか、cleanするとかがいいかなぁ。

あと、MacとLinuxでの確認はしてないー。

Version 0.2.0は、Javaのソースがないと、
webappにWEB-INFディレクトリが作られなくてデプロイ失敗することがあるんだけど、
そういうことあんまり無いだろうし、すぐ直す予定〜。

posted by すふぃあ at 22:16| Comment(0) | TrackBack(0) | 雁字

2014年12月21日

Maven経由のユニットテストでCDIを簡単に使う

前の二つの記事でCDIとJPAをユニットテストで使う方法について書いてきたから詳細なプログラムの内容はそっちに任せるとして、
まとめたパッケージを簡単に使う方法をまとめると、pom.xmlをこんな感じに。

	<repositories>
……
<repository>
<id>Empressia</id>
<url>http://www.empressia.jp/maven/</url>
</repository>
</repositories>

<dependencies>
……
<dependency>
<groupId>jp.empressia</groupId>
<artifactId>jp.empressia.test</artifactId>
<version>1.0.1-1</version>
<scope>test</scope>
</dependency>
</dependencies>

これでRunner指定すればCDIが使えるよ。さらにbeans.xmlをこんな感じに。

	<beans>
<alternatives>
<stereotype>jp.empressia.test.UnitTest</stereotype>
</alternatives>
<intercepters>
<class>jp.empressia.test.TransactionInterceptor</class>
</intercepters>
</beans>

これでJPAとJTAが使える。一部だけど。

で、Maven使ってるときに気をつけないといけないことがあるんだよね。

beans.xmlにalternatives書くのは、main側じゃないとダメって言うか、main側のパスに入ってないとだめなのね。
でも、main側にテストの要素書いちゃだめなわけで……えーっと、
mainのclassesとtest-classesで別のbeans.xm管理になるって言う感じ?
実際にalternativeしたいのは、main側。設定書いておきたいのはtest側。
有効にするためには、test側にもbeans.xml必要。
うーん……ここ、ポイントだからしっかり整理〜。

  • JavaEEの環境だと、beans.xml無くてもCDIは有効になる。
  • JavaSEの環境だとbeans.xmlが必要。
  • Mavenだとクラスファイルの格納場所が、classesとtest-classesに分かれてる。
  • ユニットテストでalternativeの書いたbeans.xmlを置かないといけないのはclasses側(試して見た感じ)。
  • ユニットテストでCDIを有効にするためにbeans.xml置くのはtest-classes側(中身は何でもよさそう)(試して見た感じ)。
  • テスト固有のbeans.xmlの内容はmain側じゃなくてtest側に書いておきたい。

これを解決しようとすると、
testするときには、テスト側のbeans.xmlをmain側において実行して、
パッケージするときは、メイン側のbeans.xmlで上書きするって手順にしないとダメそう。

あと、persistence.xmlもmain側だけじゃなくてtest側にもおいちゃうと、ユニットテストの時に、EclipseLinkを使った時点で両方読まれちゃう。
test用にRESOURCE_LOCALで細かい接続設定書いたpersistence.xmlを置くだろうから、
ユニットテストの時はmainの方を消すのを忘れずに。packagingの時にこれも気をつけないとね。

具体的にはMavenのビルドをこんな感じに。
良い悪いいろんな意見はあるだろうけど、まぁ、手軽にやるなら良いかなって。

	<build>
<plugins>
……
<!-- process-test-resources:beans.xmlをmainのclassesにコピーする。test側のbeans.xmlに書かれた内容をユニットテストの時に読み込まれるようにする。 -->
<plugin>
<groupId>com.github.goldin</groupId>
<artifactId>copy-maven-plugin</artifactId>
<version>0.2.5</version>
<executions>
<execution>
<id>copy test beans.xml to main for unit test.</id>
<phase>process-test-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<resources>
<resource>
<targetPath>${project.build.directory}/classes/META-INF</targetPath>
<file>src/test/resources/META-INF/beans.xml</file>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- process-test-resources:persistence.xmlをmainのclassesから削除する。mainのpersistence.xmlがユニットテストの時に読み込まれるのを防止する。 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>2.6.1</version>
<executions>
<execution>
<id>remove persistence.xml from classes for avoiding duplicated persistence.xml at unit test.</id>
<phase>process-test-resources</phase>
<goals>
<goal>clean</goal>
</goals>
<configuration>
<excludeDefaultDirectories>true</excludeDefaultDirectories>
<filesets>
<fileset>
<directory>${project.build.directory}/classes/META-INF</directory>
<includes>
<include>persistence.xml</include>
</includes>
</fileset>
</filesets>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<!-- テスト用にtargetが書き換えているから、src側からパッケージする。 -->
<webResources>
<resource>
<directory>src/main/resources/META-INF</directory>
<targetPath>WEB-INF/classes/META-INF</targetPath>
<includes>
<include>beans.xml</include>
<include>persistence.xml</include>
</includes>
</resource>
<resource>
<directory>target/classes/META-INF</directory>
<excludes>
<!-- beans.xmlはユニットテストの時にmain側にコピーしてるから取り込まないようにする。 -->
<exclude>beans.xml</exclude>
<!-- persistence.xmlはユニットテストの時に消えるはずだけどincludeと形をあわせておく。 -->
<exclude>persistence.xml</exclude>
</excludes>
</resource>
</webResources>
</configuration>
</plugin>
</plugins>
</build>

ここまでで、@Injectが使いたい放題でJPAのPersistenceUnit1個に対応して、Transactionalにも簡単にだけど対応した状態になっているはず!(゜▽、゜
気に入らなければCDIの力で差し替えも簡単だから、是非、お試しあれ?(゜▽、゜

posted by すふぃあ at 21:00| Comment(1) | TrackBack(0) | 雁字

JPAとJTAをJavaSE環境で使えるようにする

こっちの記事に書いたけど、CDIが出来ちゃえば、後はもう簡単だよね。
CDI使ってるってことは、リソース系は、全部@Produces作ってるはずだから、
それを上書きしちゃえばok!

JPAは普通……かどうか分からないけど、CDI使っているなら、
persistence.xmlに定義しているUnitごとに、Producer作るよね。
たとえば、persistence.xmlにSamplePUが定義されてるとするとこんな感じ。

	@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})
public @interface Sample {
}
	@RequestScoped
public class SampleProducer {

@PersistenceContext(unitName="SamplePU")
private EntityManager em;

@Produces @Sample
public EntityManager getEntityManager() {
return this.em;
}

}

そしたら、テスト側はこんな感じのを用意すれば完成!テスト用のbeans.xmlに追加するの忘れないでね。

	package jp.empressia.test;
// (略)
@Stereotype
@Alternative
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface UnitTest {
}
	@RequestScoped
public class LocalSampleProducer {

private static final EntityManagerFactory emf;

static {
emf = Persistence.createEntityManagerFactory("SamplePU");
}

private EntityManager em;

public LocalSampleProducer() {
this.em = emf.createEntityManager();
}

@Produces @Sample @UnitTest
public EntityManager getEntityManager() {
return this.em;
}

}
	<beans>
<alternatives>
<stereotype>jp.empressia.test.UnitTest</stereotype>
</alternatives>
</beans>

EntityManagerFactoryとEntityManagerの扱い方はこれでいいよね、たぶん。

それから、JTAかな。
CDIとJTAなら、javax.transaction.Transactional使っているという前提で、Interceptorを作っちゃう方向でいいよね。
あと、ユニットテストだし、接続先は共通で1個だけってことにするよ。
まず、ProducerにTransactionを提供する部分を追加するのだ。

	@RequestScoped
public class LocalSampleProducer {
// (略)
@Produces
public EntityTransaction getTransaction() {
return this.em.getTransaction();
}
}

そうしたら、こんな感じのInterceptorでも十分かなっと。
別にもっと作り込んでも良いんだけど、わたしはそんな高度な使い方しないしね。

	package jp.empressia.test;
// (略)
@Transactional
@Interceptor
public class TransactionInterceptor {

@Inject
private EntityTransaction trans;

@AroundInvoke
public Object doTransaction(InvocationContext ic) throws Exception {
Object result = null;
try {
this.trans.begin();
result = ic.proceed();
if(this.trans.isActive()) {
if(this.trans.getRollbackOnly() == false) {
this.trans.commit();
} else {
this.trans.rollback();
}
} else {
String message = "なにかメッセージ";
logger(this).warning(message);
}
} catch(Exception proceededEx) {
try {
if(trans.isActive()) {
trans.rollback();
}
} catch(Exception rollbackEx) {
String message = "なにかメッセージ";
logger(this).severe(message);
}
throw proceededEx;
}
return result;
}
}

で、これを、やっぱりbeans.xmlにっと。

	<beans>
<intercepters>
<class>jp.empressia.test.TransactionInterceptor</class>
</intercepters>
</beans>

これで、@TransactionalもEntityManagerのInjectも動くね。

ちなみに、前の記事のパッケージには、接続先のUnitが1個前提の実装が入ってるよ。
beans.xmlをこんな感じにすれば有効になるようになってる。

	<beans>
<alternatives>
<stereotype>jp.empressia.test.UnitTest</stereotype>
</alternatives>
<intercepters>
<class>jp.empressia.test.TransactionInterceptor</class>
</intercepters>
</beans>

あとはLocalなEntityManagerなProducerをこんな感じに書けばok。
『@Sample』みたいな指定が元々なければ、子のクラスは用意する必要も無い……はず、たぶん。
@Inject EntityManagerが動作してるからね。

	@RequestScoped
public class LocalSampleProducer {

@Inject
private EntityManager em;

@Produces @Sample @UnitTest
public EntityManager getEntityManager() {
return this.em;
}

}

あ、もしかしたら、EclipseLinkをjavaagentに指定して起動すれば、もっと簡単かもしれないけど、その辺は試してないよ。

posted by すふぃあ at 21:00| Comment(2) | TrackBack(0) | 雁字

JavaEE用のスコープをJavaSE環境で使えるようにする

CDIをJavaSEで使えるようにするのはそんな難しいことじゃないよね。
問題なのは、JavaEEのスコープをJavaSEで使うこと。
RequestScopedとか、ConversationScopedとか、SessionScopedとか。

他のカスタムされたスコープは、まぁ、必要な人が対応すれば良いよねってことで。
基本は、同じだし。

まずは、コンテナとしての機能を追加しないとダメだから、JUnitのRunnerを拡張してWeldの機能を有効にするところから。
ホントは、別の方法とかあるのかもしれないけど、JUnitに詳しくなくて……動けば良いよね!

こんな感じ。

	public class JUnit4RunnerWithWeld extends BlockJUnit4ClassRunner {

/** Weld */
private static final Weld Weld;
/** Weld Container. */
private static final WeldContainer Container;

static {
Weld = new Weld();
Container = Weld.initialize();
}

/** 対象となるテストクラス。 */
private final Class<?> TargetTestClass;

public JUnit4RunnerWithWeld(final Class<Object> TargetTestClass) throws InitializationError {
super(TargetTestClass);
this.TargetTestClass = TargetTestClass;
}

@Override
protected Object createTest() throws Exception {
Object test = JUnit4RunnerWithWeld.Container.instance().select(this.TargetTestClass).get();
return test;
}

}

ポイントは2つ。

  1. Weldを作って初期化する。
  2. BlockJUnit4ClassRunnerを継承してcreateTestで対象のクラスをコンテナとして作る。

うん、簡単だよね!Weld大好き!……かも。いや、別にそんな好きなわけじゃないけど。
ちなみに、Runner自体は、テストクラスごとに出来るから、Weldはstaticに作ってるよ。
これで、ユニットテストクラスに@RunWithでCDIを有効にできるようになったのだ。

	@RunWith(JUnit4RunnerWithWeld.class)
public class SomethingTest {

あ、実際に有効にするには、beans.xml忘れないでね。
beans.xmlをどういう風にしたら良いかは別の記事でまとめるよ。

次に、JavaEEのスコープにコンテキストとストレージを設定しないとね。
まぁ、これは、GlassFish3.xのころにViewScopedとか作ってたし楽勝ってことで。
こんなユーティリティを用意する。長いよ。

	public class WeldUtilities {

public static String ExtensionProductName = "Empressia Test Library";
public static String SupportCDIProductName = "Weld";

/** JavaEE向けのスコープをJavaSEでサポートする。 */
public static void supportEEScopes(BeanManager bm)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Map<String, Object> requestMap = new HashMap<>();
WeldUtilities.activateContextWithStorage(bm, RequestScoped.class, requestMap);
Map<String, Object> sessionMap = new HashMap<>();
WeldUtilities.activateContextWithStorage(bm, SessionScoped.class, sessionMap);
WeldUtilities.activateContextWithStorage(bm, ConversationScoped.class, new MutableBoundRequest(requestMap, sessionMap));
}

/** JavaEE向けのスコープをJavaSEでサポートをリセットする。 */
public static void resetEEScopeContexts(BeanManager bm)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
AbstractBoundContext<?>[] contexts = {
WeldUtilities.findFirstContext(bm, RequestScoped.class),
WeldUtilities.findFirstContext(bm, SessionScoped.class),
WeldUtilities.findFirstContext(bm, ConversationScoped.class)
};
for(AbstractBoundContext<?> context : contexts) {
context.deactivate();
context.cleanup();
}
WeldUtilities.supportEEScopes(bm);
}

public static <S> void activateContextWithStorage(BeanManager bm, Class<? extends Annotation> scopeType, S storage)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
AbstractBoundContext<S> boundContext = WeldUtilities.findFirstContext(bm, scopeType);
boundContext.associate(storage);
boundContext.activate();
}

/** BeanManagerから指定スコープの最初のContextを抽出する。アクティブかどうかは考慮されない。 */
public static <S> AbstractBoundContext<S> findFirstContext(BeanManager bm, Class<? extends Annotation> scopeType)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Map<Class<? extends Annotation>, List<Context>> contextsMap = WeldUtilities.extractContextsMap(bm);
List<Context> contexts = contextsMap.get(scopeType);
// ContextはScopeの種類に対して必ず1個用意してある前提で。
if(contexts.isEmpty()) {
String message = "なにかメッセージ";
throw new IllegalStateException(message);
}
Context context = contexts.get(0);
if(context instanceof AbstractBoundContext) {
} else {
// java.lang.ClassCastException: org.jboss.weld.context.PassivatingContextWrapper$AlterableContextWrapper cannot be cast to org.jboss.weld.context.AbstractBoundContext
context = PassivatingContextWrapper.unwrap(context);
}
@SuppressWarnings("unchecked")
AbstractBoundContext<S> boundContext = (AbstractBoundContext<S>)context;
return boundContext;
}

/** BeanManagerからScopeTypeごとの?Contextを抽出する。 */
public static Map<Class<? extends Annotation>, List<Context>> extractContextsMap(BeanManager bm)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
BeanManagerImpl beanManagerImpl;
if(bm instanceof BeanManagerProxy) {
BeanManagerProxy beanManagerProxy = (BeanManagerProxy)bm;
beanManagerImpl = beanManagerProxy.delegate();
} else if(bm instanceof BeanManagerImpl) {
beanManagerImpl = (BeanManagerImpl)bm;
} else {
String message = "なにかメッセージ";
throw new IllegalStateException(message);
}
// getContextだとactiveなものしか取れないから、強引に設定したい今回の用途では使えない。
Method m = BeanManagerImpl.class.getDeclaredMethod("getContexts");
m.setAccessible(true);
@SuppressWarnings("unchecked")
Map<Class<? extends Annotation>, List<Context>> contextsMap = (Map<Class<? extends Annotation>, List<Context>>)m.invoke(beanManagerImpl);
return contextsMap;
}

}

この辺は、解説入れるとめんどくさいから、WeldのソースとかGlassFishのソースとか読むと良いかもってことで。
で、これを呼び出すExtensionを用意する。

	package jp.empressia.test;
// (略)
public class WeldTestExtension implements Extension {

public void afterDeployment(@Observes AfterDeploymentValidation e, BeanManager bm)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
logger(this).info("Empressia Test Library Weld Test Extension startup.");
WeldUtilities.supportEEScopes(bm);
}

}

このExtensionは、META-INF/servicesに『javax.enterprise.inject.spi.Extension』ってファイルを作って、その中に、

	jp.empressia.test.WeldTestExtension

とか書いておくよ。こうしておくことで、Weldの初期化がされたときに、@Observesが呼ばれてJavaEEのスコープがすぐに有効になるのだ。
あと、テストごとに、スコープはリセットした方が良いだろうから、Runnerのメソッドをオーバーライドしておこうね。

	public class JUnit4RunnerWithWeld extends BlockJUnit4ClassRunner {

// (略)

/** テストを1個ずつ実行します。順序は保証されないから、コンテキストは毎回リセットする。 */
@Override
protected void runChild(FrameworkMethod method, RunNotifier notifier) {
BeanManager bm = JUnit4RunnerWithWeld.Container.getBeanManager();
try {
WeldUtilities.resetEEScopeContexts(bm);
} catch(NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
String message = "なにかメッセージ";
throw new IllegalStateException(message, ex);
}
super.runChild(method, notifier);
}

}

これで完成(゚ー゚)(。_。)(゚-゚)(。_。)ウンウン

この辺のクラスをパッケージにしたのを置いておいたよ。

Mavenのリポジトリを追加して依存関係を書けば使えるよ。こんな感じ?

<repositories>
……
<repository>
<id>Empressia</id>
<url>http://www.empressia.jp/maven/</url>
</repository>
</repositories>

<dependencies>
……
<dependency>
<groupId>jp.empressia</groupId>
<artifactId>jp.empressia.test</artifactId>
<version>1.0.1-1</version>
<scope>test</scope>
</dependency>
</dependencies>

古い実装とかライブラリが混じっていると、JNDIにBeanManagerを登録しておかないとちゃんと動かないかもしれないけど。
このパッケージは、その辺も簡単にだけど対応しておいたよ。

posted by すふぃあ at 21:00| Comment(1) | TrackBack(0) | 雁字

JavaEEのユニットテスト環境を用意する

☆はじめに☆

JavaEEの機能は、去年作ったファイル転送Webアプリだと、WebSocketの機能しか使ってなかったし、
そんなユニットテスト書くくらい難しいの自体作ってないんだよね。
で、今回は、JavaEEをもうちょっとしっかり使おうかなと。

とりあえず、JavaEEが提供しているJAX-RS、CDI、JPAあたりを使ってWebアプリケーションを作ろうと思ったんだけど、
これ、どれもユニットテストがめんどくさい。
まぁ、JAX-RSは、問題領域とは直接関係ないから、ユニットテストの対象からは外すとして、問題はCDIとJPAだよね。

JavaEE自体は、関数の集まりみたいなライブラリと言うよりは、コンテナだから、
ユニットテストするためには、そのコンテナを再現しないといけないんだよね。
特に、CDIはコンテナそのものって感じだしね。

あとは、環境に依存したリソースの表現だよね。
JPAは、永続化先を抽象化してくれるけど、ユニットテストの時は、それが逆にめんどくさいことに。
でも、抽象化されること自体は便利だからやっぱりJPAは使うよね。

なんか、Mockって言うの作って対応したりする例とかよくWebで見かけるんだけど、
DBとかに繋げば良いだけなのに、そんなの作るのめんどくさいよね。
きっとそれが必要な時もあるんだとは思うけど。

☆準備☆

さて、ユニットテスト環境を作るにしても、何か想定はしないとダメだよね。
今回は、こんな条件を付けてみるよ。

  • ゴール:JavaEEのWebアプリ向けに問題領域のユニットテストとしてのコンテナを用意して簡単に使えるようにする。
  • ユニットテストには、JUnitを使う。
  • CDIとJPA(とJTA)を何となく使えるようにする。
  • ユニットテストでJPAは1個の接続先を使う。
  • JavaSE向けの実装は、WeldとEclipseLinkのGlassFish4.1のバージョンを使う。
  • Mock系のライブラリとかは使わない(使うといろいろ楽だけど依存しない)。
  • ビルドにはMavenを使う。

で、ちょっと書き出してから気づいたんだけど、これ、結構長くなる……。
ってことで分割したよ。

ホントいつも流れで書いてごめんなさい(゜▽、゜

posted by すふぃあ at 21:00| Comment(1) | TrackBack(0) | 雁字

Java8の暗号利用モードのGCMって遅すぎない?

去年作ったファイル転送WebアプリをGlassFish4.1とJava8に載せ替えたんだけど、
それだけで、凄ーく遅くなったの。
載せ替える前はGlassFish4だからJava7で動いてたんだよね。
あ、ハードはfit-PC2iのWindows8(32bit)で動いてるよ。

実際に測定するとこんな感じ。

ファイル転送Webアプリの転送速度(インターネットを経由させて測定)
サーバー 速度
Java7 GlassFish4.0 26000〜28000bps
Java8 GlassFish4.1 2600〜2800bps

うん、超遅くなった。あり得ないよね、ほんと。

わたしが作った部分がおかしいのかなーとか思って、プログラムを見たんだけど、
ファイルの書き込みくらいしか重そうな処理がなったんだよね。
ファイルIOが遅いのかなーって思って、ファイルの書き込み処理を外すとこんな感じ。

ファイル転送Webアプリの転送速度(インターネットを経由させて測定:書き込み処理無し)
サーバー 速度
Java7 GlassFish4.0 26000〜28000bps
Java8 GlassFish4.1 2600〜2800bps

変わんないし!全く変わんないし!

ファイル転送のWebアプリだから、ファイルの書き込み取っちゃったら、何も残らないのに……。
と言うことで、JavaとGlassFishに問題がないか探すことに。
まぁ、普通に考えると、GlassFish側かな?Javaなコアなところはみんな触ってるだろうし。

とはいえまずは実測実測。パフォーマンス測定の基本は実測だよね!
GlasFish4.1はJava7でも動くはずだから計ってみた。

ファイル転送Webアプリの転送速度(インターネットを経由させて測定)
サーバー 速度
Java7 GlassFish4.1 26000〜28000bps

あれ……。えーっと……。遅くならない……。
Javaのコアな部分に問題があるってこと……???(゜▽、゜
もうわたしにはわからない、わからないよ。

こうなったらもうプロファイル?を見るしかないよね!
と言うことで、NetBeansのプロファイル機能を見てみる。

Java8で時間がかかってるあたり
call_hierarchy.png

うん、完全にSSLの復号化のところですっごい時間かかってるね。
同じあたりをJava7とJava8で比較してみる。

Java7の呼び出しツリー
call_hierarchy_jdk7.png

Java8の呼び出しツリー
call_hierarchy_jdk7.png

Java8は結構呼び出しを展開してみたんだけど、同じ呼び出しが延々深く続いてるだけみたい。
NetBeansが重くなって最後まで開けなかったけど。

ここから分かるのは、Java7の方は、『AES』とか『CihperBlockChaining』とかが見えるんだけど、
Java8の方は、『AES』の他に、『GaloisCounterMode』とか『GHASH』とかが見える。
使われている暗号化の方式が変わってる……?

Oracleのサイトを見ると、Java7からJava8になったときにGCMって言うのが増えてるんだね。
https://docs.oracle.com/javase/jp/8/technotes/guides/security/SunProviders.html

これか!これが悪さしてるのか!新しいのに遅いなんて!

と、決めつける前に、確認確認。
Java7とJava8とで、それぞれのGlassFish4.1のセキュリティ設定を確認してみる。

Java7のセキュリティ設定
ciphers_jdk7.png

Java8のセキュリティ設定
ciphers_jdk8.png

たしかに、Java8の方が多くなってる〜。
これでJava8の時の暗号化方式をGCM以外から選ぶように設定ッと。
select_without_gcm.png

測定しなおした結果はこんな感じ。

ファイル転送Webアプリの転送速度(インターネットを経由させて測定:GCM除外)
サーバー 速度
Java8 GlassFish4.1 26000〜28000bps

速くなった!原因はGCM、これだね!

GlassFish4.1が関係ないことを確認するために、
Java8で実際に検証コードを書いてみる。

こんな感じ。大きく差が出るだろうから、適当で良いよね。

CBC

	int length = 200000000;
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
Key key = keyGenerator.generateKey();
IvParameterSpec parameters = new IvParameterSpec(new byte[16]);
Cipher CBCEncrypter = Cipher.getInstance("AES/CBC/PKCS5Padding");
CBCEncrypter.init(Cipher.ENCRYPT_MODE, key, parameters);
Cipher CBCDecrypter = Cipher.getInstance("AES/CBC/PKCS5Padding");
CBCDecrypter.init(Cipher.DECRYPT_MODE, key, parameters);
byte[] targetBytes = new byte[length];
long time0 = System.currentTimeMillis();
byte[] encryptedBytes = CBCEncrypter.doFinal(targetBytes);
long time1 = System.currentTimeMillis();
byte[] decryptedBytes = CBCDecrypter.doFinal(encryptedBytes);
long time2 = System.currentTimeMillis();
System.out.println(Arrays.equals(targetBytes, decryptedBytes));
System.out.println("暗号化時間:" + (time1 - time0) + "ミリ秒");
System.out.println("復号化時間:" + (time2 - time1) + "ミリ秒");

GCM

	int length = 200000000;
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
Key key = keyGenerator.generateKey();
GCMParameterSpec parameters = new GCMParameterSpec(128, new byte[16]);
Cipher GCMEncrypter = Cipher.getInstance("AES/GCM/PKCS5Padding");
GCMEncrypter.init(Cipher.ENCRYPT_MODE, key, parameters);
Cipher GCMDecrypter = Cipher.getInstance("AES/GCM/PKCS5Padding");
GCMDecrypter.init(Cipher.DECRYPT_MODE, key, parameters);
byte[] targetBytes = new byte[length];
long time0 = System.currentTimeMillis();
byte[] encryptedBytes = GCMEncrypter.doFinal(targetBytes);
long time1 = System.currentTimeMillis();
byte[] decryptedBytes = GCMDecrypter.doFinal(encryptedBytes);
long time2 = System.currentTimeMillis();
System.out.println(Arrays.equals(targetBytes, decryptedBytes));
System.out.println("暗号化時間:" + (time1 - time0) + "ミリ秒");
System.out.println("復号化時間:" + (time2 - time1) + "ミリ秒");

測定結果は、こんな感じ〜。

# バイト数 時間(ミリ秒)  
CBC GCM GCM/CBC
暗号化 復号化 暗号化 復号化 暗号化 復号化 AVG
1 100000 16 24 160 96 10.0 4.0 7.0
2 100000 16 24 160 96 10.0 4.0 7.0
3 1000000 64 88 1000 915 15.6 10.4 13.0
4 1000000 56 88 1016 912 18.1 10.4 14.3
5 10000000 392 400 8483 7852 21.6 19.6 20.6
6 10000000 376 368 7881 8004 21.0 21.8 21.4
7 100000000 3060 3351 53760 68640 17.6 20.5 19.0
8 100000000 3065 3325 74182 72976 24.2 21.9 23.1
9 100000000 2720 3142 61908 64244 22.8 20.4 21.6
10 200000000 4886 5103 110236 102925 22.6 20.2 21.4
11 200000000 6267 6223 125277 110626 20.0 17.8 18.9
12 200000000 5658 6211 124878 124675 22.1 20.1 21.1

 

うん、サイズが小さいときも遅いけど、サイズが大きくなってくるとだいたい20倍くらい遅いね!これはひどい(゜▽、゜
GCMのWikipedia見た感じだと、速くなりそうなのにね。
遅いにしても限度って言うものがあると思うんだけど……。
もしかして、去年Windowsのアプリが遅くなったのも何か関係あったりするのかな?
暗号化は、WindowsのNativeなAPI使ってるとか……?

まぁ、とりあえず直ったから良いよね(゚ー゚)(。_。)(゚-゚)(。_。)ウンウン

posted by すふぃあ at 21:00| Comment(3) | TrackBack(0) | 雁字

JavaEEなWebアプリケーションを作ろうとしたときのお話

☆はじめに☆

この記事はJava Advent Calendar 2014の12月21日分の記事だよ。

前は、2012年の時にAndroidの話を書いたんだけど、
今回は、Webアプリケーションを作ろうとしたときの話を書くのだ。

☆導入☆

去年の12月くらいに、いつも使ってたWindowsのファイル転送アプリが凄ーく遅くなって、
もう、これは耐えられない!ってことで、代わりにGlassFish4のWebSocketを使ったファイル転送Webアプリを作ったの。

で、先月、他にもなんかWebアプリケーションを作ってみようかなって思って、
ついでだからGlassFishを4.1にバージョンアップ!さらにJavaも7から8にバージョンアップ!したんだけど、
そのとたんに、ファイル転送Webアプリの速度が凄〜く遅くなったんだよね。

と言うわけで前編はその時のお話だよ。

で、遅いのは解決したんだけど、
新しいWebアプリは、ただ、転送するだけのアプリと違って、
いろいろ作りたかったから、
ちょっとがんばってユニットテストも作ろうかな!って思ったんだよね。

と言うわけで後編はその時のお話だったり。

ちなみに、今回作ったWebアプリケーション自体については何も書いてなかったりする(゜▽、゜

それと、今回はホント、ネタのあたりから、検証あたりとか、いろいろ協力してもらえて助かったのだ(゚ー゚)(。_。)(゚-゚)(。_。)ウンウン
間接的にも助けてもらったりしたし、みんなに感謝なのだ(゜▽、゜

posted by すふぃあ at 21:00| Comment(1) | TrackBack(0) | 雁字

2012年12月08日

Androidのjava.util.zipパッケージと戦った記憶

この記事はAdvent Calendar 2012 Javaの8日目だよ。

前の日の記事はryu22e(@ryu22)さんで、
Javaのデータベースマイグレーションツール「Flyway」 #JJUG』だったね。
次の日の記事はHideki Kishida(@quicy)さんがやってくれるみたいだよ。

で、この記事では、わたしが最近作っている『コミみみ』って言うAndroidアプリの動きを確認していたときのことを淡々と書くのだ。
読みやすさは全く考えられていないので覚悟して読んでね。

コミみみ』って言うのは、コミックマーケット用のアプリ……まぁ、えっと、カタログViewerなんだけど、
サークルカット(画像)をZIPで1つのファイルにまとめたものを読み込んでいるんだ。
その読み込みをするときに問題が起こったの。

ZIPファイルをJavaで扱う定番は、java.util.zipパッケージのZipFileクラスだよね。
で、ZipFileクラスで有名な問題といえば、
開こうとしているZIPファイルに含まれているファイル数が65535個を超えていると開けないってこと。
ZIP64っていう拡張仕様までサポートしてないよってことだから、不具合と言うより仕様かな。
これはAndroidに限らずある問題だね。Java7ではサポートしたみたいだけど、AndroidはJava6までだし。

65535個って言うのは、unsigned short型(2バイト)での表現の限界から来ているみたい。
だから、ZIP64をサポートしないと、それより多いファイルを含むZIPファイルは開けない。

Androidでわたしがぶつかった問題も、この『含まれているファイルの数』によるものなの。
この問題をぐーぐるさんで検索すると『多すぎるからだよ!』って言うのがたくさんヒットするよ。

でも、カタログでサークルカットが多いって言っても、35000個くらいしかないんだよね……。
65535個を超えてなくても開けないっ。
試して見ると、だいたい32000個を超えてきたくらいで開けなくなるんだよね。

はい、これでわかっちゃうと思うけど、32767個を超えると開けなくなるみたい。
Javaにはunsignedな型はないから、
unsigned shortで表現されている『含まれているファイルの数』を持つためにはint型が必要になるんだよね。
short型は-32768〜32767までしか表現できないから。
たぶん、short型でファイルの数を持っちゃっておかしくなっているんだと思う。

あれ?でも、ちょっと待って。なんでこんな問題があるの?
java.util.zipパッケージなんて大昔からあるわけでしょ?
そこに、こんな問題があるなんておかしいでしょ!

で、調べてみるとAndorid 2.3.3系では発生しないんだよね。これ。
でも、Android 4.0.xでは発生するの。Android 3.xはどっちかな〜?まぁ、どっちでもいいよね。
つまり、これ、最近……でもないかもだけど、java.util.zipパッケージが書き換えられたことで起こっているみたい。

……え?やっぱり、おかしくない?
拡張仕様をサポートしたわけでもないのに、ZipFileの実装が書き換わってる……?

Android 2.3.3系は……大丈夫っ。でも、その後書き換えられておかしくなっている……。

もしかして、OracleとGoogleの訴訟の問題から来てたりするのかな?
やっぱりGoogle、やっちゃってたのかな?とか想像してみちゃった。
詳しいこと何も知らないけどね!

Android 4系のアプリサポートはこれからだろうから、
ZIPでファイルをいっぱい使うする人は気をつけようね。

そこ!ZIPファイルで数万もファイル扱うことなんて無いなんて言わないでっ!
あ、忘れるところだった。解決方法だけど、
  1. ZIPフォーマットは簡単だから自分で作る。
  2. Android 2.3.3系は動くからそっちから持ってくる。
  3. org.apache.tools.zipパッケージを使う。
って言う感じかな。最後のは、java.util.zipパッケージより20倍〜40倍遅いからやめた方が良いと思うけど。
posted by すふぃあ at 11:34| Comment(2) | TrackBack(0) | 雁字

2012年09月15日

Androidのライブラリを作ってみたよ

Empressia for Androidって

Android用のライブラリを作ってみたよ。
いくつか便利なクラスを用意してあるから、紹介するね。
※Android 2.3.3(API Level 10)以上、r16以上のSupport Library v4を使ってるよ。

DBUnit

JPAみたいな@Idと@Columnのアノテーションをデータクラスにつけて、
簡単にDBのやりとりをするクラスだよ。
SQLかかなくてすむわけじゃないけど、だいぶ楽になるんじゃないかな〜。

基本的には、継承して使ってね。たとえばこんな感じ。
public class HogeDBUnit extends DBUnit {
@Override
protected Class<?>[] getManagedClasses() {
return new Class<?>[] { DataClass1.class, /* DataClass2.class, DataClass3.class */ };
}
}
public class DataClass1 {
@Id @Column
public int ID;
@Column
public String Name;
}

これで、DBUnit使うときに適当なCREATE文が自動で作られてDBが準備されるよ。
アノテーションはsetterとかにつけてもok。

データを全部取ってくる場合は
List list = this.DBUnit.findAll(DataClass1.class, null);
これで全件取得できるよ。

まぁ、JOINとかWHEREとかを書くときは、全部自力で書かないといけないんだけどね。
でもオブジェクトにマッピングするところは自動でできるよ。

SharedPreferences

Androidに用意されてるSharedPreferencesのキーってStringなんだよね。
ローカライズするものじゃないからだと思うけど、キーをリソースにしたくなったりしない?
そういう場合は、このクラス。

SharedPreferences prefs = new SharedPreferences(originalPrefs);
こんな感じでラップすればリソースをキーに使えるよ。
SharedPreferences prefs = SharedPreferencesUtilities.getPrivate(context);
ってやれば、アプリ用の設定を一発で取ってくることもできるよ。
う〜ん、微妙?
キーをリテラルで埋め込んでるよりはましかな〜って思ったんだけど。 

AbstractListAdapter

ListAdapterでなんかViewのリサイクルとか考えるのが面倒だから、AbstractListAdapterって言うのを用意してみたよ。
createViewとupdateViewをそれぞれ実装すれば良いだけだから簡単だよ。

AbstractListPagerAdapter

ViewPager用のクラスを作ってみたよ。
ViewPagerは元々、いろんな見栄えのページを切り替えるために作られたと思うんだけど、
同じ見栄えのページをListViewみたいに連続して表示したいと思うことってない?

コンストラクタで、レイアウトと表示するデータのリストを渡すと、あとは自動で切り替えとかするよ。

updateViewでViewを書き換えるだけで横に1ページごとに切り替えられるListViewのできあがり。
PagerAdateprとか、FragmentPagerAdapter、FragmentStatePagerAdapterから作るよりも
楽に作れてメモリも節約できるんじゃないかな〜。

他にもいろいろクラス入ってるので試してみてね('-^*/
Javadocもあるんだけど、なんかメモとか混入してるからほしい人がいればってことで……(゜▽、゜

posted by すふぃあ at 15:22| Comment(1) | TrackBack(0) | 雁字

2012年03月25日

DoCoMoのアプリ落ちすぎ

SPモードなメールを見るようになってからだと思うんだけど、なんかすごくDoCoMoのアプリが落ちる。
とりあえず、スタックトレースを2個拾ってみた。
ATOKの時は発生率低かったけど、こっちは簡単に起きるね(笑)

最初のやつは、黙って裏で吐いてるエラー(普通に使ってる人には見えない)。
2番目のは、強制終了したとかメッセージが出てくる。
バグレポートも送れないし、プリインストールするならもっとちゃんとしたの作ってほしいな。
2個目のは、DoCoMoじゃないのかな?

03-25 22:58:06.861: E/DatabaseUtils(32600): Writing exception to parcel
03-25 22:58:06.861: E/DatabaseUtils(32600): android.database.sqlite.SQLiteAbortException: unavailable to link the app during the startup process.
03-25 22:58:06.861: E/DatabaseUtils(32600): at jp.co.nttdocomo.carriermail.service.AppLinkProvider.h(AppLinkProvider.java:157)
03-25 22:58:06.861: E/DatabaseUtils(32600): at jp.co.nttdocomo.carriermail.service.AppLinkProvider.query(AppLinkProvider.java:371)
03-25 22:58:06.861: E/DatabaseUtils(32600): at android.content.ContentProvider$Transport.bulkQuery(ContentProvider.java:174)
03-25 22:58:06.861: E/DatabaseUtils(32600): at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:111)
03-25 22:58:06.861: E/DatabaseUtils(32600): at android.os.Binder.execTransact(Binder.java:324)
03-25 22:58:06.861: E/DatabaseUtils(32600): at dalvik.system.NativeStart.run(Native Method)
03-25 22:58:09.351: E/AndroidRuntime(32621): FATAL EXCEPTION: main
03-25 22:58:09.351: E/AndroidRuntime(32621): android.database.sqlite.SQLiteException: unable to close due to unfinalised statements
03-25 22:58:09.351: E/AndroidRuntime(32621): at android.database.sqlite.SQLiteDatabase.dbclose(Native Method)
03-25 22:58:09.351: E/AndroidRuntime(32621): at android.database.sqlite.SQLiteDatabase.onAllReferencesReleased(SQLiteDatabase.java:327)
03-25 22:58:09.351: E/AndroidRuntime(32621): at android.database.sqlite.SQLiteDatabase.close(SQLiteDatabase.java:892)
03-25 22:58:09.351: E/AndroidRuntime(32621): at android.database.sqlite.SQLiteOpenHelper.close(SQLiteOpenHelper.java:220)
03-25 22:58:09.351: E/AndroidRuntime(32621): at jp.co.omronsoft.android.decoemojimanager_docomo.DBManager.close(DBManager.java:224)
03-25 22:58:09.351: E/AndroidRuntime(32621): at jp.co.omronsoft.android.decoemojimanager_docomo.DecoEmojiManager$1.handleMessage(DecoEmojiManager.java:159)
03-25 22:58:09.351: E/AndroidRuntime(32621): at android.os.Handler.dispatchMessage(Handler.java:99)
03-25 22:58:09.351: E/AndroidRuntime(32621): at android.os.Looper.loop(Looper.java:138)
03-25 22:58:09.351: E/AndroidRuntime(32621): at android.app.ActivityThread.main(ActivityThread.java:3701)
03-25 22:58:09.351: E/AndroidRuntime(32621): at java.lang.reflect.Method.invokeNative(Native Method)
03-25 22:58:09.351: E/AndroidRuntime(32621): at java.lang.reflect.Method.invoke(Method.java:507)
03-25 22:58:09.351: E/AndroidRuntime(32621): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:878)
03-25 22:58:09.351: E/AndroidRuntime(32621): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636)
03-25 22:58:09.351: E/AndroidRuntime(32621): at dalvik.system.NativeStart.main(Native Method)
posted by すふぃあ at 23:22| Comment(1) | TrackBack(0) | 雁字

2011年12月24日

LINQがわからない

この記事はAdvent Calendar 2011のC#の24日目……のおまけ編だよ。

Advent Calendarの他の人の記事でも解説はいろいろ出てきているけど、LINQがわからないんだよね。
と言うわけで、何がわからないのかを書いてみるよ。

わからないって言うのは、使いどころって言うか、どういうときに使うのかって言うか、そんな感じ。
わたしの直感全開なので意味不明になると思うけど、いつものことなので気にしない。

とにかく使えばいいじゃんって話なのかもだけど、
よくわからないでとりあえずよさそうだから使うって言うのはどうかと思うの。
最初はそれでも、ちゃんと理解した方が良いよね。

で、LINQって調べてると、SQLっぽいのがいっぱい出てくるんだよね。
SQLってDBとかになれてる人が見るとわかりやすいのかもしれないけど、
クライアントのアプリ作ってるとDBってあまり使わない気がするの。
だから、クエリ構文?で解説されてもよくわからない……。

この記事を書くのに、メソッド構文?を中心に書いてあるneue ccさんの記事から
いくつかサイト巡ってたら、この辺にパイプライン型とかの図があってわかりやすかった。

で、
「LINQってデータコレクションを、好きなパイプラインを使ってフィルターしてくれるってことかなー」
って思うところまで来たんだけど、それで良いのかがわからない(・x・

……これを書いてる間に少しわかってきた気がするから、少し使ってみようかな?
posted by すふぃあ at 00:45| Comment(1) | TrackBack(0) | 雁字

Windows PhoneでVNC Viewerつくってみたよ

☆前書き☆

この記事はAdvent Calendar 2011のC#の24日目だよ。

去年のAdvent Calendarとかみんな凄いの書いてるから、
わたしなんか書いて良いのかな〜って思ったりもしたんだけど、
相談したら書いて良いんじゃない?って言われたので書くことにしたの。

他の人に比べて内容も読みやすさもレベルを下げてお届け!
読みにくいって思ったら、この日の記事はなかったことにして次に期待すると良いとおもいまーす。
あ、C#のコアな話を期待している人、そんな内容はないからね!

と、前書きはこのくらいにしてすすむね。
この1ヶ月くらいで作ったWindows Phoneアプリあたりの話をだらだら書くよ。

☆作ったWindows Phoneアプリの紹介☆

C#の勉強ついでにいろいろアプリ作ってみてるんだけど、
Windows Phoneを手に入れたので、せっかくだから何かつくろーって。
で、Androidで作った「ラティ」ってアプリのWindows Phone版を作ることにしたの。
VNCとかRDPとかの、いわゆるリモートデスクトップって言うののクライアントアプリだよ。

なんか、最初に作るならHello Worldでしょ!って言うのが普通なのかもしれないけど、
見た目さみしいし、やっぱりきれいで動くのがいいじゃん?

Android版はVNCベースじゃなかったから、今回は、VNCにちゃんとつながるようにしよう!
というわけでWindows Phone版の「ラティ」はTightVNCのServerとかにつながるようになってるよ。
有料のカテゴリ?になってるけど、トライアル版でも機能差は無いので普通に無料で使えるよ。

☆RFBプロトコル編☆

VNCって言うのはRFBプロトコルで通信するので、それを実装したライブラリとかがないと、だよね。
ってことで探したんだけど……、使えそうなのがないっ(・x・

というわけでRFBプロトコルの仕様書日本語)を読むところから始めることに。
リモートデスクトップのプロトコルって読み解くの大変なんじゃないのかなぁ。
な〜んて最初は思って読んでみたんだけど、これが意外に凄い簡単なの。
FTPのプロトコル日本語)よりも簡単簡単。たぶん。

最初はなんか文字列の送受信するだけで、あとは、
位置+サイズ+画像のデータをひたすら受け取るだけだよ。ほら簡単。

いろいろ手抜き感はあるけど、実装したライブラリがあるので後でリンク張るね。
ここでは簡単に使い方を紹介するよ。

基本的なプロトコルの実装は、RFBStreamって言うのがあるから、それを使えばおっけ。
でもほら、接続から一連のパスワード確認とかは、まとめてやった方が楽じゃない?
というわけでRFBClientってクラスもあるよ。
でもでも、それでももっと簡単に使いたいって人のために、EmprssiaRFBClientって言うのも用意してみた〜。
今回はこれの紹介ってことで。

えーっと、ラティから抜粋したコードをブログ用に書き換えてるので動作保証はないでーす。
あ、もちろん、書き換えて無くても動作保証はないけど。

コーディングスタイルがあり得ない!って言うのはスルーするけど、
LINQとかRxとかAsyncはぜ〜んぜん使えてないのだ。
せっかくAsync CTP入れたのにね!(謎)

this.RFBClient = new EmpressiaRFBClient();
int[] encodings = new int[] { EncodingType.tight, EncodingType.ZRLE, EncodingType.zlib, EncodingType.zlibhex, EncodingType.CoRRE, EncodingType.RRE, EncodingType.Raw, EncodingType.Hextile, EncodingType.Cursor };

// 1.セットアップでいろいろ準備。
this.RFBClient.setup(
// 1−1.使う通信方式(エンコーディング)の設定。
encodings,
// 1−2.カーソルをクライアントで描画する。
(PixelFormat, x, y, width, height, pixelBytes, maskBytes) => {
}
// 1−3.EncodingType.CopyRectを使用する場合はもう一個引数増える。
);
this.RFBClient.ConnectFailed += (sender, e) => {
if(e.IsCanceled) {
return;
}
string message = null;
if(e.SecurityResult != null) {
if(e.SecurityResult.FailedReason != null) {
message = e.SecurityResult.FailedReason;
}
}
if(message == null) {
if(e.UnexpectedException != null) {
message = e.UnexpectedException.Message;
} else {
message = "なんか接続に失敗したみたい。ごめんなさい。理由はよくわかんない。";
}
}
this.RaiseDisconnected(message);
};
this.RFBClient.SendFailed += (ex) => {
this.RaiseDisconnected(ex.Message);
};
this.RFBClient.ReceiveFailed += (ex) => {
this.RaiseDisconnected(ex.Message);
};
this.RFBClient.FrameBufferInitialized += (PixelFormat, width, height) => {
// 2.画面情報の初期化。
AutoResetEvent Lock = new AutoResetEvent(false);
this.Dispatcher.BeginInvoke((EmpressiaRFBClient.FrameBufferInitializedHandler)delegate {
// 2−1.Windows PhoneだとARGB32が楽。違う場合は要求を送信するよ。
PixelFormat ARGB32 = new PixelFormat() {
BitsPerPixel = 32,
Depth = 24,
BigEndianFlag = 0,
TrueColourFlag = 1,
RedMax = 255,
GreenMax = 255,
BlueMax = 255,
RedShift = 16,
GreenShift = 8,
BlueShift = 0,
Padding1 = 0,
Padding2 = 0,
Padding3 = 0,
};
if(PixelFormat.Equals(ARGB32) == false) {
this.RFBClient.sendSetPixelFormat(ARGB32);
}
this.Image = new WriteableBitmap(this.RFBClient.FrameBufferWidth, this.RFBClient.FrameBufferHeight);
Lock.Set();
}, PixelFormat, width, height);
Lock.WaitOne();
};
this.RFBClient.MessageReceivingStarted += () => {
// 3.初回だけリクエストのタイミングを決める。2回目以降は勝手に呼ばれる。
this.RFBClient.sendFramebufferUpdateRequest(true, 0, 0, this.RFBClient.FrameBufferWidth, this.RFBClient.FrameBufferHeight);
};
this.RFBClient.ImageReceived += (s, rects) => {
lock(this.ImageEventLock) {
this.ImageLock.Reset();
this.Dispatcher.BeginInvoke((EmpressiaRFBClient.ImageReceivedHandler)delegate {
foreach(RFBRectangle rect in rects) {
// 4.透明なのを塗りつぶすよ。
for(int i = 3; i < rect.PixelBytes.Length; i += 4) {
rect.PixelBytes[i] = 0xFF;
}
for(int lineIndex = 0; lineIndex < rect.Height; ++lineIndex) {
Buffer.BlockCopy(rect.PixelBytes, lineIndex * rect.Stride, this.Image.Pixels, (rect.Y + lineIndex) * this.Image.PixelWidth * 4 + rect.X * 4, rect.Stride);
}
}
this.Image.Invalidate();
this.ImageLock.Set();
}, s, rects);
this.ImageLock.WaitOne();
}
};

this.RFBClient.connectAsync(Host, Port, Password);

重要そうなところは1〜4の番号を振ってコメント入れておいたっ。
1−2と1−3:
特殊な画像処理が必要なところ(クライアントの描画環境によるからRFBの範囲ではどうしようもない)。
2−1:
Windows PhoneのWriteableBitmapがARGB32(BGRA32?)だから楽するための処理ね。
わたしの作ったライブラリの一部がそれ前提なところが残ってる気もするし。
3:
コメント書いてて、これライブラリとしてダメじゃないカナーとか思った!
思ったけど今はこうなってるからいいよね(゚ー゚)(。_。)(゚-゚)(。_。)ウンウン
4:
RFBプロトコルは、透明度扱わないんだけど、
WriteableBitmapのARGB32だとアルファチャンネルってことになっちゃうから、
全部透過なしに上書きしてる。

あとは、xaml側と調整すれば画面が出るはず〜。
キー送信とか、マウスの操作は、
sendKeyEventとsendPointerEventっていうメソッドがあるから画面の操作と連携させればおっけ〜。
KeyEventは結構大変だけどねっ。マウス操作くらいならすぐすぐ。

是非試してみてね(  ̄▽ ̄)ノ

☆SSHプロトコル編☆

RFBプロトコルってパスワード部分をのぞけば暗号化されないから、
SSHでポートフォワードとかやりたいよね。
というか、Empressia Tunnelなんて作ってたから、
簡単にできるでしょ〜って思って、手を出してみたの。
まぁ、つまり大変でしたって話なんだけど……。

C#でSSHと言えば、SharpSSHとかが有名……だよね、
うん、わたしが知ってるくらいだから有名なの。
その、SharpSSH使おうと思ったんだけど、
これってJavaのJSchっていうライブラリの移植版みたいなんだよね。
元が結構古くて.NEtFramework1.1ベースみたい。

ってことで、ArrayListとかHashtableとかがWindows Phoneだと動かない。
Threadとかのメソッドも減って消えてるメソッドがあるからそのままじゃ使えないし。
BigIntegerもMono?のを使ってるみたいでこれがunsafeコードを持ってるんだよね。
結局、いろんなところ書き換えちゃった。

ほんとはそのまま動くようにするだけで良いんだろうけど、
元がJavaだからか、プロパティとかイベントとかがほとんど使われてなかったから、
めんどくさくて適当に書き換えちゃったところも。

RFBと同じでライブラリは最後にまとめて張るね。
まぁ、ポートフォワード付近しかタメしてなかったりするから、いろいろなところでエラーとか出るかも(笑)

あ、おまけでEmpressiaSSHなんてライブラリも作ったよ。
このライブラリを使うと、SECSH(ssh.com)形式の秘密鍵ファイルが使えるようになるの。
あと、ポートフォワードが簡単に使えるクラスEmpressiaSSHTunnelClientも用意してあるのでお試しアレ。
RFBみたいに使い方を紹介するよ。
もちろん動かなくても知らない!さすがわたし!無責任!

EmpressiaSSHTunnelClient SSHTunnelClient = new EmpressiaSSHTunnelClient();
userInfo.FingerprintConfirmationRequired += (string host, string type, string fingerprint) => {
return true;
};
userInfo.ChangedFingerprintConfirmationRequired += (string host, string type, string fingerprint) => {
return true;
};
SSHTunnelClient.Connected += delegate {
// 接続したよ。
};
SSHTunnelClient.Bound += delegate {
// ポートフォワードが開始されたよ。
};
SSHTunnelClient.ConnectionFailed += delegate {
// 接続に失敗したよ。
};
SSHTunnelClient.Disconnected += delegate {
// 切断されたよ。
};
SSHTunnelClient.Unbound += delegate {
// ポートフォワードが終わったよ。
};
EmpressiaUserInfo userInfo = new EmpressiaUserInfo("sshpassword");
SSHTunnelClient.connectAsync(new TransferProfile() {
TunnelHost = "example.com",
TunnelPort = 22,
TunnelUser = "sshuser",
TunnelPassword = "sshpassword",
LocalHost = "127.0.0.1",
LocalPort = 5900,
RemoteHost = "192.168.0.2",
RemotePort = 5900,
}, userInfo, null);
なんか、パスワード2回指定してるけど気にしない!

でもさ、結局、Windows PhoneってSocketのAcceptできないんだよね。
というわけでローカルポートにバインドできなくて今回はあきらめたっ_(__)ノ彡☆

あ、あと、SilverlightとWindows Phoneだと、SshShellのコンソールリダイレクトが使えないはず。
Console.OpenStandardInput()とConsole.OpenStandardOutput()をどうすればいいのかがわからなくて……。

☆基本ライブラリ編☆

SSHとかRFBをWindows Phoneで作る時に思ったんだけど、
Windows Phoneって結構標準のクラスライブラリが削られてて面倒なんだよね。
暗号系はBouncy Castleあたりがあれば良いんだけど、ネットワーク関連がどうしようもなくて……。
ってことで、NetworkStreamとか作ってみてるよ。
パフォーマンスは微妙だと思うけど、だいたい同じ感じで使えるはず……。

あと、リングバッファなByteQueueとか、
それをストリームで扱うEndlessStreamなんかもあるので良ければドーゾ。

EmpressiaLibrary.dllにまとめて入れてあるので興味があれば〜。
ライブラリはまたまた最後にまとめて張っておくね。

使い方は、別に特殊なのはないので省略ね。
……みんなはこういうのどうしてるんだろう……?(・_・?)ハテ

☆その他ライブラリ編☆

RFBやってると、ZlibなStreamを延々読み続けないといけないんだよね。
ただ1個のストリーム読むだけなら良いんだけど、
飛び飛びにあるピクセルデータ部分だけを連続したZlibなStreamとして扱わないとダメって感じ。
標準クラスライブラリのDeflateStreamを使ってる分には問題ないんだけど、
DotNetZipのDeflateStreamだと、これがうまく動かないっ。
というわけでこれも書き換えてみたよ。
まぁ、使う機会はほとんどないと思うけど、後で張っておくね!

Bouncy CastleもWindows Phoneで動くように書き換えたのでこれも張っておくよ。

☆ライブラリまとめ☆

ず〜っとライブラリの話書いてきたけど、下に関連してる図を載せておくね。
EmpressiaLibraryDependencyGraph_20111224.png
であとは、ライブラリ達でーす。

えっと、Silverlight版は、プロジェクトファイル用意するだけなんだけど、
わたしが使う予定がないので用意してなかったり!

動かないじゃんっていうのがあれば、再現できるサンプルとかあればがんばるよ!たぶん(・x・

☆余談☆

ところで、Vita用のSDKが来ないんだけど……。
って書こうと思ったら、昨日(23日)の15時頃に連絡があったよ。
やっと、開発環境周りの日本語訳ができたのかな〜。
というわけでPS Suite SDKダウンロードしてみた。
まだ何も作ってないけど、なんか作りにくそうだった……(・x・

タグ:Windows Phone
posted by すふぃあ at 00:34| Comment(4) | TrackBack(1) | 雁字

2011年12月08日

Windows Phone 7で暗号化

えっと、なんか、WP7の暗号化っていろいろ足りなかったりするよね。

たぶん、Phone7.FxとかBouncy Castleとかいろいろあるから、
何でも使えば良いんだと想うんだけど、

#wp7dev_jpで困っている人がいるみたいだったので、
とりあえず、Bouncy CastleをWP7で動くようにビルドしたのをおいてみたよ('-^*/

もちろん、ライセンスは、元の方を見て使ってね。
http://www.bouncycastle.org/
一応ビルドしたのはここね(゜▽、゜
http://www.empressia.jp/dll/BouncyCastle.Empressia.WindowsPhone.dll

こういうのはダメなんだよ!ってことはないとおもってるけど、
そういう情報があったら教えてね。速攻消すから。
あと、WP7で使っておかしくなっても、元の方に文句言わないでね。
タグ:Windows Phone
posted by すふぃあ at 23:09| Comment(1) | TrackBack(0) | 雁字

2011年10月30日

Windows PhoneのISEToolのGUIアプリ

Windows Phone OS 7.1 あたりで出てきたISEToolがCUIなので、
GUIで操作できるアプリを作ってみたよ。一通り必要な機能は入れたつもり。
この辺からダウンロードできるよ

リモートの表示がちょっと遅いんだよねぇ。階層が深ければ深いほど。
必要なときに読み込むようにしてあげた方が良いのかなぁ。
タグ:Windows Phone
posted by すふぃあ at 15:13| Comment(3) | TrackBack(0) | 雁字

2011年09月03日

MMDX(SlimDX版)のモデルロードを高速化

何となくぼーっと、MMDXを触ってみているよ。


Lat式ミクVer2.3を歩かせてみてるんだけど、ロード時間が長いので、ライブラリの速度アップをしてみました。


ここのリンクからダウンロードできるよ。


わたしのPC上でだけど、Lat式ミクVer2.3のロード時間が12秒→3秒くらいになた(゜▽、゜快適
タグ:MMD MMDX
posted by すふぃあ at 01:08| Comment(1) | TrackBack(0) | 雁字

2011年07月18日

WeldからJSFのViewScopedが使えるようになったみたい

Weld(CDI)とMojarra(JSF)の連携がGlassFish v3.1あたりで強化されているみたいなので確認してみたよ。



GlassFish v3のころは、連携が十分じゃなかったんだよね。

具体的には、@ManagedBeanに対して、@Injectが動かなかったの。

でも今は動くようになってる。




比較していくと、普通にWeld(CDI)の場合はこんな感じ。

テキストエディタで書いてるから間違ってたらごめんなさい。



@javax.inject.Named

@javax.enterprise.context.RequestScoped

public class HogeComponent {

  // CDIとしてDIされる。

  @javax.inject.Inject

  PiyoComponent piyo;

}


これでCDI管理の元で、Injectされる。

で、今までMojarra(JSF)で同じことやろうとすると、こんな感じ。


@javax.faces.bean.ManagedBean

@javax.faces.bean.ViewScoped

public class HogeComponent {

  // JSFとしてDIされる。

  @javax.faces.bean.ManagedProperty

  PiyoComponent piyo;

}

@Injectが使えなかったってこと。


ManagedBeanだと、CDI管理下に入らないから、

CDIがInjectするための@Injectが動かなかったんだと思う。


Weldの良いところは、なんでも@Injectで入れちゃうぞ、っていうことだから、

『ManagedBeanの方使わなければいいじゃん。』っていう話でもあったんだけど、

そうすると、ViewScopedが使えなくなるという……。



下のようなのは動かないってこと。



@javax.inject.Named

@javax.faces.bean.ViewScoped

public class HogeComponent {

  // GlassFish v3では動かない。

  @javax.inject.Inject

  PiyoComponent piyo;

}

このために、Weldで動くViewScopedを自作するというのをやってたんだけど、

GlassFish v3.1に載ってるWeldさんならそんな必要もなくなったみたい。

結局、下のでCDI管理下に入るようになったのかな。



@javax.faces.bean.ManagedBean

@javax.faces.bean.ViewScoped

public class HogeComponent {

  // GlassFish v3.1では動く。

  @javax.inject.Inject

  PiyoComponent piyo;

}

これで、Injectがちゃんとされる。JSF全体がCDI管理下に入った感じかな。

もうオリジナルなViewScopedはいらないね('-^*/


タグ:Weld CDI ViewScoped
posted by すふぃあ at 10:36| Comment(2) | TrackBack(0) | 雁字

2011年05月13日

AndroidのATOKでエラー やっとスタックトレース捕まえたよ

別にデバッグしたかった訳じゃないんだけど、Android版ATOKがなかなか更新がないので張ってみる。

激しく色々リークしてそうな……(゜▽、゜

今日バージョン上がったみたいだけど直ってないとおもうんだよねー。

裏の見えないところで色々リークしてそうだねー。



ERROR/Database(2392): Leak found

ERROR/Database(2392): java.lang.IllegalStateException: /data/data/com.justsystems.atokmobile.trial.sifications.db SQLiteDatabase created and never closed

ERROR/Database(2392): at android.database.sqlite.SQLiteDatabase.(SQLiteDatabase.java:1695)


ERROR/Database(2392): at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java

ERROR/Database(2392): at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatab

ERROR/Database(2392): at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatab

ERROR/Database(2392): at android.app.ApplicationContext.openOrCreateDatabase(ApplicationContext.

ERROR/Database(2392): at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java

ERROR/Database(2392): at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpen

ERROR/Database(2392): at android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpen

ERROR/Database(2392): at com.justsystems.atokmobile.trial.common.c.a(Unknown Source)

ERROR/Database(2392): at com.justsystems.atokmobile.trial.service.AtokInputMethodService.onStart)

ERROR/Database(2392): at android.inputmethodservice.InputMethodService.doStartInput(InputMethodS

ERROR/Database(2392): at android.inputmethodservice.InputMethodService$InputMethodImpl.startInpu.java:354)

ERROR/Database(2392): at android.inputmethodservice.IInputMethodWrapper.executeMessage(IInputMet

ERROR/Database(2392): at com.android.internal.os.HandlerCaller$MyHandler.handleMessage(HandlerCa

ERROR/Database(2392): at android.os.Handler.dispatchMessage(Handler.java:99)

ERROR/Database(2392): at android.os.Looper.loop(Looper.java:123)

ERROR/Database(2392): at android.app.ActivityThread.main(ActivityThread.java:4370)

ERROR/Database(2392): at java.lang.reflect.Method.invokeNative(Native Method)

ERROR/Database(2392): at java.lang.reflect.Method.invoke(Method.java:521)

ERROR/Database(2392): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.j

ERROR/Database(2392): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)

ERROR/Database(2392): at dalvik.system.NativeStart.main(Native Method)
タグ:android
posted by すふぃあ at 21:04| Comment(2) | TrackBack(1) | 雁字

2011年05月02日

もしわたしがAndroidのFragmentを使ったら


さて、だいぶ更新あいちゃったけど、

Fragment APIを一通り使ってみたので、書いてみようと思うの。

いつものように、普通のその辺のチュートリアル記事とかじゃおもしろくないので、

わざとそれとは違うことを書くですよ。




まず、よく見るのは、Fragment使うと、UIがとっても便利!みたいな話だよね。

これは、たしかにタブレットなAndroidで重要で、そのためにFragmentが作られたみたいだし間違ってはいない……気がする。

でも……、見た目ばかり先行しちゃって、重要なことが本当に理解はされていないと思うんだよね。

つまりね、一番重要なのは、「ライフサイクル動作までの単位で再利用を可能にしたこと」だと思うの。




Dialogを例にしてみるよ。

今まで(Fragment APIが出るまで)は、Dialogは拡張できたけど、そのライフサイクル依存な動作は、Activityに書くしかなかったよね。

えっと、onResumeで決まった動作をするDialogとか作りたくても、その部分だけは、Activityに記述するしかなかったの。

同じような処理をいろんなアクティビティでやりたければ、Activityごとに全部書かないといけなかったってこと。

これはとっても再利用しにくいよね。




で、Fragmentがあるとどうなるかっていうと。

Fragmentにもライフサイクル機能があるから、onResumeでのその決まった動作は、ActivityじゃなくてFragmentの方にかける。

ってことは、Activityにはそのコードがなくなる。つまり、Activityごとに書いてた処理が一つのFragmentに寄せられるってわけ。

ライフサイクルのプラグインって感じね。まぁ、実際コード見ると、ActivityのonResumeで、FragmentのonResumeが呼ばれているわけだけど(笑)

これで、必要なコードは一カ所にまとめられて再利用しやすくなるの。




あ、そうそう、注意点があるよ。

あくまでもこれは、Activityに寄せられてたライフサイクルの記述をFragmentと言う単位に切り出せるようになっただけ。

よくListViewがListFragmentになるように見られることがあるみたいだけど(、まぁ、それは間違っていないんだけど)、

実際には、Activityに書かれてたコードがFragmentに寄るだけで、UIをカスタマイズしたらそのコードはFragmentとは別に用意することになる。

「Activity→Fragment→View」ってなるかな。クラスはFragmentの分増えるってこと。

間違えないようにしないとね。




もう一個重要なのは、上に書いたことから、ViewがないFragmentも作れるってこと。

ライフサイクルに沿ったバックグラウンド処理とかが、Acitivityから切り離したと言う意味で再利用可能な形で作れるってことだよ。

むしろ今は、こっちの方が重要な気がする(笑)




何でかっていうと、Fragment、なんかアニメーションがおかしいんだよね。

普通に設計ミス?みたいな感じがしてるけど(笑)

一応、「Android Issue 15623」あたりでググれば出てくると思うよ♪




適当にやってきた感じをまとめると以下のような感じかなー。あくまで個人的な感覚だからね〜(゜▽、゜

1.ダイアログ表示は積極的にDialogFragmentを使うようにする。

2.ListViewはライフサイクル制御が必要なときだけFragmentにすればok無駄にFragmentにする必要はない。

3.ライフサイクルに依存したバックグラウンド処理はFragment使うといい感じ。

4.アニメーションしたいなら、Issue 15623あたりに注意する。


タグ:android
posted by すふぃあ at 14:17| Comment(1) | TrackBack(0) | 雁字