元Web系エンジニアのごはんブログ

JavaとかKotlinとかのごはん関係のブログです。

IntelliJ IDEAとGradleとTomcatでHelloWorld

IntelliJ IDEAが学生・教員は無料で使えることがわかり早速ダウンロードしました。そこで、今回はGradleでプロジェクトを作成し、Tomcat上でServletを動かすところまでをやってみました。

今回実行した、環境は次のとおりです。

プロジェクトの作成

まず、プロジェクトを作成します。IDEAを起動し「Create Project」を選択します。

f:id:webarata3:20170416123050p:plain

次に、一覧から「Gradle」を選択し「Additional Libraries and Frameworks」から「Web」を選択します。

f:id:webarata3:20170416123905p:plain

プロジェクトの「GroupId」と「ArtifactId」を入力します。今回はそれぞれ「com.example」、「hello-world」と入力しました。

f:id:webarata3:20170416123957p:plain

Gradle等の設定になりますので、「Use gradle wrapper task configuration」を選択します。

f:id:webarata3:20170416124110p:plain

最後に、プロジェクト名とプロジェクトの場所を決定します。

f:id:webarata3:20170416124137p:plain

ここまででプロジェクトの雛形ができます。プロジェクト作成直後は、gradle wrapper関連のファイル(gradlew等)はないのですが、少し待つと生えてきます。

f:id:webarata3:20170416125209p:plain

Gradleの設定

最初にGradleで必要なライブラリーを設定します。build.gradleを開いて、次のように編集します。

group 'com.example'
version '1.0-SNAPSHOT'

task wrapper(type: Wrapper) {
  gradleVersion = '3.3'
  distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
}

apply plugin: 'war'

repositories {
    mavenCentral()
}

dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
    providedCompile 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.1'
    providedCompile 'javax.el:javax.el-api:3.0.0'
    testCompile group: 'junit', name: 'junit', version: '4.11'
}

追加するのは、providedCompileの3行です。

そして、GradleのViewを表示します。メニューから「View」→「Gradle」を選択します。

f:id:webarata3:20170416131520p:plain

次のような画面が表示されたら、「Reflesh all Gradle projects」アイコンを押します。

f:id:webarata3:20170416131702p:plain

そうすると、依存ライブラリーが解決されます。

f:id:webarata3:20170416131837p:plain

Servletの作成

デフォルトだとソースフォルダーが作成されていませんので、「src/main」フォルダーの中に「java」フォルダーを作成します。

f:id:webarata3:20170416125814p:plain

f:id:webarata3:20170416125429p:plain

適当なパッケージを作ります。ここでは、「com.example」としています。

f:id:webarata3:20170416125921p:plain

f:id:webarata3:20170416125945p:plain

次に作成したパッケージにHelloServletクラスを作成します。

f:id:webarata3:20170416130241p:plain

f:id:webarata3:20170416130426p:plain

HelloServletクラスは次のように作成します。

package com.example;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/helloWorld")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter pw = resp.getWriter();
        pw.println("HelloWorld");
        pw.flush();
        pw.close();
    }
}

Tomcatの準備

最後にTomcatを準備します。TomcatMacLinuxの場合にはtar.gz版をダウンロードするといいと思います。Tomcatは展開して適当なフォルダーに置いておきます。

続いて、ウインドウの右上の、実行設定のボタンを押し「Edlit Configurations...」を選択します。

f:id:webarata3:20170416133348p:plain

「+」を押して「Tomcat Server」→「Local」を選択します。

f:id:webarata3:20170416133715p:plain

表示された画面で、任意の「name」を設定し、「Application Server」の「Configure」ボタンを押します。

f:id:webarata3:20170416133947p:plain

参照ボタンを押して、Tomcatを展開したフォルダーを選択します。

f:id:webarata3:20170416134235p:plain

続いて、「Deployment」タブを選択し、「+(Add)」ボタンを押します。

f:id:webarata3:20170416134612p:plain

「+(Add)」ボタンを押した後、「Artifacts」を選択します。Artifactsはwarで固められた形式と、warに固めずファイルシステムとして展開されたもの(exploded)の2種類から選択できます。ここでは、warで固められたものを選択しています。

f:id:webarata3:20170416134555p:plain

実行するwarファイルを指定します。

f:id:webarata3:20170416134836p:plain

設定すると、コンテキストパスが設定できますので、適当に設定します。今回は「/example」としました。

f:id:webarata3:20170420191335p:plain

ここまでで準備完了です。「実行」ボタンを押すとTomcatが起動しデフォルトのブラウザが起動します。

ブラウザーのアドレスバーに「http://localhost:8080/exmple/helloWorld」と入力すると次のように表示されます。

f:id:webarata3:20170416135427p:plain

(今は)意外と問題なかった名前が日本語のファイル名のダウンロードのはなし

昔検証したこともあったのですが、現在アクティブなブラウザでは名前が日本語のファイルのダウンロードがどうなっているのか気になりました。

