yoおさらい

ここ2,3ヶ月、AngularJSでアプリケーション作ってたんですが、
もう一つAngularJSでアプリケーションを作る事になり、
あれ、最初どうやったんだっけとボケてしまっていたので、
yoでひな形作るとこをおさらいです。

YEOMANによって、Yo, Grunt, Bowerが動作する環境です。

yo angular

で実行したディレクトリにAngularJSのひな形が出来上がります。
AngularJSのテンプレートが無い場合は、

npm install -g generator-angular

でインストールしておきます。

あとはもう

grunt server

でGruntでWebサーバが起動するので、ガシガシ実装します。
ひな形作る作業としてはこれだけですね。
いやー楽過ぎる。ミスもしないし。

AngularJSでSelectが一文字しか表示されない。

解決に苦労したので、メモ。
select書く場合、こんな感じですね。

<select ng-model="type" ng-options="item.val as item.name for item in types" disabled>
</select>

これをモーダル表示させた場合、
http://i.stack.imgur.com/3kb0w.png
こんなんなります。IE9で発生します。ChromeFirefox、IE10以降では発生しません。
試行錯誤した結果、
原因としては、ng-optionsからのng-repeatが発生する時に
既にブラウザではselectの幅が決定されている為、一文字しか表示されないのだと思います。
実際、
・素直にselect,optionを記述すると発生しない。
・ng-optionsを使わず、optionでng-repeatを使い、固定で何かoptionを追加指定すると、
 固定で追加した分の文字のバイト数分だけは表示される。(hogeと追加すると4バイトは表示される。UTF-8)

IE9だけの為に個別対応はしたくないので、調べた所、解決策ありました。
angularjs - ng-options populated by ajax only displaying first letter in IE - Stack Overflow

<select ng-style="{'width': '100%'}" ng-model="type" ng-options="item.val as item.name for item in types" disabled>
</select>

ng-styleで幅を100%にしてあげるだけでOKです。
よかったよかった。

 

Java7 Zip

Java7からnioにFilesが追加されたりと諸々ファイル周りのAPIが強化されてますね。
個人的には、Zipをライブラリに頼らず、Javaだけでサクッとやりたいと思ってました。
Zip File System Provider

まぁドキュメントやらgoogle先生に助けてもらって実装出来たんですけど、
圧縮は問題なさそうですね。
SimpleFileVisitorでディレクトリを探索して、Zipに追加って感じで。
相対パスをちゃんと考えないといけないので、
変数名を分かりやすくしとかないと若干混乱します。

で、解凍なんですが、圧縮と同じような考えでいけます。
解凍のほうがコードは短くすみます。
ただ、どうにも分からなかったのが、
手動で圧縮したファイルやJavaで圧縮したZipは問題なく解凍出来たのですが、
HttpClientで取得したファイルの解凍が出来ませんでした。

InputStream in = responce.getEntityInputStream();
byte[] bytes = ByteStreams.toByteArray(in); // guava
File file = new File("C:/Users/nosa/AppData/Local/Temp/temp.zip");
Files.write(file.toPath(), bytes, StandardOpenOption.CREATE);

これを解凍したかったんですが、このZipをFileSystemに食わすと

java.util.zip.ZipError: zip END header not found
	at com.sun.nio.zipfs.ZipFileSystem.zerror(ZipFileSystem.java:1605)
	at com.sun.nio.zipfs.ZipFileSystem.findEND(ZipFileSystem.java:1021)
	at com.sun.nio.zipfs.ZipFileSystem.initCEN(ZipFileSystem.java:1030)
	at com.sun.nio.zipfs.ZipFileSystem.<init>(ZipFileSystem.java:130)
	at com.sun.nio.zipfs.ZipFileSystemProvider.newFileSystem(ZipFileSystemProvider.java:117)
	at java.nio.file.FileSystems.newFileSystem(FileSystems.java:322)
	at java.nio.file.FileSystems.newFileSystem(FileSystems.java:272)

が発生します。
昔ながらの方法(ZipFile,ZipEntry)の方法だと解凍出来ました。(もちろん手動でも解凍出来る)
また、zip4jでも解凍出来ました。

