20191215のJavaに関する記事は10件です。

JavaでExcelデータの取り込み

はじめに

今回はApachePOIです。
Javaの処理の中にExcelで作られたデータ群を取り込む為に使いました。

ApachePOIとは

正式名称ApachePOI
WordやExcelといったMicrosoft Office形式のファイルを読み書きできるJavaライブラリ
※Wiki参照
https://ja.wikipedia.org/wiki/Apache_POI

ここから以下のjarファイルをダウンロードしました。
https://poi.apache.org/download.html

poi-4.1.1.jar
poi-ooxml-4.1.1.jar
poi-ooxml-schemas-4.1.1.jar
commons-collections4-4.4.jar
commons-compress-1.19.jar
xmlbeans-3.1.0.jar

作った処理

対象のExcelファイルの1シート目、A列をJavaに取り込む処理を作ってみました。

SamplePOI.java
public class SamplePOI {

    /*
     * 本処理
     */
    public static void main(String[] args) {
        //ここに取り込みたいExcelファイルのフルパスを入れる
        String ExcelPath = "";

        //Excel用オブジェクト
        Workbook wb;
        Sheet sh;
        Row row;
        Cell cell;

        //取得データを保持するリスト
        List<String> columnA_List = new ArrayList<>();

        try (InputStream is = new FileInputStream(ExcelPath)) {

            //対象のExcelファイルをJavaに取り込み
            wb = WorkbookFactory.create(is);
            //対象ファイルの1枚目のシートを指定
            sh = wb.getSheetAt(0);
            //シート内の最大行を取得
            int rowMaxA = sh.getLastRowNum();

            //最大行分のループを回してA列のセルをString型として取得
            for (int i = 0; i <= rowMaxA; i++) {
                row = sh.getRow(i);
                cell = row.getCell(0);
                String cellValue = getCellStringValue(cell);
                columnA_List.add(cellValue);
            }

            //コンソールに出力
            System.out.print("[");
            for (String outStr : columnA_List) {
                System.out.print(outStr);
            }
            System.out.print("]");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*
     * セルの状態を判別してString型で返す。
     */
    private static String getCellStringValue(Cell cell) {
        String retStr;
        CellType cellType = cell.getCellType();
        switch (cellType) {
        case STRING:
            retStr = cell.getStringCellValue();
            break;
        case NUMERIC:
            retStr = String.valueOf(cell.getNumericCellValue());
            break;
        case BOOLEAN:
            retStr = String.valueOf(cell.getBooleanCellValue());
            break;
        case FORMULA:
            retStr = String.valueOf(cell.getCellFormula());
            break;
        case ERROR:
            retStr = String.valueOf(cell.getErrorCellValue());
            break;
        default:
            retStr = "";
            break;
        }
        return retStr;
    }
}

実行してみる

次のひらがなを入れたExcelファイルを読み込ませて実行してみると、
「あかさたなはまやらわ」が出力される。
image.png

samplePOI.result
[あかさたなはまやらわ]

まとめ

簡単な処理しか行っていないが感覚的にはVBAと同じだなと感じました。
セルの中身を取得する際にはセルタイプを考慮しないといけないという所は少し理解に苦戦を強いられた。

Excelで関数を入れて取り込もうとすると、計算結果じゃなくて関数そのものが値として取得されるっぽい?
ので結果が欲しい場合は少し大変そう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Getting Started with Gradle on Heroku をやってみた

動機

私は現在、TitterやLINEでbotを作り、特定の界隈向けに情報を発信しています。これらは既存のプラットフォームにのっかるだけなので比較的簡単に公開まで持って行くことができます。
しかし、これらの方法での発信では物足りなくなってきました。そこで、webサービスを作ろうと考えました。
完全に趣味としてやっているので、可能な限り無料でやろうということでHerokuに目を付けました。
というわけで今回はHeroku公式にあるチュートリアルをやっていこうと思います。
この記事はほぼチュートリアルの和訳みたいになってしまいましたがご了承ください。

準備

最初に見つけたのはGetting Started on Heroku with Javaというページだったのですが、こちらはMavenを使用するようです。
このページの中で「MavenではなくGradleを使う人はこっちをみてね。」とあったので、今回はそちらをやっていきます。
※私個人がGradleの方が馴染みがあるため。

環境

今回使用するPCの環境をざっくりと。

  • OS Windows10 Home 64bit
  • Java 11
  • Git Bashは導入済み

チュートリアル目次

Introduction

こちらには今回のチュートリアルの前提条件が記載してあります。

  • Herokuのアカウントを持っていること
  • Java8が導入されていること

Javaのバージョン指定が8となっていましたが、私の環境では11でも問題ありませんでした。

Set up

ここではHeroku Command Line Interface (CLI)を導入します。
(以前はHeroku Toolbeltという名前だったようです。)
CLIはアプリケーションの管理とスケーリング、アドオンのプロビジョニング、Herokuで実行されるアプリケーションのログの表示、アプリケーションのローカル実行に使用するようです。
自分の環境にあったインストーラを選択してダウンロードします。

インストールが終わるとherokuコマンドが使えるようになります。heroku loginコマンドでHeroku CLIへログインしましょう。

herokuへのログイン
$ heroku login
heroku: Press any key to open up the browser to login or q to exit
# ここでキーを押すとブラウザで画面が立ち上がる
# 画面にあるログインボタンを押すとログインできる
 ›   Warning: If browser does not open, visit
 ›   https://cli-auth.heroku.com/auth/browser/***
heroku: Waiting for login...
Logging in... done
Logged in as me@example.com

proxy設定ができるようですが、今回は必要ないので省略します。
参考:CLI Usage / Using an HTTP proxy

Prepare the app

用意されているサンプル用プロジェクトを任意のディレクトリにクローンします。

プロジェクトをクローン
$ git clone https://github.com/heroku/gradle-getting-started.git
$ cd gradle-getting-started

Deploy the app

先ほどクローンしたプロジェクトをデプロイしていきます。
まず、Herokuにアプリケーションを作成しHerokuでソースコードを受け取れるようにします。

アプリケーションを作成
$ heroku create
Creating app... done, arcane-inlet-19935
https://arcane-inlet-19935.herokuapp.com/ | https://git.heroku.com/arcane-inlet-19935.git

arcane-inlet-19935というのはアプリケーション名でランダムに命名されたものです。アプリケーション名は指定することもできるようです。このアプリケーション名はURLの一部になります。今回はチュートリアルなので特に指定しませんでした。

herokuというリポジトリへpushします。

$ git push heroku master

アプリケーションが動いていることを確認します。

$ heroku ps:scale web=1
Scaling dynos... done, now running web at 1:Free

下記コマンドをを打つとブラウザが立ち上がり、ページを開いてくれます。

$ heroku open

URLを確認すると、https://{アプリケーション名}.herokuapp.comとなっています。

View logs

Herokuのログはすべてのアカウント、Herokuコンポーネント共通で単一の出力ストリームに時系列順でログを出力しています。

ログ出力
$ heroku logs --tail
2019-12-15T06:22:53.476464+00:00 app[web.1]: 2019-12-15 06:22:53.476  INFO 4 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page template: index
2019-12-15T06:22:54.441609+00:00 heroku[web.1]: State changed from starting to up
2019-12-15T06:22:54.408814+00:00 app[web.1]: 2019-12-15 06:22:54.405  INFO 4 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 37878 (http) with context path ''
2019-12-15T06:22:54.452349+00:00 app[web.1]: 2019-12-15 06:22:54.452  INFO 4 --- [           main] com.example.heroku.HerokuApplication     : Started HerokuApplication in 12.317 seconds (JVM running for 14.742)
2019-12-15T06:32:15.384939+00:00 app[web.1]: 2019-12-15 06:32:15.382  INFO 4 --- [io-37878-exec-8] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-12-15T06:32:15.384963+00:00 app[web.1]: 2019-12-15 06:32:15.382  INFO 4 --- [io-37878-exec-8] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-12-15T06:32:15.412236+00:00 app[web.1]: 2019-12-15 06:32:15.412  INFO 4 --- [io-37878-exec-8] o.s.web.servlet.DispatcherServlet        : Completed initialization in 29 ms

--tailはリアルタイムで表示するというオプションです。起動確認時に訪れたページに再度アクセスすると、そのログが流れていく様子を見ることができます。
Ctrl + Cでリアルタイム表示を終了できます。
ログの出し方のオプションは他にもいろいろ用意されています。
ログの出し方:https://devcenter.heroku.com/articles/logging#view-logs

Define a Procfile

アプリケーションのルートディレクトリにあるProcfileでアプリケーションを起動するコマンドを明示的に宣言します。
サンプルプロジェクトのProcfileは次のようになっています。

Procfile
web: java -jar build/libs/gradle-getting-started-1.0.jar

単一のプロセスタイプwebとをれを実行するために必要なコマンドが宣言されています。ここではwebという名前が重要な意味を持ちます。
プロセスタイプやProcfileについては下記を読んでください。
Procfileについて:https://devcenter.heroku.com/articles/procfile

Procfileに書かれたコマンドがそのままUNIX環境で実行されるので、Syntaxが重要になります。

Scale the app

現在、アプリケーションは単一のWeb dynoで実行されています。dynoというのはProcfileで指定されたコマンドを実行する小さなコンテナだと考えることができます。

psコマンドで実行されているdynoの数を確認できます。

$ heroku ps
Free dyno hours quota remaining this month: 949h 54m (94%)
Free dyno usage for this app: 0h 0m (0%)
For more information on dyno sleeping and how to upgrade, see:
https://devcenter.heroku.com/articles/dyno-sleeping

=== web (Free): java -jar build/libs/gradle-getting-started-1.0.jar (1)
web.1: up 2019/02/13 11:10:03 -0600 (~ 13s ago)

この例ではweb.1が動いていることを確認できます。

この辺りは課金の話になりますが、dynoについてはこのように書かれています。

  • デフォルトではFreeのdynoにデプロイされる。
  • Freeのdynoは30分間操作しない(トラフィックを受信しない)とスリープ状態になる。
  • スリープ時にリクエストを受けると起動のために数秒の遅延が起きる。
  • アカウント単位で月ごとにFreeのdynoの起動時間の無料枠があり、それを消費しきるとFreeのdynoにデプロイされているアプリケーションは月末までスリープ状態のままになってしまう。

詳細はこちらに書いてあります。

Declare app dependencies

Herokuはルートディレクトリにgradlewまたはbuild.gradleがあることでGradleアプリであることを認識しています。

サンプルプロジェクトにはbuild.gradleが入っています。

build.gradle(抜粋)
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    runtimeOnly 'org.postgresql:postgresql'
    runtimeOnly 'org.webjars:jquery:3.3.1-1'
    runtimeOnly 'org.webjars:jquery-ui:1.12.1'
    runtimeOnly 'org.webjars:bootstrap:4.1.3'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Javaのバージョンはルートディレクトリにあるsystem.propertiesに記載します。

system.properties
java.runtime.version=1.8

Introductionで書きましたが、このチュートリアルはJava8を前提としているため1.8となっています。

それではbuildコマンドを実行しましょう。

$ ./gradlew build

Run the app locally

続いてローカル環境での実行です。

$ heroku local web

Heroku上で起動するのと同様にProcfileで実行するものを決定します。ポートの指定はsrc/main/resources/application.propertiesに記載します。

application.properties
server.port=${PORT:5000}

heroku localコマンドはアプリケーションの実行だけでなくconfig varsのセットもできます。(後述)

Push local changes

このセクションではローカルの変更をHerokuへ反映する手順を説明します。
例として以下のようにサンプルに変更を加えていきます。

build.gradleの30行目
compile "org.jscience:jscience:4.3.1"
src/main/java/com/example/heroku/HerokuApplication.java
// 19行目
import static javax.measure.unit.SI.KILOGRAM;
import javax.measure.quantity.Mass;
import org.jscience.physics.model.RelativisticModel;
import org.jscience.physics.amount.Amount;

// 中略

// 59行目
  @RequestMapping("/hello")
  String hello(Map<String, Object> model) {
      RelativisticModel.select();
      Amount<Mass> m = Amount.valueOf("12 GeV").to(KILOGRAM);
      model.put("science", "E=mc^2: 12 GeV = " + m.toString());
      return "hello";
  }
src/main/resources/templates/hello.htmlを作成
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{fragments/layout :: layout (~{::body},'hello')}">
<body>
  <div class="container">
    <p th:text="${science}"/>
  </div>
</body>
</html>

変更を終えたら再度ビルドし、アプリケーションを実行します。

$ ./gradlew build
...
BUILD SUCCESSFUL in 41s
5 actionable tasks: 5 executed

$ heroku local web
...
17:48:20 web.1   |  2019-12-15 17:48:20.865  INFO 14244 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 9409 ms
17:48:23 web.1   |  2019-12-15 17:48:23.462  INFO 14244 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
17:48:25 web.1   |  2019-12-15 17:48:25.429  INFO 14244 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page template: index
17:48:26 web.1   |  2019-12-15 17:48:26.397  INFO 14244 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 5000 (http) with context path ''

起動したらhttp://localhost:5000/helloにアクセスします。

E=mc^2: 12 GeV = (2.139194076302506E-26 ± 1.4E-42) kg

という何か物理に関する数式のようなものが出ればOKです。この計算をするためにdependenciesimportで追加したライブラリを使っているようです。
jscienceなんてライブラリがあるんですね…)

確認出来たらデプロイしていきます。

$ git add .
$ git commit -m "Demo"
$ git push heroku master

デプロイできたことを確認します。

$ heroku open

デモのトップ画面が開いたらURLの末尾に/helloを付けると、先ほどの数式のが表示されるはずです。

Provision add-ons

Herokuには様々なサードパーティアドオンがあります。今回はPapertrailというログ用アドオンを追加します。
Pepertrailを入れるとブラウザ上でログを確認することができます。

$ heroku addons:create papertrail
Creating papertrail on mysterious-wildwood-86066... !
 !    Please verify your account to install this add-on plan (please enter a
 !    credit card) For more information, see
 !    https://devcenter.heroku.com/categories/billing Verify now at
 !    https://heroku.com/verify

どうやらアドオンを追加するにはクレジットカードの登録が必要なようです。クレジットカードを登録するとFree dyno時間(いわゆる無料枠)が400h/月から1000h/月に拡大されるので私は登録しました。

では改めて

$ heroku addons:create papertrail
Creating papertrail on mysterious-wildwood-86066... free
Welcome to Papertrail. Questions and ideas are welcome (support@papertrailapp.com). Happy logging!
Created papertrail-curly-70913 as PAPERTRAIL_API_TOKEN
Use heroku addons:docs papertrail to view documentation

無事に追加できました。追加されているアドオンは以下のようにして確認することができます。

$ heroku addons

Add-on                               Plan     Price  State
───────────────────────────────────  ───────  ─────  ───────
papertrail (papertrail-curly-70913)  choklad  free   created
 └─ as PAPERTRAIL

The table above shows add-ons and the attachments to the current app (mysterious-wildwood-86066) or other apps.

それでは下記のコマンドでログを見てみましょう。

$ heroku addons:open papertrail

ブラウザでログが表示されたページが立ち上がります。

Start a one-off dyno

one-off dynoというのはHeroku上でコマンドを一度限りで実行するもののようです。one-off dynoではheroku runというコマンドを用います。

例)Javaのバージョンを確認する
$ heroku run java -version
Running java -version on arcane-inlet-19935... up, run.6561 (Free)
openjdk version "1.8.0_201-heroku"
OpenJDK Runtime Environment (build 1.8.0_201-heroku-b09)
OpenJDK 64-Bit Server VM (build 25.201-b09, mixed mode)