少し前までは、ユーザーエージェントによる分岐等をしないといけないような状態でした。

qiita.com

実際に昔からの方法や、新しい?方法等でいろいろ試した結果をまとめましたのでそれを紹介します。

確認に使用したファイル名は「あ①Ⅰ + 🐸.txt」(あ、まるいち、ローマ数字のいち、半角スペース、プラス記号、全角スペース、かえる)です。

動作確認した環境と結果は次のとおりです。

OS ブラウザ バージョン ①URLエンコードのみ ②RFC6266 filnename*=utf-8'' MIME B ④ISO-8859-1によるエンコード
macOS Safari 10.1
Chrome 57
Firefox 52
Windows 10 Edge 15
IE 11
Chrome 57
Firefox 52
Windows 7 IE 9 △(🐸は化ける) △(🐸は化ける)
Android 7.1.2 Chrome 57
Firefox 52

結論的に2017年4月7日現在サポートされているブラウザーは基本的に②のRFC6266の方法で実装すれば、日本語は文字化けしなさそうです。幾つかのパターンに分岐しないときちんと処理できないだろうと思っていたので、いささか拍子抜けしてしましました。

最後に、コードの主要な部分を紹介します。

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import javax.mail.internet.MimeUtility;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.EncoderException;
import org.apache.commons.codec.net.URLCodec;

public class ResponseUtil {
    public static String urlEncode(String fileName) {
        // Java標準では、半角スペースが+にエンコードされる
        // String encodeFileName = URLEncoder.encode(fileName, "UTF-8");
        // Commons Codecでは、半角スペースが+に、+が%2Bにエンコードされる
        URLCodec codec = new URLCodec("UTF-8");
        String encodeFileName = "";
        try {
            encodeFileName = codec.encode(fileName);
        } catch (EncoderException e) {
            // 来ないはず
            return "bad_fine_name";
        }
        return encodeFileName.replaceAll("\\+", "%20").replaceAll("%2B", "+");
    }

    // ①URLエンコードのみ
    public static void download1(File file, HttpServletResponse response) throws IOException {
        String fileName = file.getName();
        String encodeFileName = urlEncode(fileName);

        response.setContentType("application/octet-stream");
        response.setContentLength((int) file.length());
        response.setHeader("Content-Disposition", "attachment; filename=\"" + encodeFileName + "\"");
        IoUtil.copy(new FileInputStream(file), response.getOutputStream());
    }

    // ②RFC6266
    public static void download2(File file, HttpServletResponse response) throws IOException {
        String fileName = file.getName();
        String encodeFileName = urlEncode(fileName);

        response.setContentType("application/octet-stream");
        response.setContentLength((int) file.length());
        response.setHeader("Content-Disposition", "attachment; filename*=utf-8''" + encodeFileName);
        IoUtil.copy(new FileInputStream(file), response.getOutputStream());
    }

    // ③MIME B
    public static void download3(File file, HttpServletResponse response) throws IOException {
        String fileName = file.getName();

        response.setContentType("application/octet-stream");
        response.setContentLength((int) file.length());
        response.setHeader("Content-Disposition",
                "attachment; filename=\"" + MimeUtility.encodeWord(fileName, "UTF-8", "B") + "\"");
        IoUtil.copy(new FileInputStream(file), response.getOutputStream());
    }

    // ④ISO-8859-1によるエンコード
    public static void download4(File file, HttpServletResponse response) throws IOException {
        String fileName = file.getName();

        response.setContentType("application/octet-stream");
        response.setContentLength((int) file.length());
        response.addHeader("Content-Disposition",
                "attachment; filename=" + new String(fileName.getBytes(), "ISO-8859-1"));
        IoUtil.copy(new FileInputStream(file), response.getOutputStream());
    }

}

Gradleで次のライブラリーを使用しています。

compile 'commons-codec:commons-codec:1.10'
compile 'javax.mail:mail:1.4.7'