圧縮

        File zipDir = new File(dirPath);
        final Path zipPath = zipDir.toPath();
        // 圧縮
        URI zipUri = zipFile.toURI();
        URI uri = new URI("jar:" + zipUri.getScheme(), zipUri.getPath(), null);
        Map<String, String> env = new HashMap<>();
        env.put("create", "true");
        env.put("encoding", "UTF-8");
        try (FileSystem zipfs = FileSystems.newFileSystem(uri, env)) {
            for (File srcFile : zipDir.listFiles()) {
                if (srcFile.isDirectory()) {
                    // ディレクトリ
                    Files.walkFileTree(srcFile.toPath(), new SimpleFileVisitor<Path>() {
                        @Override
                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                                throws IOException {
                            // zipするディレクトリでのファイルの相対パス
                            Path targetPath = zipPath.relativize(file);
                            Files.copy(file, zipfs.getPath(targetPath.toString()),
                                    StandardCopyOption.REPLACE_EXISTING);
                            return FileVisitResult.CONTINUE;
                        }

                        @Override
                        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                                throws IOException {
                            // zipするディレクトリでのディレクトリの相対パス
                            Path targetPath = zipPath.relativize(dir);
                            Files.createDirectory(zipfs.getPath(targetPath.toString()));
                            return FileVisitResult.CONTINUE;
                        }
                    });
                } else {
                    // ファイル
                    Path dest = zipfs.getPath(srcFile.getName());
                    Files.copy(srcFile.toPath(), dest, StandardCopyOption.REPLACE_EXISTING);
                }
            }
        }

解凍

        // 解凍
        URI zipUri = zipFile.toURI();
        URI uri = new URI("jar:" + zipUri.getScheme(), zipUri.getPath(), null);
        Map<String, String> env = new HashMap<>();
        env.put("create", "false");
        env.put("encoding", "UTF-8");
        try (FileSystem zipfs = FileSystems.newFileSystem(uri, env)) {
            final Path zipRoot = zipfs.getPath("/");
            Files.walkFileTree(zipRoot, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                        throws IOException {
                    Path targetPath = Paths.get(toDir.toString(), file.toString());
                    Files.copy(file, targetPath, StandardCopyOption.REPLACE_EXISTING);
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                        throws IOException {
                    Path targetPath = Paths.get(toDir.toString(), dir.toString());
                    Files.createDirectory(targetPath);
                    return FileVisitResult.CONTINUE;
                }
            });
        }

うーん。SimpleFileVisitorの仕様は好きなんですけど原因分からず、zip4jに逃げようかと...。
Zipフォーマットを理解してからもう一度調べてみよう。
Zipが壊れてて手でも解凍出来ない、ZipFileでも解凍出来ないとかであれば、
良かったんですけど。


下記、大いに参考にさせて頂きました。
JavaSE ZIP圧縮/解凍 Java7 - @//メモ

JAX-RSとDI

JAX-RSの環境でDIを実現をしようとした場合、
JAX-RS2.0を前提として、組み合わせとしては

1.Jersey + CDI
  JavaEE環境でそのまま使える。GlassFishとか。
2.Jersey + Spring
  JerseyのDocumentにも記述があるので、JAX-RS2.0でも使えそう。
3.Jersey + Guice
  jersey-guiceがJersey2.0に対応してない...(GuiceContainerがJersey1.0に依存?)
4.Jerseyのみ
  RequestとResponceのインターセプタで代用。(適用箇所がリソースに限定される)

1→Tomcatで動かそうとするも挫折...
2→試さず。Guiceが使いたかった。
3→いい感じだったが、2.0に対応出来ず。
4→割り切ればいいと思ったが、割り切れず。

3に関しては、実現してる方もいるようですが、
ちょっとプロジェクトとして荷が勝ち過ぎる印象を受けました。

それなりにネットを彷徨いましたが、
JAX-RS2.0はリリースされてから、まだ日が浅く情報が少ない(新しい)です。
だもんで、まずは実績の多いJersey1.18+Guice3.0でやってみたいと思います。
Client APIとか1と2で結構違うので、出来れば2が良かったんですが...

Gradle Jetty から Maven Tomcat

Gradle使う時は、手っ取り早いんで
開発サーバとしてはJetty使うのが主流でしょうか。
ただ私の場合は、Jetty使うのを断念しました。
前提がEclipseを開発環境とした場合ですが、
理由として、