ほかにもローカル端末に接続されたREPLプロセスを起動してアプリの環境で実験したり、アプリケーションと共にデプロイしたコードを起動したりすることもできます。

Define config vars

Herokuでは、暗号化キーや外部リソースアドレスなどのデータをconfig varsに保存して、構成を外部化できます。config varsは実行時に環境変数としてアプリケーションに公開されます。
例として先ほどサンプルに追加したHerokuApplication.javahelloメソッドがENERGY環境変数からエネルギー値を取得するように変更してみます。

HerokuApplication.java
@RequestMapping("/hello")
String hello(Map<String, Object> model) {
    RelativisticModel.select();
// ここから追加
    String energy = System.getenv().get("ENERGY"); // ここで環境変数ENERGYを使用
    if (energy == null) {
       energy = "12 GeV";
    }
// ここまで
    Amount<Mass> m = Amount.valueOf(energy).to(KILOGRAM); // 変数energyを使用するように変更
    model.put("science", "E=mc^2: " + energy + " = "  + m.toString()); // 変数energyを使用するように変更
    return "hello";
}

最上位ディレクトリの.envファイルに環境変数を記述します。このファイルはheroku localコマンドの時に読み込まれるようです。

.env
ENERGY=20 GeV

ビルドし直してlocalで起動後http://localhost:5000/helloへアクセスすると数値が変わっていることが確認できます。