全体の動くサンプルはGitHubにあります。(GitHub - webarata3/FileDownload: Java Servletによる日本語ファイルのダウンロードのサンプルです

KotlinのlateinitでJavaFXが捗る

*最近*Kotlinに遅延初期化プロパティがあることを知りました。

taro.hatenablog.jp

遅延初期化プロパティはドキュメントから引用すると、

通常、非null型として宣言されたプロパティは、コンストラクタ内で初期化される必要がある。
しかし、これはかなり不便である。例えば、プロパティが依存オブジェクト (dependency injection 訳注:参考)により初期化されたり、ユニットテストのセットアップメソッドで初期化されたり。
この事例では、非nullイニシャライザをコンストラクタ内で提供することができないが、それでもクラス内の本体にあるプロパティを参照する際にnullチェックを避けたいであろう。
このような事例を処理するには、プロパティを lateinit 識別子でマークすることができる

http://qiita.com/dogwood008/items/6e8d3225ea9bb0fe3099#%E9%81%85%E5%BB%B6%E5%88%9D%E6%9C%9F%E5%8C%96%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3

これを使うことで、JavaFXコンポーネントを非nullとして扱えます。

まず、lateinitを使用しない場合は次のようなコードになります。

class SampleController : Initializable {
    @FXML
    var button: Button? = null

    @FXML
    var label: Label? = null

    @FXML
    fun onActionButton() {
        label!!.text = "テスト"
    }

    public override fun initialize(location: URL?, resources: ResourceBundle?) {
    }
}

コンストラクタ内でButtonオブジェクトを確実に初期化しなければならず、またコンストラクタ実行時の段階では値が定まらないため、null許容型にし、さらに値自体もnullで初期化しなければなりません。そのため、ボタンが押された時のonActionButtonメソッドで、label!!.text = "テスト"のように!!をつけてプロパティやメソッドを呼び出す必要がありました。

しかし、lateinitを使用すると、次のように書くことができます。

class SampleController : Initializable {
    @FXML
    lateinit var button: Button

    @FXML
    lateinit var label: Label

    @FXML
    fun onActionButton() {
        label.text = "テスト"
    }

    public override fun initialize(location: URL?, resources: ResourceBundle?) {
    }
}

このように!!を使わずにすっきりと書くことができます。

jQueryのファイルアップロードプラグインを作ってみた

JavaScriptの学習がてら、jQueryのファイルアップロードのプラグインを作ってみました。

世の中には、jQueryのファイルアップロードプラグインはすでに存在していますが、私が使いたい感じのものがないこともあり自作しました。

機能的には、次のようなものになります。

  • 複数ファイルのアップロード
  • アップロードの進捗状況の表示
  • ドラッグアンドドロップによるアップロード
  • アップロード途中での停止
  • アップロードしたファイルの削除

上記の機能はオプションによって変更できます。例えば、ドラッグアンドドロップが必要なければ使わないということができます。


https://camo.githubusercontent.com/5e2d82b7ec9863d31e64ce3508f4cca47a99e355/68747470733a2f2f7765626172617461332e6769746875622e696f2f6a51756572792d66696c652d75706c6f61642f696d6167652f66696c6575706c6f61642e676966

具体的なオプションに関しては、次のGitHubのREADMEをご覧ください

github.com

Kotlin用Apache POIのラッパーライブラリーKExcelAPI 0.2.0をリリースしました

Kotlin用Apache POIのラッパーライブラリーKExcelAPI0.2.0をリリースしました。

github.com

変更点は次のとおりです。

  • Workbook[sheetNo]でシートを取得できるようにしました
  • セルのインデックス([0, 1]、[2, 6])からセル名(A2、C7)を取得できるようにしました
  • toStrで数値を取得する場合に、小数点以下が0であれば小数点以下をなくすようにしました
  • その他多くのバグ修正

自分で使っていて、かなり多くのバグを見つけてテストが足りていなかったことを痛感しました。そのため、テストケースを見なおしました。

ダウンロードは、Release 0.2.0 · webarata3/KExcelAPI · GitHubか、次のMavenリポジトリから行えます。

repositories {
    maven { url 'https://raw.githubusercontent.com/webarata3/maven/master/repository' }
}

dependencies {
    compile 'link.arata.kexcelapi:kexcelapi:0.2.0'
}

予定

月1くらいで何かしら機能をつけてリリースできたらいいなと思っています。

MyBatisのEclipseプラグインを作った

MyBatisのEclipseプラグインを作りました。

github.com

といっても大げさなものではなく、MapperのJavaファイルから対応するXMLファイルへジャンプするだけのプラグインです。

それでも対応するXMLファイルがない場合には、テンプレートから自動生成しますので、少しは便利に使えると思います。

今回プラグインを作ったのは、S2DaoプラグインJavaソースからSQLファイルへジャンプする機能が便利だったため、同様の機能をMyBatisでもやりたいと思ったためです。

プラグイン開発そのものが初めてということもあり、かなりいろいろなところでハマりました。

Web上で質問するのも初めての経験でした。

ja.stackoverflow.com

最終的に次の本のプラグイン開発の説明がドンピシャで役に立ちました。

JavaデベロッパーのためのEclipse完全攻略 [4.x対応版]

JavaデベロッパーのためのEclipse完全攻略 [4.x対応版]

他にもいろいろと機能を付け加えたいのですが、いかんせんプラグインの知識そのものが乏しいため、遅々として進まない状態です。それでも最低限の機能は作れたため、後はゆっくりやっていこうと思います。

MyBatisを使っている場合に、良ければ使ってみてください。

KotlinでQRコードでExcelで

KotlinでExcel方眼紙にQRコードを吐き出してみました。もちろんKExcelAPIを使います。


KotlinでExcelにQRコードを描く

吐き出した結果は次のような感じです。

f:id:webarata3:20160605142434p:plain

これで急にQRコードを使いたくなってもExcelとKotlinさえあれば、すぐに用意できます。

えっ、画像で(ry