gradle jettyRunで起動するJettyが使いづらい

 ・EclipseからGradleタスクの実行では、デバッグオプションが付加出来ない?
 ・GradleのEclipseプラグインにはRunDebugがない為、Eclipseでリモートデバッグの構成が必要になる。
  Jetty停止→Jetty起動→リモートデバッグ起動(この一手間がそこそこ苦痛)
 Jetty と grunt で Java でもサクサク Web 開発 - Qiita
 こちらの方のように組み込みJettyを自前で起動してしまえば、
 上の問題は解決しそうですが、本番がTomcatなので、
 開発と本番のアプリケーションサーバを同じにして、
 スムーズに開発したいとの思いが強かったです。

GradleにもTomcatプラグインありますが、使う上での手順はJettyとあまり変わりません(たぶん)

まぁ、とは言え流れはMaven→GradleなのでGradleは使いたい。
という事で、
ビルド=Gradle
開発サーバ=maven-tomcat-plugin
でやるようにしました。
build.gradleを触る回数は限られるので。

build.gradle編集→pom吐き出す→pomにmaven-tomcat-plugin追加→mvn tomcat7:runでtomcat起動。

てな感じです。
Gladleからpomを吐くのこんなんで出来ます。素晴らしい。

task writePom << {
    pom {
        groupId = 'my-group'
        packaging = 'war'
    }.writeTo('pom.xml')
}

Gradleいいんですけど、
現時点では情報、プラグインの量がMavenのほうが多い為、
ケースによって使い分けたほうがいいですね。

yeomanインストール

yeomanインストールしたいんですが、
残念ながら私のPCはWindows7...

どしよかーと調べてたら、あるんですね。こんなのが。
Windowsアプリをコマンドラインからインストールする「Chocolatey」 (1) PowerShell上で動作するNuGet | マイナビニュース
http://decodize.com/css/installing-yeoman-front-end-development-stack-windows/

各パッケージのインストール先