変更後の数式
E=mc^2: 20 GeV = (3.5653234605041761E-26 ± 2.9E-42) kg

確認できたところでHerokuにも同じ環境変数を設定しましょう。

$ heroku config:set ENERGY="20 GeV"
'C:\Program' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

???
とりあえずチュートリアルをさかのぼっていたらDefine a Procfileの項にこのように書いてありました。

When running locally under Windows, you may receive an error because the Unix way of passing in environment variables ($JAVA_OPTS) and of concatenating paths (:) is incompatible.

Unix環境では文字列結合の環境変数の渡し方とpathの連結の仕方に互換性がないことが原因のようです。
仕方がないのでHeroku上での確認はいったんここはスルーします。

Use a database

add-on maketplaceにはRedisやMongoDBからPostgres、MySQLまで数多くのデータストアがあります。まずはPostgreSQLアドオンの新しいインスタンスをアプリにアタッチしてみましょう。

$ heroku addons:create heroku-postgresql
Creating heroku-postgresql on mysterious-wildwood-86066... free
Database has been created and is available
 ! This database is empty. If upgrading, you can transfer
 ! data from another database with pg:copy
Created postgresql-amorphous-60925 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation

heroku addonsコマンドで見てみましょう。

$ heroku addons

Add-on                                          Plan       Price  State
──────────────────────────────────────────────  ─────────  ─────  ───────
heroku-postgresql (postgresql-amorphous-60925)  hobby-dev  free   created
 └─ as DATABASE

