つまりこれです。ずっととんがったデザイン(文字通りの意味)だったのですが、2023.3からとうとうMacOS標準のスタイルになりました。 ずっと待っていたので地味に嬉しいです。
TomcatをVirtualThreadで動作させる
前回の投稿から大分空いてしまいました、、、
本日は技術的なトピックを投稿してみます。
Java19になって仮想スレッド(かつてグリーンスレッドと呼ばれていたもの)が使えるようになりました。とはいえまだExperimentalですが。
JVMの起動パラメータに --enable-preview
と付ければ関連APIを実行できるようになります。
(※23-01-10追記) グリーンスレッドとvirtual threadは大きく異なるものでした。 M:Nモデルというモデルです。詳しくはこちら。 https://stackoverflow.com/questions/74639116/what-is-the-difference-between-green-threads-and-virtual-threads
そこで、Tomcatを仮想スレッドで動作させられないか調べてみたら案外簡単に動きました。
まず、このようなクラスを用意します。
package jp.docurain.tomcat; import java.util.concurrent.Executors; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; import org.apache.catalina.util.LifecycleMBeanBase; public final class VirtualThreadExecutor extends LifecycleMBeanBase implements org.apache.catalina.Executor { private java.util.concurrent.Executor es; private String name = "tomcat-vt"; private String namePrefix = "tomcat-vt-"; public void setName(final String x) { this.name = x; } @Override public String getName() { return this.name; } public void setNamePrefix(final String x) { this.namePrefix = x; } public String getNamePrefix() { return this.namePrefix; } @Override public void execute(final Runnable command) { this.es.execute(command); } @Override protected String getDomainInternal() { return null; } @Override protected String getObjectNameKeyProperties() { return "type=Executor,name=" + this.getName(); } @Override protected void startInternal() throws LifecycleException { this.es = Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name(this.namePrefix, 0).factory()); this.setState(LifecycleState.STARTING); } @Override protected void stopInternal() throws LifecycleException { this.setState(LifecycleState.STOPPING); } }
次に、このクラスをコンパイルしてjarにして、Tomcatのインストールディレクトリのlibへ放り込みます。
つぎにserver.xmlを以下のようにするだけ。簡単ですね。
<Server port="8005" shutdown="SHUTDOWN"> ...(略) <Service name="Catalina"> <!-- 上記でコンパイルしたクラス --> <Executor name="vthread" className="jp.docurain.tomcat.VirtualThreadExecutor" /> <!-- Executorのnameをexecutorへ指定 --> <Connector executor="vthread" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> </Service> ...(略) </Server>
で、起動して何かリクエストを投げてみると、これまでは
2022-11-27 16:47:32,099 INFO [...] http-nio-8080-exec-1 何かのログ
のようなログだったのが、
2022-11-27 16:47:32,099 INFO [...] tomcat-vt-1 何かのログ
のように変わっています。 起動中のスレッドを見てみても、TomcatのNioコネクター関連のスレッドが一つもありません。というわけで成功したようです。
実際に商用利用可能なレベルなのか、引き続き調査してみます。本日はここまで。
PDFマージ機能を使って精密な帳票を作成しましょう
Docurainの得意な帳票として、納品書や請求書はもちろんのこと、商業印刷にも耐えられるような厳密なサイズで決められた帳票作成も可能です。この記事ではそれを担うPDFマージ機能について解説します。
続きを読む明細行を出力する際に利用する配列操作関数について
帳票はヘッダー、明細そしてフッターで構成されています。ヘッダーやフッターは各帳票で一度しか出力しないので簡単ですが、明細行は次のような機能が必要でしょう。
- 明細データから指定した行数分のデータを取り出す
- 明細データを指定した行数ごとに分割する
この記事では帳票を作る際に必要な配列操作について解説します。
今回利用するデータについて
今回利用するデータは次のような内容であることとします。
{ "items": [ {"name": "商品1", "price": 100, "unit": 10}, {"name": "商品2", "price": 200, "unit": 20}, {"name": "商品3", "price": 300, "unit": 30}, {"name": "商品4", "price": 400, "unit": 40}, {"name": "商品5", "price": 500, "unit": 50}, {"name": "商品6", "price": 600, "unit": 60}, {"name": "商品7", "price": 700, "unit": 70}, {"name": "商品8", "price": 800, "unit": 80}, {"name": "商品9", "price": 900, "unit": 90}, {"name": "商品10", "price": 1000, "unit": 100}, {"name": "商品11", "price": 1100, "unit": 110}, {"name": "商品12", "price": 1200, "unit": 120}, {"name": "商品13", "price": 1300, "unit": 130}, {"name": "商品14", "price": 1400, "unit": 140} ] }
この items
キーが明細データになります。
1ページ4明細の帳票の場合
今回のデータは明細が14件ありますので、1ページ4明細の場合は次のような帳票になります。
- 全4ページ
- 4ページ目が2件
こうした帳票を作る場合は、まず items
キーの内容を次のように分割すれば良さそうです。
[ [ {"name": "商品1", "price": 100, "unit": 10}, {"name": "商品2", "price": 200, "unit": 20}, {"name": "商品3", "price": 300, "unit": 30}, {"name": "商品4", "price": 400, "unit": 40} ], [ {"name": "商品5", "price": 500, "unit": 50}, {"name": "商品6", "price": 600, "unit": 60}, {"name": "商品7", "price": 700, "unit": 70}, {"name": "商品8", "price": 800, "unit": 80} ], [ {"name": "商品9", "price": 900, "unit": 90}, {"name": "商品10", "price": 1000, "unit": 100}, {"name": "商品11", "price": 1100, "unit": 110}, {"name": "商品12", "price": 1200, "unit": 120} ], [ {"name": "商品13", "price": 1300, "unit": 130}, {"name": "商品14", "price": 1400, "unit": 140} ] ]
このようにデータを分割するメソッドが chunk
です。次のように実行します。
#set($pages = $ROOT.items.chunk(4))
この chunk
メソッドは指定したデータ件数(今回は4)ごとにデータを分割して、配列にします。つまり $pages
の内容は次のようになります。
[ [ {"name": "商品1", "price": 100, "unit": 10}, {"name": "商品2", "price": 200, "unit": 20}, {"name": "商品3", "price": 300, "unit": 30}, {"name": "商品4", "price": 400, "unit": 40} ], [ {"name": "商品5", "price": 500, "unit": 50}, {"name": "商品6", "price": 600, "unit": 60}, {"name": "商品7", "price": 700, "unit": 70}, {"name": "商品8", "price": 800, "unit": 80} ], [ {"name": "商品9", "price": 900, "unit": 90}, {"name": "商品10", "price": 1000, "unit": 100}, {"name": "商品11", "price": 1100, "unit": 110}, {"name": "商品12", "price": 1200, "unit": 120} ], [ {"name": "商品13", "price": 1300, "unit": 130}, {"name": "商品14", "price": 1400, "unit": 140} ] ]
後はこの pages
ごとにループ処理をすれば、ページごとの明細を出力できます。
#for ($page in $pages) ## ページ単位のループ #for ($item in $page) ## 明細ごとのループ #end #end
1ページ目が4件の明細、2ページ目以降は6件という帳票の場合
次は1ページ目を4件、2ページ目以降は6件の場合は、次のような帳票になります。明細データは先ほどと同じく14件とします。
- 全3ページ
- 1ページ目は4件、2ページ目は6件、3ページ目は4件
1ページ目用のデータを取得する
まず items
キーの中から4件のデータを取得します。この時には take
を使います。
#set($firstPage = $ROOT.items.take(4))
この時、$firstPage
は次のような内容になっています。
[ {"name": "商品1", "price": 100, "unit": 10}, {"name": "商品2", "price": 200, "unit": 20}, {"name": "商品3", "price": 300, "unit": 30}, {"name": "商品4", "price": 400, "unit": 40} ]
1ページ目のテンプレートを作り、そのまま出力を行います。
2ページ目以降のデータを取得する
次に2ページ目以降、データを6件ずつに分割します。この時に使うのが drop
です。 drop
は指定した数字(今回は4)分、データをスキップします。そして残りのデータについて chunk
で6件ずつに分割します。
#set($pages = $ROOT.items.drop(4).chunk(6))
この時の $pages
の内容は次のようになります。
[ [ {"name": "商品5", "price": 500, "unit": 50}, {"name": "商品6", "price": 600, "unit": 60}, {"name": "商品7", "price": 700, "unit": 70}, {"name": "商品8", "price": 800, "unit": 80}, {"name": "商品9", "price": 900, "unit": 90}, {"name": "商品10", "price": 1000, "unit": 100}, ], [ {"name": "商品11", "price": 1100, "unit": 110}, {"name": "商品12", "price": 1200, "unit": 120}, {"name": "商品13", "price": 1300, "unit": 130}, {"name": "商品14", "price": 1400, "unit": 140} ] ]
2ページ目以降のテンプレートを作り、出力します。
最後のページだけ出力内容が異なる場合
帳票において、最後のページだけ出力内容が異なるケースは良くあります。たとえば集計行の出力です。ループ処理の中で最後のページかどうか判別する際には $foreach.last
を使います。
#for ($page in $pages) ## ページ単位のループ #for ($item in $page) ## 明細ごとのループ #end #if ($foreach.last) ## 最後のページの場合 ## 集計行の表示など #end #end
$foreach.last
を含め、次のようなデータがあります。
データ | 型 | 意味 |
---|---|---|
$foreach.first | 真偽値 | ループの先頭ならtrue |
$foreach.last | 真偽値 | ループの最後ならtrue |
$foreach.count | 数値 | ループの回数(1..) |
$foreach.index | 数値 | ループのインデックス(0..) |
なお、印刷範囲を3つにすることで「最初のページ」「2ページ目以降のページ」「最後のページ」に分けて印刷範囲を作成することもできます。この時、明細データの取得方法は次のようになります。 firstNum
は最初のページの明細件数、 num
は2ページ目以降の明細件数です。
- 最初のページ
#set($page = $e.items.take(firstNum))
- 2ページ目以降のページ(最後のページを除く)
#set($pages = $e.items.drop(firstNum).chunk(num).slice(0, -1))
- 最後のページ
#set($page = $e.items.drop(firstNum).chunk(num).slice(-1)[0])
この3つのパターンを使うことで、 $foreach
を使わない形でも帳票を作成できます。
デバッグ
もし明細の内容がどのようになっているか気になった場合には B列
以降の列で %{page}
のように指定した上で帳票出力してください。データの内容がダンプされるので、どういった項目があるのか確認できます。
まとめ
帳票で明細行を出力する処理は改ページにも繋がるので、処理が複雑になります。頭の中で出力結果を考えながら記述するのは難しいですが、慣れてしまえばすぐできるようになるでしょう。ぜひトライしてください。
Docurainを使って帳票作りにチャレンジ(その2:明細行、フッターの記述法)
DocurainはWeb APIを使って簡単に納品書や請求書をはじめとする、帳票を生成するサービスになります。今回はこのDocurainの使い方について、帳票を一から作成してみます。
前回は帳票のヘッダーについて解説しましたので、今回は注文などの繰り返し部分(明細行)やフッターの書き方を解説します。
テーブル定義を解除する
今回参照した帳票では、注文一覧部分がテーブルとして定義されています。Docurainではテーブル定義は利用できませんので、テーブル部分の適当なセルを選択した状態で、テーブルデザインの中にある 範囲に変換
を選びます。
確認ダイアログが出たら、そのまま はい
を選択してください。
ロジック記述を追加する
今回の注文データは複数(可変)定義できることとします。つまり、データは次のように定義されていることとします。今回は3件ですが、4件や5件でも問題ありません。
"orders": [ { "num": 100, "no": 1001, "unit": 100, "info": "ボールペン", "discount": 0 }, { "num": 300, "no": 1002, "unit": 200, "info": "カードケース", "discount": 5000 }, { "num": 200, "no": 1003, "unit": 100, "info": "付箋紙", "discount": 6000 } ],
そこで、まず帳票のテーブル部分の1行目にあたる部分のA列(ロジック記述用の列)に、次のように記述します。
#foreach($o in $e.orders)
これは、JSON内の orders
を繰り返し処理するという命令になります。そして3行目には次のように記述します。
#end
これは繰り返し処理部分の終わりを指定しています。つまり #foreach
から #end
で囲まれた行の処理を繰り返すという意味になります。実際の記述内容は次の通りです。
繰り返し行部分の記述
繰り返し処理で $o
と記述しています。繰り返し処理時には、この $o
の中に注文データが繰り返し入ってきます。つまり最初の $o
の内容は次のようになります。
{ "num": 100, "no": 1001, "unit": 100, "info": "ボールペン", "discount": 0 }
これに従って、明細行の内容を記述していきます。今回は次のようになります。
データがない場合について
たとえば備考など、必ずしもデータがない場合もあるでしょう。そうした時には $!{o.info}
のように !
を付けておきます。こうすることで、データが場合には空白になります。
繰り返し行内での計算処理について
通常のExcelではセルの値を参照した計算処理は =A5*E5
のように書きます。しかし、繰り返し処理部分では行番号が変わってしまいます。そこで、Docurainでは %=
という行頭記号を使います。今回の小計計算は次のように記述します。
%= $o.unit * $o.num - $o.discount
%=
を記述しておくことで、テンプレートとして処理されます。つまり1行目の場合には %= $o.unit * $o.num - $o.discount
が =100*100-0
と変換されます。これで正しく計算処理されるようになります。
フッターの処理について
帳票のフッターでは合計金額を出すことが多いでしょう。合計金額は明細行の集計結果になりますので、セルが可変の場合に書くのが困難です。そこでDocurainではセルに名前を定義して対応します。今回は割引の合計金額を例に紹介します。
まず、割引金額(上下のセルを含めて)を範囲選択します。
そして、その範囲に名前を付けます。今回は 値引き
としています。
今は3行が名前の範囲ですが、繰り返し処理が行われると自動的に拡張されます。そこで、割引合計のセルは =SUM(値引き)
とすることで、集計された金額が入力されるようになります。小計も同様です。
消費税や集計金額についても同じように明細行を使うこともできますが、小計セルの名前を 小計
などと名前を付けて、そのセルを対象として =小計*1.1
とした方が分かりやすいでしょう。
後は、他にも置き換えられる文字列をJSONで定義し、置き換え文字列として定義すれば完成です。
今回のJSONデータは次のようになりました。
{ "company": { "name": "ルート42株式会社", "message": "クラウド帳票開発のドキュレイン" }, "customer": { "name": "ドキュメント 太郎", "company": "帳票株式会社", "address1": "〒110-0012 東京都台東区", "address2": "竜泉1-10-6 秋田屋ビル2F", "tel": "03-000-0000" }, "billing": { "no": "100010", "date": "2021-07-19" }, "orders": [ { "num": 100, "no": 1001, "unit": 100, "info": "ボールペン", "discount": 0 }, { "num": 300, "no": 1002, "unit": 200, "info": "カードケース", "discount": 5000 }, { "num": 200, "no": 1003, "unit": 100, "info": "付箋紙", "discount": 6000 } ], "charge": { "name": "ルート 花子", "tel": "03-111-1111", "email": "info@docurain.jp" } }
実行する
今回もコマンドラインで実行します。OSによってコマンドが多少変わるので注意してください。 YOUR_TOKEN
はDocurainのトークン | Docurainにて取得できるトークンになります。
Windowsの場合
Windowsではコマンドプロンプトを利用してください。テンプレートのXLSXファイル、上記JSONデータファイルがあるフォルダ内でコマンドを実行します。
curl -X POST https://api.docurain.jp/api/instant/pdf ^ -H "Authorization:token YOUR_TOKEN" ^ -H "Content-Type:multipart/form-data" ^ -F "template=@./receipt.xlsx;type=application/vnd.ms-excel" ^ -F "entity=@./data.json;type=application/json" ^ -o receipt.pdf
macOS/Linuxの場合
macOSやLixuxはターミナルを利用してください。Windowsと同じく、テンプレートのXLSXファイル、上記JSONデータファイルがあるフォルダ内でコマンドを実行します。
curl -X POST https://api.docurain.jp/api/instant/pdf \ -H "Authorization:token YOUR_TOKEN" \ -H "Content-Type:multipart/form-data" \ -F "template=@./receipt.xlsx;type=application/vnd.ms-excel" \ -F "entity=@./data.json;type=application/json" \ -o receipt.pdf
これで明細行含め、正しくPDFが得られるか確認してください。
まとめ
今回は新しい帳票ファイルを使って、Docurainのテンプレートにしていく過程を紹介しました。コマンドや表示方法は他にも多数ありますので、Excelテンプレート リファレンス | Docurainを参照してください。
Docurainを使って帳票作りにチャレンジ(その1:帳票仕様とヘッダー作成)
DocurainはWeb APIを使って簡単に納品書や請求書をはじめとする、帳票を生成するサービスになります。今回はこのDocurainの使い方について、帳票を一から作成してみます。
続きを読むマクロを使って帳票のメンテナンス性を高めましょう
マクロを使って帳票のメンテナンス性を高めましょう!
帳票では細かなカスタマイズが不定期に求められるので、メンテナンス性高い状態に維持するのが大事なポイントになります。そのためにできることは幾つかありますが、一つはコピー&ペーストで場当たり的な対応をしないことです。
例えば自社の情報やヘッダー情報、フッターに記載した連絡先に関する情報などを全ての帳票に直書きしていると、いざその情報が変わった時に変更漏れが発生したり、幾つも修正する手間が発生します。かといって、それをシステム側から出力するのも面倒でしょう。
そこで使って欲しいのがDocurainのマクロ機能です。共通処理を部品化し、再利用性を高めます。
続きを読む