chocolately自体はC直下でもよいのですが、
各パッケージがC直下に入るのは、正直困ります。
ProgramFilesに入れるか別に入れるかは、好みにもよると思いますが。
これは、前もって設定しておいたほうがいいです。
GitHub - chocolatey-archive/chocolatey: [DEPRECATED - https://github.com/chocolatey/choco] Chocolatey NuGet - Like apt-get, but for windows.
Chocolateyのセットアップ - bakemoji |> log
システム環境変数のchocolatey_bin_rootを指定したいフォルダ名にする(ドライブ名入れない)
事で変更出来ましたが、
変更出来るインストール先は、おそらくC直下にしか変更出来ないと思います。
もっと色々試せばよかったのですが、インストール、アンインストールに疲れました...
ただ、この設定でもlibjpeg-turbo64は、C直下にインストールされてしまいました。

ここで1つ、chocolately経由でyeomanインストールすると、
Ruby, Compass, Python, Git, PhantomJS, Node.js
がガーッとインストールされますが、Windowsの場合Nodeだけは
個別にインストールしたほうがいいと思いました。
chocolatelyでインストールされるNodeはV.0.8.?ですが、最新はv0.10.24です。
V.0.8だとひな形を作成するyo webappのコマンドが
Object # has no method 'tmpdir'
でエラーとなってしまい、v0.10だとうまくいきました。

トータルの手順は、
・cinst yeoman
 でガーッとインストール。
環境変数を確認。以下がパスに通っているか。
 Ruby, Compass, Python, Git, PhantomJS
・Nodeをアンインストール、v0.10をインストール。
・yeoman initを使わずに、yo, grunt, bowerをnpm経由で個別にインストール。
 npm install -g yo
 npm install -g bower
 npm install -g grunt
 npm install -g grunt-cli 
 これらは、C:\Users\ユーザ名\AppData\Roaming\npm\node_modules
 にインストールされるので、C:\Users\nosa\AppData\Roaming\npmをパスに通す。
・generatorをインストール。
 npm install -g generator-webapp
・インストールしたgeneratorからひな形作成。
 yo webapp
・gruntで確認。
 grunt server
でいけました。(途中なんどもやり直してるので、確証はありませんが)

Windowsでyeoman(yo + grunt + bower)を使えるようにするには、
v0.9.6→v1.0.0で大きく変わってる事。そもそもLinuxベースのツールである事。
なので、個人的にはNodeベースのツールであることからも
Nodeをちゃんと使えるようにして、npmで
yo, grunt, bower
をそれぞれインストールしたほうがいいと思います。

JAX-RS

時代はJAX-RSって事で。

最初悩んだのがAPサーバを何にするかでした。
当初は、開発Jetty、本番GlassFishでやろうかと考えてました。

1. web.xml
色々情報があって噛み砕けてませんが、
GlassFish→web.xml不要
Jetty,Tomcat→web.xml必要(不要にも出来るらしい)
開発と本番でベースの設定が異なるのは避けたい。

2. デバッグ
Jettyのデバッグ環境をセットアップしていく中で、
gradle jettyRun
で起動しておき、Eclipseでリモートデバッグ
これがそこそこ苦痛。これまでmaven-tomcat-pluginで
Eclipse内でTomcatの起動、デバッグがシームレスに行えていたので、
デバッグまでの手順と設定が多い事がネック。
Jetty組み込みも考えたが、シンプルさに欠ける感じ。

3.その他
・Gradleは今後の為にも使っておきたい。
JAX-RSは是非使いたいが、JavaEEマストではない。
GlassFishの商用サポートが無くなった。

などなど考えて、
開発Jetty、本番Tomcat
にする事にしました。

開発は、あえてGradleを使う事を優先しました。
これまで通りMaven,Tomcatのほうが設定も少なく、
開発しやすいのは分かっているのですが、
「Gradleを使っておきたい」が大きな理由です。
Gradle使うとなればJettyのほうが情報量が多いのでJetty採用。
デバッグの手間は我慢出来ないレベルではないかなと。後に楽なやり方を見出だせる事かも。
本番は、1の理由とGlassFishである絶対の理由が無いので、Tomcatで。

以上を踏まえて、Eclipse上でGradle、JAX-RSを使って、
Jettyで開発出来る環境を作ります。

JDK 1.7.0_45
Eclipse Standard 4.3.1
・Gradle 1.10(binをパスに通す)
それぞれダウンロードとインストール。

・Gradle IDEプラグイン追加
 http://dist.springsource.com/release/TOOLS/update/e4.3
 Eclipse Integration for Gradle
 EclipseからGradleプロジェクトが作成出来るようになる。
 ディレクトリ構成とか手動メンドイので。JavaQuickStartでプロジェクト作成。

・Jettyの起動
 EclipseのExternal ToolsでGradleのjettyRunタスクが実行出来ればよかったのですが、
 どうにもビルドが途中で止まる(8%から進まない)。Jetty自体は起動している。
 jettyStopタスクでも止まらない。他のタスクは動作する。
 External Toolsを諦め、jettyRunタスクの起動スクリプトで実行させる事としました。
 デバッグ起動する必要があるので、以下参考にデバッグモードで起動させます。
Struts 1.3 + Spring 2.5のサンプルアプリ(ついでにGradle化) - wadahiroの日記

set GRADLE_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=n
gradle jettyRun

・起動したJettyにリモートデバッグ
 EclipseのDebugからRemoteJavaApplicationで8787にアタッチ。
 URLからGET、ブレークポイントが止まる、ホットデプロイ出来る、OK。

・WARデプロイ
 gradle war
 で作成したWARをTomcatにデプロイ。起動、URLからGET、OK。

・build.gradle

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin : 'jetty'
apply plugin: 'war'

sourceCompatibility = 1.7
version = '1.0'

repositories {
    mavenCentral()
}

dependencies {
    compile 'javax.ws.rs:javax.ws.rs-api:2.0'
    compile 'org.glassfish.jersey.core:jersey-server:2.4'
    compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.4'
    testCompile 'junit:junit:4.11'
}

task wrapper(type: Wrapper) {
    gradleVersion = '1.10'
}

・web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
      id="WebApp_ID" version="3.0">
    <servlet>
        <servlet-name>ServletContainer</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>com.hoge.resources</param-value>
    </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>ServletContainer</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>
</web-app>

JAX-RS(Jersey)いい。これはいい。ブラウザが近く?に感じます。
Gradleは正直、現時点ではMavenのほうが色々やりやすい印象です。
今後に期待です。