papertrail (papertrail-curly-70913)             choklad    free   created
 └─ as PAPERTRAIL

The table above shows add-ons and the attachments to the current app (mysterious-wildwood-86066) or other apps.

heroku-postgresqlが追加されています。

続いて、環境変数を見てみましょう。

$ heroku config
=== arcane-inlet-19935 Config Vars
DATABASE_URL:         postgres://pccigjsjmqxlvk:b3aedb1e099aa729d8037c0be6454e1028dc0c7c4dc16f8b366471486a8a7014@ec2-54-235-159-101.compute-1.amazonaws.com:5432/db2b5rodiinvv4
PAPERTRAIL_API_TOKEN: duMLEm2E5WHtf7mC163g

DATABASE_URLという変数が追加されています。
heroku にはpgというコマンドも用意されています。

$ heroku pg
=== DATABASE_URL
Plan:                  Hobby-dev
Status:                Available
Connections:           10/20
PG Version:            11.6
Created:               2019-12-15 10:26 UTC
Data Size:             7.7 MB
Tables:                0
Rows:                  0/10000 (In compliance) - refreshing
Fork/Follow:           Unsupported
Rollback:              Unsupported
Continuous Protection: Off
Add-on:                postgresql-amorphous-60925

これをみるとHobby-dev(=無料プラン)であり、PGのバージョンが11.6であることがわかりました。
これまでの手順でデプロイ済みのアプリケーションには既にデータベースの機能が含まれています。
https://{アプリケーション名}.herokuapp.com/dbにアクセスするとアクセスした日時が表示されます。(北米リージョンなので日本時間の9時間前)

HerokuApplication.java(抜粋)
  @Value("${spring.datasource.url}")
  private String dbUrl;

  @Autowired
  private DataSource dataSource;

  @RequestMapping("/db")
  String db(Map<String, Object> model) {
    try (Connection connection = dataSource.getConnection()) {
      Statement stmt = connection.createStatement();
      stmt.executeUpdate("CREATE TABLE IF NOT EXISTS ticks (tick timestamp)");
      stmt.executeUpdate("INSERT INTO ticks VALUES (now())");
      ResultSet rs = stmt.executeQuery("SELECT tick FROM ticks");

      ArrayList<String> output = new ArrayList<String>();
      while (rs.next()) {
        output.add("Read from DB: " + rs.getTimestamp("tick"));
      }

      model.put("records", output);
      return "db";
    } catch (Exception e) {
      model.put("message", e.getMessage());
      return "error";
    }
  }

  @Bean
  public DataSource dataSource() throws SQLException {
    if (dbUrl == null || dbUrl.isEmpty()) {
      return new HikariDataSource();
    } else {
      HikariConfig config = new HikariConfig();
      config.setJdbcUrl(dbUrl);
      return new HikariDataSource(config);
    }
  }

これにより、/dbにアクセスするたびにtickテーブルに現在時刻のレコードが追加され、これまでのアクセス日時が一覧表示されます。

Next steps

ここまででデプロイの仕方、設定変更、ログチェック、アドオンの追加ができるようになりました。
次に学習するのにおすすめの記事がいくつか挙げられています。

終わりに

Herokuの基本的なところはこれで押さえられたかなと思います。Herokuはアクセスしなければ30分でスリープ状態になり無料枠を消費しなくなるので、お試しでいくつもアプリケーションを作った後にわざわざ消したりしなくてもよいのが地味にうれしいところかなと思います。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

特定の日付との差が何年何か月かを表示

社内システムである特定の日付と現在の日付との差を何年何か月で表示する必要があったので、調べてみた。

  • Java

あまり使ってこなかった java.time を使用。
今回は時間までは必要ないため、LocalDateTimeではなく、LocalDateを使用。

LocalDate sampleDate = LocalDate.parse("2019/12/09", DateTimeFormatter.ofPattern("yyyy/MM/dd"));
LocalDate currentDate = LocalDate.now();
// 特定の日付と本日の差を年形式で取得
//long year = ChronoUnit.YEARS.between(sampleDate , currentDate); 
// 特定の日付と本日の差を月形式で取得 ※間違い。トータル月数が取得される
//long month = ChronoUnit.MONTHS.between(sampleDate , currentDate);

// コメントいただいた方法で修正
Period period = Period.between(sampleDate , currentDate);
// 特定の日付と本日の差を年形式で取得
long year = period.getYears();
// 特定の日付と本日の差を月形式で取得
long month = period.getMonths();

上記で取得した年と月を文字列にして画面に表示。

  • PHP

ついでにPHPも最近使ったので覚書。
PHPは独学なのでやり方間違っているかも。

$sampleDate = new DateTime('2019/12/09');
$currentDate = new DateTime('now');
$diffDate = $currentDate ->diff($sampleDate);
$year = $diffDate->format('%y');
$month = $diffDate->format('%m');

終わり!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フォームへSLEEP値を入力してタイムアウトをテストするアプリ

はじめに

Webフォームへ値を入力し送信ボタンを押すと、その秒数SLEEPして応答を返すという単純なJavaのサンプルコードです。タイムアウト値をテストする際に使ったものです。

サーブレット配置

servlet
servlet
 +WEB-INF
 | +classes
 | | +RequestSample1.class
 | +web.xml
 +formsample.html

formsample.html

formsample.html
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" Content="text/html;charset=Shift_JIS">
<title>sleep_sample</title>
</head>
<body>
<p>sleep_sample</p>
<form action="/sample/RequestSample1" method="get">
<table>
<tr>
<td>SLEEP(秒)</td>
<td><input type="text" size="3" value="" name="sleep"></td>
</tr>
</table>
<input type="submit" name="button1" value="送信">
</form>
</body>
</html>

web.xml

web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
   http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
  version="2.4">
  <servlet>
    <servlet-name>RequestSample1</servlet-name>
    <servlet-class>RequestSample1</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>RequestSample1</servlet-name>
    <url-pattern>/RequestSample1</url-pattern>
  </servlet-mapping>
</web-app>

RequestSample1.java

RequestSample1.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class RequestSample1 extends HttpServlet {
  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException{
    response.setContentType("text/html;charset=Shift_JIS");
    PrintWriter out = response.getWriter();
    String sleep_sec = request.getParameter("sleep");
    try {
     Thread.sleep(Long.parseLong(sleep_sec) * 1000);
    } catch (InterruptedException e) {
    }
    StringBuffer sb = new StringBuffer();
    sb.append("<html>");
    sb.append("<head>");
    sb.append("<title>sleep_sample</title>");
    sb.append("</head>");
    sb.append("<body>");
    sb.append("<p>");
    sb.append(sleep_sec);
    sb.append("秒SLEEPしました。</p>");
    sb.append("</body>");
    sb.append("</html>");
    out.println(new String(sb));
    out.close();
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Androidアプリ開発で国民の祝日を表示させるのに試行錯誤。前編

 どうも!ヨースケです。日記アプリもようやく1画面が終わったので、約10時間に及ぶ日帰りミニ旅をしていました。一眼レフでたくさん撮るぞーと意気込んでいましたが、ほとんど撮れませんでした...ただ疲れただけで、帰り道に前方後方不注意の車に二度轢かれかけるなどさんざんな一日でした(笑)。
 
 今回は日記アプリに取り入れたその日が国民の祝日ならば表示しタップするとその詳細をダイアログで表示する。というのを前編後編にわたって紹介します。

  • 国民の祝日
  • 日付が固定されている祝日
  • 第〇月曜日の祝日
  • 春分と秋分
  • 振替休日

国民の祝日

 まず、日本にある国民の休日を記してみます。
元日、成人の日、建国記念の日、天皇誕生日、春分の日、昭和の日、憲法記念日、みどりの日、こどもの日、海の日、山の日、敬老の日、秋分の日、スポーツの日、文化の日、勤労感謝の日の16個あります。「体育の日」は2020年に「スポーツの日」と改められました。次はプログラムで重要になる日付の話をします。

日付が固定されている祝日

 プログラム的には楽な日付が初めから決められている祝日の一覧です。

祝日名 日付 備考
元旦 1月1日 -
建国記念の日 2月11日 -
天皇誕生日 2月23日 -
昭和の日 4月29日 -
憲法記念日 5月3日 -
みどりの日 5月4日 -
こどもの日 5月5日 -
山の日 8月11日 2020年は8月10日
文化の日 11月3日 -
勤労感謝の日 11月23日 -

第〇月曜日の祝日

祝日名 日付 備考
成人の日 1月の第2月曜日 -
海の日 7月の第3月曜日 2020年は7月23日
敬老の日 9月の第3月曜日 -
スポーツの日 10月の第2月曜日 2020年は7月24日

以上の表のようにオリンピック・パラリンピックがある2020年は祝日が動いてしまいますが、月と週と曜日が分かるので何とか判定できそうです。

春分と秋分

 この2つの祝日は厄介でした。まず、春分の日は3月の19日~22日うちの1日に、秋分の日は9月の22~24日のうちの1日になります。なぜ年によって日にが異なるのかと言うと地球の公転周期と関係があるようで詳しくはこちらをご覧ください(投げやり)。
 春分の日や秋分の日は逐一カレンダーを見ながら入れていかなければならないのか?と最初は肩を落としましたが、Wikipediaによれば西暦年を4で割ったあまりでその年の春分・秋分の日が決められるそうです。
春分の日:https://ja.wikipedia.org/wiki/%E6%98%A5%E5%88%86%E3%81%AE%E6%97%A5
秋分の日:https://ja.wikipedia.org/wiki/%E7%A7%8B%E5%88%86%E3%81%AE%E6%97%A5
 

振替休日

 振替休日は「国民の祝日の日曜日の翌日の月曜日以降の国民の祝日でない祝日の翌日」とちょっとややこしいですがそのように祝日法ではそう記されています。簡単に言えば日曜日が祝日なら次の平日が振替休日に充てられると考えた方がいいかもです。

 後編では考え方や実際書いたソースコードを載せて解説とは言い難いですが、説明をしていこうかなと思います。今週中には上げるのでよろしくお願い致します。

参考文献

祝日法:https://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html
国民の祝日:https://ja.wikipedia.org/wiki/%E5%9B%BD%E6%B0%91%E3%81%AE%E7%A5%9D%E6%97%A5

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Redmine REST APIでハマったことリスト

背景

今年 Redmine REST API を使って下記業務を実装する機会がありました。

  • 進捗管理をExcelからRedmineにmigration
  • CIのリグレッションテストでFailしたテストケースをRedmineに自動起票

目的

今まで Redmine REST API は使ったことがなく、実装しながら学んでいきました。その中でハマったことを中心に皆さんに共有して、僕のはハマった罠を回避して頂きたいと思います。

前提

  • javaをそこそこ理解している
  • Redmineの機能をそこそこ理解している

環境

Redmine Version
Environment:
  Redmine version                3.4.6.stable
java Version
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
pom.xml
        <dependency>
            <groupId>com.taskadapter</groupId>
            <artifactId>redmine-java-api</artifactId>
            <version>4.0.0.preview.1</version>
        </dependency>

ハマったこと

1. ユーザーに付与された権限にAPIの機能も制限される

Redmine REST APIを使うために最初に RedmineManagerクラスを生成します。このときに API KEYというものを使用します。

RedmineManager manager = RedmineManagerFactory.createWithApiKey(REDMINE_URL, API_KEY);

この API KEYはRedmineのユーザー一人一人に付与されます。当時、自分のユーザー権限は管理者権限ではありませんでした。APIのメソッドによってはRedmineのシステム管理者権限が必要なものがあります。

全ユーザーを取得
    /**
     * Load list of users from the server.
     * <p><strong>This operation requires "Redmine Administrator" permission.</strong>
     * <p>
     * This method calls Redmine with "include = memberships,groups" parameter.
     *
     * @return list of User objects
     * @throws RedmineAuthenticationException invalid or no API access key is used with the server, which
     *                                 requires authorization. Check the constructor arguments.
     * @throws NotFoundException
     * @throws RedmineException
     */
    public List<User> getUsers() throws RedmineException {
        return transport.getObjectsList(User.class, new BasicNameValuePair(
                "include", "memberships,groups"));
    }

柔軟に利用したい場合、システム管理者権限になることをお勧めします。

2. Transport が必要

redmine-java-apiのversion4以降から取り込まれた概念のようです。RedmineManagerクラスから取得できます。

RedmineManager redmineManager = RedmineManagerFactory.createWithApiKey(REDMINE_URL, API_KEY);
Transport transport = redmineManager.getTransport();

Transportがないとチケット作成・更新時に「Transportが未設定だぞ!」という例外がスローされます。なので作成・更新前に Issueのインスタンスに Transportを設定してあげてください。

新規チケット作成
// Transportをセット
Issue issue = new Issue(manager.getTransport());
// (中略)
issue = issue.create();
既存チケット更新
Issue issue = manager.getIssueManager().getIssueById(fetchedIssue.getTicketId());
// Transportをセット
issue.setTransport(manager.getTransport());
// (中略)
issue.update();

3. Paramsの使い方

既存チケットに更新をかけるとき、「この条件にあったチケット」を取得したいときがあります。そのときは Params を使うことで「この条件」を表現できます。

チケットの題名が"actual.getFileName"に一致するという条件
Params().add("set_filter", "1")
        .add("f[]", "subject")
        .add("op[subject]", "=")
        .add("v[subject][]", actual.getFileName());

ハマったことは下記3点です。

題名以外(例:担当者)ときのキー値は?

ドキュメントを見つけられなかったので、力技で特定してました。チケット一覧のフィルタで希望の条件を設定するとURLのパラメータとしてフィルタ条件が表示されます。パラメータをデコードするとキーの値を特定できます。
flow.png

一致以外(例:一致しない、含む)のときは?

上の方法で解析。

チケット全部取得したいから条件なしで取得したら500件しか取得できない

現状不明。方法をご存じの方がいたらご教示ください。

4. 既存チケットのカスタムフィールドを更新するときは一度clearする

一度既存チケットのカスタムフィールドを取得して、値を更新する。そのあとに既存チケットのカスタムフィールドを clearCustomFields してから追加しないとうまく更新されませんでした。

// この処理で既存チケットからカスタムフィールドを取得して更新して、返却している
Collection<CustomField> customFieldCollection = setEachCustomField(issue, fetchedIssue);
issue.clearCustomFields();
issue.addCustomFields(customFieldCollection);

5. 日付型のカスタムフィールドに値を設定するときのフォーマット

yyyy-MM-dd一択です。これ以外はparseエラーになります。

以上(思いついたら追加していきます。)

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Redmine REST APIでハマったこと

背景

今年 Redmine REST API を使って下記のようなことをする機会がありました。

  • 進捗管理をExcelからRedmineにmigration
  • CIのリグレッションテストでFailしたテストケースをRedmineに自動起票

目的

今まで Redmine REST API は使ったことがなく、実装しながら学んでいきました。その中でハマったことを中心に皆さんに共有して、僕のはハマった罠を回避して頂きたいと思います。

前提

  • javaをそこそこ理解している
  • Redmineの機能をそこそこ理解している

環境

Redmine Version
Environment:
  Redmine version                3.4.6.stable
java Version
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
pom.xml
        <dependency>
            <groupId>com.taskadapter</groupId>
            <artifactId>redmine-java-api</artifactId>
            <version>4.0.0.preview.1</version>
        </dependency>

ハマったこと

1. ユーザーに付与された権限にAPIの機能も制限される

Redmine REST APIを使うために最初に RedmineManagerクラスを生成します。このときに API KEYというものを使用します。

RedmineManager manager = RedmineManagerFactory.createWithApiKey(REDMINE_URL, API_KEY);

この API KEYはRedmineのユーザー一人一人に付与されます。当時、自分のユーザー権限は管理者権限ではありませんでした。APIのメソッドによってはRedmineのシステム管理者権限が必要なものがあります。

全ユーザーを取得
    /**
     * Load list of users from the server.
     * <p><strong>This operation requires "Redmine Administrator" permission.</strong>
     * <p>
     * This method calls Redmine with "include = memberships,groups" parameter.
     *
     * @return list of User objects
     * @throws RedmineAuthenticationException invalid or no API access key is used with the server, which
     *                                 requires authorization. Check the constructor arguments.
     * @throws NotFoundException
     * @throws RedmineException
     */
    public List<User> getUsers() throws RedmineException {
        return transport.getObjectsList(User.class, new BasicNameValuePair(
                "include", "memberships,groups"));
    }

柔軟に利用したい場合、システム管理者権限になることをお勧めします。

2. Transport が必要

redmine-java-apiのversion4以降から取り込まれた概念のようです。RedmineManagerクラスから取得できます。

RedmineManager redmineManager = RedmineManagerFactory.createWithApiKey(REDMINE_URL, API_KEY);
Transport transport = redmineManager.getTransport();

Transportがないとチケット作成・更新時に「Transportが未設定だぞ!」という例外がスローされます。なので作成・更新前に Issueのインスタンスに Transportを設定してあげてください。

新規チケット作成
// Transportをセット
Issue issue = new Issue(manager.getTransport());
// (中略)
issue = issue.create();
既存チケット更新
Issue issue = manager.getIssueManager().getIssueById(fetchedIssue.getTicketId());
// Transportをセット
issue.setTransport(manager.getTransport());
// (中略)
issue.update();

3. Paramsの使い方

既存チケットに更新をかけるとき、「この条件にあったチケット」を取得したいときがあります。そのときは Params を使うことで「この条件」を表現できます。

チケットの題名が"actual.getFileName"に一致するという条件
Params().add("set_filter", "1")
        .add("f[]", "subject")
        .add("op[subject]", "=")
        .add("v[subject][]", actual.getFileName());

ハマったことは下記3点です。

題名以外(例:担当者)ときのキー値は?

ドキュメントを見つけられなかったので、力技で特定してました。チケット一覧のフィルタで希望の条件を設定するとURLのパラメータとしてフィルタ条件が表示されます。パラメータをデコードするとキーの値を特定できます。
flow.png

一致以外(例:一致しない、含む)のときは?

上の方法で解析。

チケット全部取得したいから条件なしで取得したら500件しか取得できない

現状不明。方法をご存じの方がいたらご教示ください。

4. 既存チケットのカスタムフィールドを更新するときは一度clearする

一度既存チケットのカスタムフィールドを取得して、値を更新する。そのあとに既存チケットのカスタムフィールドを clearCustomFields してから追加しないとうまく更新されませんでした。

// この処理で既存チケットからカスタムフィールドを取得して更新して、返却している
Collection<CustomField> customFieldCollection = setEachCustomField(issue, fetchedIssue);
issue.clearCustomFields();
issue.addCustomFields(customFieldCollection);

5. 日付型のカスタムフィールドに値を設定するときのフォーマット

yyyy-MM-dd一択です。これ以外はparseエラーになります。

以上(思いついたら追加していきます。)

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Catalinaにjavaをインストールできない場合

javaをmacにインストールできない、、

macを買い替えたので、新たにjavaをインストールしようとしたときに問題が起こりました。
MacのOSはCatalina(10.15.1)です。

brew cask install java 

このスクリプトを実行してバージョンを確認しようとしたところ思わぬエラーが、、
スクリーンショット 2019-12-15 12.47.08.png
MacのOSがcatalinaになってからこのようなエラーが出ている人がちらほらいるみたいです。

解決策

解決策としては、oracle公式のインストーラを使ってインストールすることで事なきを得ました。
oracle公式にあるインストーラ(拡張子がdmgのほう)を使うことでこの問題を解決することができます。
バージョンも確認できました。

$ java -version
java version "13.0.1" 2019-10-15
Java(TM) SE Runtime Environment (build 13.0.1+9)
Java HotSpot(TM) 64-Bit Server VM (build 13.0.1+9, mixed mode, sharing)

注意点

oracle公式からmac用のインストーラが2つあるのですが、拡張子がtar.gzのほうを使うと同様のエラーが出てしまう可能性があります。
自分の環境ではエラーが出ました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaのenumにabstractなメソッドを定義してそれぞれの振る舞いを書く

概要

このエントリでは、Javaのenumにabstractなメソッドを定義してそれぞれの振る舞いを書くことについて取り扱います。
(EffectiveJava 3rd editionに書いてあるやつ、だいたいそのまんまです。自分でやってみた内容についてメモしておきます。)

Javaのenum

Javaのenumでは、値だけでなくメソッドを定義することができます。

値だけ定義したパターン

    enum Operator {
        PLUS, SUBTRACT, MULTIPLY, DIVIDE, NONE
    }

これに対し、値ごとにメソッドを定義し、下図のような書き方をすることができます。

    enum Operator {
        PLUS {
            @Override
            BigDecimal apply(@NonNull BigDecimal lhs, @NonNull BigDecimal rhs) {
                return lhs.add(rhs);
            }
        },
        SUBTRACT{
            @Override
            BigDecimal apply(@NonNull BigDecimal lhs, @NonNull BigDecimal rhs) {
                return lhs.subtract(rhs);
            }
        },
        MULTIPLY {
            @Override
            BigDecimal apply(@NonNull BigDecimal lhs, @NonNull BigDecimal rhs) {
                return lhs.multiply(rhs);
            }
        },
        DIVIDE {
            @Override
            BigDecimal apply(@NonNull BigDecimal lhs, @NonNull BigDecimal rhs) {
                return lhs.divide(rhs, BigDecimal.ROUND_UNNECESSARY);//;
            }
        },
        NONE {
            // allow null for rhs
            @Override
            BigDecimal apply(@NonNull BigDecimal lhs, BigDecimal rhs) {
                return lhs;
            }
        };

        abstract BigDecimal apply(BigDecimal lhs, BigDecimal rhs);
    }

enum内の「abstract BigDecimal apply(BigDecimal lhs, BigDecimal rhs);」ですべての値で定義すべきメソッドを指定しておき、それぞれの値の宣言時にメソッドをOverrideしています。

(補足)上のソースで、「@NonNull」は、lombokで提供されているアノテーションの一つで、Nullチェックを実装してくれるものです。

使用例

前述の形でenumを定義しておくと、例えば以下のようなメソッドが、

    public synchronized BigDecimal pushEvalButton() {
        var v = new BigDecimal(sb.toString());
        switch(currentOperator) {
            case PLUS: {
                v = stack.add(getCurrentValue());
                break;
            }
            case SUBTRACT: {
                  v = stack.subtract(getCurrentValue());
                  break;
            }
            case MULTIPLY: {
                  v = stack.multiply(getCurrentValue());
                  break;
            }
            case DIVIDE: {
                  v = stack.divide(getCurrentValue(), BigDecimal.ROUND_UNNECESSARY);//
                  break;
            }
            case NONE: {
                return v;
            }
            default: {
                throw new RuntimeException("Not defined.");
            }
        }
        currentOperator = Operator.NONE;
        replaceBuffer(v.toPlainString());
        clearStack();
        return v;
    }

下記のようにスッキリ書けます。

    public synchronized BigDecimal pushEvalButton() {
        var v = new BigDecimal(sb.toString());

        if(Operator.NONE == currentOperator) {
            return v;
        }
        v = currentOperator.apply(stack, getCurrentValue());
        currentOperator = Operator.NONE;
        replaceBuffer(v.toPlainString());
        clearStack();
        return v;
    }

enumのメソッドの方に内容が移動したので元のメソッドからコードが減るのは当然として、以下がメリットと感じました。
- switch内のdefaultを追い出せた
- ほかの場所でもenumのメソッドを使うことにより、処理が一か所にまとめられた

まとめ

このエントリでは、Javaのenumにabstractなメソッドを定義してそれぞれの振る舞いを書くことについて、例をあげて説明しました。

上記の差分例は、GitHubのこのcommitを参照ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

可変長引数

個人的な勉強のためまとめました

可変長引数

・引数の数を自由に変更できる引数
引数の型の直後ピリオドを3つ...」つけて宣言
・理論上は引数をいくつでも渡しても呼び出すことができる
・渡された複数の値は、JVMによって配列に置き換えられる。そのため、値を使うときは配列と同じように「[]」を使う

2つの注意点

①同じ型の数が可変な引数をまとめられるだけで、異なる型はまとめられない
②可変長引数以外の引数を受け取る必要がある場合、可変長引数は最後の引数にすること

①は、可変長引数が配列に置き換えられていることを考えれば当然
②は、例えば次のようなメソッドの宣言では、どこまでが第一引数で、どこからが第二引数かわからず、コンパイルエラーになる

例)可変長引数を最初に記述したメソッド(コンパイルエラー)

sample.java
void sample(int... num,int value) {
    //do something
}

次のように順番を入れ替えるとコンパイルエラーが発生しない

例)可変長引数を最後に記述したメソッド

sample.java
void sample(int value,int... num) {
    //do something
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む