SDKMAN! メモ
久々更新です。 新しいテーブルとイスとキーボードによりモチベが高まっています。
SDKMAN! is 何
公式サイトより引用
The Software Development Kit Manager
だそうです。うん、そっか。
初心者的に解釈すると、Javaの言語自体のバージョンを管理できるツールです。 Rubyのrbenvを参考にしているのだとか。Pythonでいうとpyenv、Node.jsでいうとnvm、、、 どの言語にも大体ありますね。
SDKMAN!はScala、Kotlin等のJVMを使う言語や、Maven、Spling Boot等の主要なライブラリのSDKも提供しています。
何故SDKMAN!を使うのか
最近はDocker等によりその必要性が薄くなってきている感もありますが、コマンド一発でマイナーアップデートが取得出来たり、バージョン変えられたりしたらやはり便利です。
JavaだとIDE(IntelliJ IDEA)がその辺の面倒も見てくれるので今まで使用していなかったのですが、コマンドでちょちょいとJavaやScala使うことも多くなってきたのでインストールする気になりました。
今の使い方の想定としては、Java,Scala等の言語についてはSDKMAN!で管理して、Maven等は今まで通りなんも考えずにIDEに一任しようと思ってます。
SDKMAN!導入
公式サイトの通りにコピペすれば簡単です。
$ curl -s "https://get.sdkman.io" | bash $ source "$HOME/.sdkman/bin/sdkman-init.sh" $ sdk version
SDKMAN!ができること
JDK(Java Development Kit)のインストール
$ sdk install java #stableかつfomalなJavaがインストールできます。 $ sdk install java 19-tem #Temurinというコミュニティ版JDK。バージョンも指定します。
インストールできるJavai一覧
$sdk list java $ sdk list scala #scalaなんかもこんな感じで
想像以上に色んなJDKがありますね。GraalVMでネイティブ化するのもたのしそう。。
使用するJDKを設定
$ sdk use 19-tem
普段使うJDKを設定(グローバル)
$ sdk default 19-tem
普段使うJDKを設定(フォルダごと)
$ cd ~/work/sdkman_test # ここで使うバージョンを設定する。 $ sdk env init # これで初期設定。`.sdkmanrc`というファイルができる。 $ cat .sdkmanrc # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below java=17.0.4.1-tem $ sdk env # ただディレクトリに入るだけじゃダメで、このコマンドでバージョンが入れ替わる。
もしcd
だけでバージョンを切り替えたいなら、$ sdk config
でエディタが開くので、sdkman_auto_env=true
とする。
管理下のパッケージをアップグレード
$ sdk upgrade
SDKMAN!自体をアップデート
$ sdk selfupdate
なんか困ったとき
$ sdk update # なんかメタデータを更新してくれるらしい $ sdk flush # キャッシュを削除してくれるらしい $ sdk flush archives # 古いパッケージを削除してくれるらしい $ sdk flush tmp # 古いSDKMAN!のインストーラを削除してくれるらしい
その他
$ sdk broadcast # なんか新機能とかのニュースが見れるらしい
IDEとの連携
~/.sdkman $ tree -L 3 . ├── bin │ └── sdkman-init.sh ├── candidates │ └── java │ ├── 17.0.4.1-tem │ ├── 19-tem │ └── current -> 19-tem
~/.sdkman/candidates
にJDKの本体が収まっていました。これ使えばよさそうです。
まとめ
SDKMAN!は機能がシンプルで使いやすい印象です。 カジュアルにJavaを楽しめるようになったかなぁ。
参考文献
Atom形式のフィードを読んで指定のファイルより新しいエントリを取得するVBScript
本当はチャットツールに差分をPOSTするところまで実装する予定だったが、力尽きたので今日はここまで。
Option Explicit Dim FEED_URL Dim PREV_RSS_UPDATE_DATE_FILE 'FEED_URL = "http://www.data.jma.go.jp/developer/xml/feed/regular.xml" FEED_URL = "http://example.com" PREV_FEED_UPDATE_DATE_FILE = "C:\Users\hayak\TMP\prevDate.txt" ' 本処理 Dim xml ' XMLDOMオブジェクト GetFeed FEED_URL, xml ' フィードをダウンロード ' 更新確認 if StrComp(GetPrevUpdateDate(), GetUpdateDate(xml)) = -1 Then WScript.Echo GetNewEntry(xml, GetPrevUpdateDate()) End If ' フィードを取得してXMLDOMオブジェクトを格納する Sub GetFeed(rssURL, xmlDom) Dim xmlhttp Set xmlhttp = WScript.CreateObject("MSXML2.XMLHTTP.3.0") Dim resText xmlhttp.Open "GET", rssURL, False xmlhttp.send If xmlhttp.status <> 200 Then WScript.Echo "リクエスト失敗" Set xmlhttp = Nothing WScript.Quit End If ParseXML xmlhttp.responseText, xmlDom Set xmlhttp = Nothing End Sub ' XML文字列をパースしてXMLDOMオブジェクトを格納する Sub ParseXML(xmlString, ByRef xmlDom) Dim loadSuccess Set xmlDom = WScript.CreateObject("MSXML2.DOMDocument") loadSuccess = xmlDom.LoadXML(xmlString) If Not loadSuccess Then WScript.Echo xmlDom.parseError.errorCode WScript.Echo xmlDom.parseError.reason WScript.Echo xmlDom.parseError.line WScript.Echo xmlDom.parseError.linepos WScript.Echo xmlDom.parseError.filepos WScript.Echo xmlDom.parseError.srcText WScript.Echo xmlDom.parseError.url Set xmlDom = Nothing WScript.Quit End If End Sub ' ファイルを読んで文字列を格納する Sub ReadFile(filePath, textStr) Dim fs Dim tf Set fs = WScript.CreateObject("Scripting.FileSystemObject") Set tf = fs.OpenTextFile(filePath, 1) textStr = tf.ReadAll tf.Close Set fs = Nothing Set tf = Nothing End Sub ' XMLから更新日を取得する Function GetUpdateDate(xml) GetUpdateDate = xml.getElementsByTagName("updated").Item(0).Text End Function ' ファイルから以前の更新日を取得する Function GetPrevUpdateDate() ReadFile PREV_FEED_UPDATE_DATE_FILE, GetPrevUpdateDate End Function ' 以前の更新日より新しいエントリを取得する Function GetNewEntry(xml, prevDate) Dim entry Dim result result = "未読のentryがあります \n" For Each entry in xml.getElementsByTagName("entry") AppendNewEntry prevDate, entry, result Next GetNewEntry = result End Function ' 新しいエントリのみ追加する Sub AppendNewEntry(prevDate, entry, result) Dim updateDate updateDate = entry.getElementsByTagName("updated").Item(0).Text If StrComp(GetPrevUpdateDate(), updateDate) = -1 Then Dim title Dim link title = entry.getElementsByTagName("title").Item(0).Text link = entry.getElementsByTagName("link").Item(0).getAttribute("href") result = result & title & " \n" & link & " \n" & " \n" End If End Sub
PowerShellをPowerShellでアップデートする
久々です。
備忘録がてらの更新。※20201114あまりにも適当だったので手を加えました。
PowerShell7.0がリリースされて久しいですが、最近はこんな機能ができてます。
Microsoft お得意の(?)おせっかいですね。 アップデート用のコマンドとか用意してくれたらいいのに…なんて思ったんですが、
ないんですよね、これが!!!
コマンドでPowershell7.0.xをインストールする
一応、PowerShell Gallery | Home にワンパンでアップデートできるモジュールは散見されますが、
何入れたか逐次忘れてしまう私は、そのモジュールのアップデートさえ怠ってしまいそうです、、、
よって、 WSL2を導入しましょう コピペで使えるアップデートコマンドを作ってメモしておこうという話です。
↓これです
PS > (New-Object Net.WebClient).DownloadFile("https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/PowerShell-7.0.3-win-x64.msi", "$env:Temp\powershell.msi") PS > msiexec.exe /package $env:Temp\powershell.msi /passive ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1 ENABLE_PSREMOTING=1 REGISTER_MANIFEST=1
(New-Object Net.WebClient).DownloadFile
は curl
(Invoke-WebRequest
) でもいいと思います。
PS > Invoke-WebRequest "https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/PowerShell-7.0.3-win-x64.msi" -OutFile "$env:Temp\powershell_a.msi" PS > msiexec.exe /package $env:Temp\powershell_a.msi /passive ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1 ENABLE_PSREMOTING=1 REGISTER_MANIFEST=1
msiexec.exe
には公式では /quiet
オプションが入っていましたが、進捗状況が分からなくなったので /passive
としました。
再起動して終了です。
結構手間かかったので、フツーにインストールした方が早いかも。
以上!!!
GoとGCEで小さなWebアプリケーションを構築する(1) ~とりあえずGCE上で動かしてみるの巻~
久々に書きます。 今日はお仕事ネタではなく、Golangの話題についてです。
ひと月ほど前に友人からこんな本を頂きました。ほんとありがとね。
Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る impress top gearシリーズ
- 作者:Sau Sheong Chang,武舎 広幸,阿部 和也,上西 昌弘
- 発売日: 2017/03/17
- メディア: Kindle版
一通り遊んだので、そろそろアウトプットしていきたいと思います。
この記事の目的
以下のようなWebサイトをWWW(この世のすべて)に公開することです。
もちろん、ここで終わりにするつもりはなく、 簡単なDB連携やES、JSONでのREST通信的なものも書いてみるつもりです。ネタは豊富です!
あと、誰かが見れるようにした方が楽しいと思ったので、あえてデプロイまでつっこみます。Golangなら、デプロイ自体はとても簡単ですよ。
※セキュリティには配慮しつつ書きますが、どうしても知識不足なところはあると思います。
このエントリだけでなく、他記事からも情報を仕入れつつ、慎重にデプロイしてください。
使うお道具
- Windows
- Google Compute Engine (GCE)
- Visual Studio Code
- 今回は実施しないですが、GCE上に置いておくこともできたりします。
- gopherの皆様にも人気のエディタです。
- (参考:Go Developer Survey 2019 Results - The Go Blog)
- Golang
- 今回のWebアプリケーションを実装してくれる言語です。
手順
GCEを整える
GCEは、マシンタイプ f1-micro
であれば、無料で使えます。
会員登録等は省略します。
まず、こちらにアクセスし、必要事項を記入しましょう。
右上にお金かかりそうなことが書いてありますが、この構成の1台のみならば、お金は発生しないはずです。 発生したらごめんね。
SSHの設定
当方、Windowsなので、PowerShell(OpenSSH)にてSSH公開鍵作成&SSH接続をしてみます。
4096bitのrsa暗号鍵ペアを作ります。 -f
オプションは適宜変更してください。
PS >ssh-keygen -t rsa -b 4096 -f C:\Users\(あなたのおなまえ)\.ssh\id_rsa_test
指定したパスに次のファイルができます。
拡張子なしのファイルは秘密鍵です、動かしてはなりません。誰かに悟られてはいけません。 .pubを公開鍵といいます。これをGCE上に置きます。
GCEコンソールに戻って、作成したVMインスタンスをクリック、 上タブの「編集」をクリックして、.pubファイルの中身をコピペします。
では、PowerShellで接続してみましょう。
PS> ssh (あなたのおなまえ)@VMインスタンスの外部IP -i c:/users/(あなたのおなまえ)/.ssh/id_rsa_test -p 22
これで環境設定は終了です。と言いたいところですが、次のファイアウォールの設定は絶対にしておきましょう。
ファイアウォール
ここが詳しいです。必ず設定しましょう。 22ポートを開放していると、世界中の怖い人がたくさん訪問してきますよー^^ eightyfivelife.com
また、同様の設定でHTTP通信のポートも制限しておくといいかもしれません。
これで、環境設定は終了です。
Goのインストール
他にたくさん文献がありますので、省略します。
この辺りを見たらできるんじゃないんでしょうか。
テキストエディタにてファイル編集
環境変数GOPATH以下が、Golangの主なワークスペースです。 Golangに付属するコマンドは、大体ここを見ながら動きます。
さあ、初めてのWebアプリを書きましょう(ここまで記事を書くのに3時間)。 いまは、Golangの文法等については気にしなくて結構です。
%GOPATH%\src\first_webapp\server.go
package main import ( "fmt" "net/http" ) func handler(writer http.ResponseWriter, request *http.Request) { fmt.Fprintf(writer, "Hello World, %s", request.URL.Path[1:]) } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) }
Goを コンパイルする
まず、Goのワークスペースフォルダに移動して、 上記で作成したプロジェクト「first_webapp」を初期化します。
PS> cd $env:GOPATH PS> go install first_webapp
Goをビルドしてできたバイナリは、別OS上では動作しません。 しかし、環境変数を設定することによって、Linux用のバイナリを作成することが可能です。
PS> $env:GOOS = "linux" PS> $env:GOARCH = "amd64"
ビルドの時は、いつの間にか出来ているbinフォルダに入って行います。
PS> cd bin PS> go build first_webapp
さ、これでバイナリが出来上がっています。
あとはこのバイナリをSCP送信して、実行します。
PS> scp -i (秘密鍵) (goのバイナリ) (外部IP):~/
bash: first_webapp
WEBページにつないでみましょう。 開発用にポートを∶8080にしたことに注意してください。
これで、晴れて世界デビューです。おめでとうございます。
詳しい解説は、また別日に!!
PHP FPDI + FPDF でPDFファイルを分割する
タイトルの通りです。お仕事で使ったので、備忘録。
青空文庫から人間失格ー太宰治のPDFを取得し、テストデータとした。
PDFから、最初の3ページ分を抜粋し、新しいPDFとして保存する。
今回使うのは、PHPのPDFの編集用ライブラリとして太古の昔から存在する、FPDIとFPDFである。
PHPで使えるPDFのライブラリはたくさんこと存在する。PDFlib、TCPDF、haru...
今回の選定ポイントは、古いPHP動くこと、その一点に尽きる。
PHP動くようにする(XAMPP、VSCode)
今回の記事も長くなりそうである。なぜなら、環境から作る必要があるから。
でも環境構築なら優れた記事はもうたくさんあるので丸投げして任せよう。
ここがいいんじゃないかな
Qiitaーisual Studio Code でPHPを開始するまでの手順
うむうむ。
ん~、開発用途に一つサーバー借りようかな。VSCodeをリモートで動かしたい。
さて次
FPDI と FPDF のダウンロード
Composerを使ったりとかするんだろうけど、今回はそんな大層なことはしないので愚直にクラスファイルをダウンロードして、htdocsに突っ込むことにする。
ダウンロード
フォルダ構成は以下
ソースコード
最小構成ならば、実に簡単にPDFのあれこれができてしまう。
<?php use setasign\Fpdi\Fpdi; require_once('fpdf/fpdf.php'); // はじめにFPDF require_once('fpdi/src/autoload.php'); // 次にFPDIの順で読み込むよ。 // PDFファイルを読み込む $pdf = new Fpdi(); $pageCount = $pdf->setSourceFile('NingenShikkaku.pdf'); for ($i = 1; $i <= $pageCount; $i++) { // カウントは1から!(ページ数に相当) if ($i <= 3) { $templateId = $pdf->importPage($i); // 該当ページをテンプレートとしてインポート $pdf->AddPage(); // 出力用のページを一つ追加 $pdf->useTemplate($templateId); // 出力用のページに、テンプレートを適用する } } $pdf->Output('result_3pages.pdf', 'F'); // ファイルとしてフォルダ内に書き出し echo("<h1>Finished.</h1>"); // おわり
動かす
用意したのは青空文庫の人間失格をPDF化したもの。47ページ。
ブラウザ。まあ見るまでもないわなw
生成ファイルは、"result_3pages.pdf"
生成されたファイルは3ページになっているが。。。おっと。
もとが横長のページだったが、それが反映されず、縦向きの文書として出力された結果である。
$pdf->useTemplate($templateId);
↓
$pdf->useTemplate($templateId, ['adjustPageSize' => true]);
とすればよい。
はいどうぞ
感想
師が「PHPはなんか、よくわからんが、何でもできるんだ。気軽にな。」と言っていた。
ようやく気持ちが分かってきた気がする。なんだかわからんが確かにできるんだなぁ。気軽になぁ。
Apache Commons Net listFiles() の注意点
JavaでFTPサーバーのデータを扱う場合、Apache Commons Net APIを使うのが一般的(多分)。
FTPというのはHTTPと同じように通信を行うときの約束事(Protocol)のひとつで、
File Transfer Protocolの略である。HTTPのコマンド(GET
,POST
)と同じように、FTPにもコマンドがある(コマンド一覧)。
例えば、CWD
コマンドでディレクトリを移動して、LIST
コマンドでそのフォルダにあるファイルの一覧(もしくは、ファイル自体)を取得できる。
ただここで一つ懸念があって、LIST
コマンドで取得できるファイル情報は、サーバー側のOSに依存した結果だったりする。つまりFTPサーバーが運用の途中で切り替わったりすると大本から回収が必要になる。
これじゃ悲しい結末を迎えるのが確定的に明らかなので、そのOS間の差を吸収できるようにしたのがApache Commons Net APIのFTP関連のクラスたちである。
恐らく多くのシステムでこのAPIは使われていて、信用度はかなりのものだと思う。
だが、これで取得したデータでNullぽしてしまったのである。えーーーー。
org.apache.commons.net.ftp.FTPClient
上記のクラスで、相手のホストへの接続、ログイン、ファイル転送すべてが行える。いろいろクラスはあるが、基本的にこれさえ覚えればもう大丈夫。簡単にFTPサーバーへとアクセスできる。
この中のメソッドに、listFiles()
というものがあり、上記のLIST
コマンドで取得したファイル一覧を取得してFTPFile
型の配列を返してくれる。相手がCentOSであれUbuntuであれ、WindowsServerであれ、後も先も同じメソッドが使えるということである。
便利なメソッドlistFiles()
だが、一つ注意点がある。もしファイル情報が取得できなかったら、こいつ、配列にnullを入れてくる。
つまり、こんな感じの処理をすると...
FTPFile[] files = client.listFiles(); // ファイル一覧取得 for (FTPFile file : files) { // もし、filesのどれかがnullだったりしたら System.out.println(file.getName()); // .getName()でぬるぽ }
やあ、nullさん。いつも仕事をくれてありがとう。もう出てこなくていいよ。
Javadocもそう言っている
閑話休題。Apache Commonsの公式ドキュメントを見てみる。
Returns:
The list of file information contained in the current directory in the format determined by the autodetection mechanism.
NOTE: This array may contain null members if any of the individual file listings failed to parse. The caller should check each entry for null before referencing it.
もしファイルのパーシング(OS間の差をなくしたりして、FTPFile型の値に直す処理)に失敗したら、nullを入れる*1から、値を参照する前にちゃんとnullチェックしてね。ということだ。公式が言っているんだから、ちゃんとチェックしないといけないね(2回目)。
にもかかわらず、いま診てるシステムではしていなかったのだなあ。ネットに転がっているサンプルコードでも、nullチェックしているものは少ない。(そもそもこのAPIに関する記事が少ないけどね)
改善案
さっきから愚痴が多いが、直すならこう。
FTPFile[] files = client.listFiles(); // ファイル一覧取得 for (FTPFile file : files) { // もし、filesのどれかがnullだったりしたら if(file == null) continue; // 最初に戻る System.out.println(file.getName()); // .getName()でぬるぽらない! }
周りの人を困らせたいならこう
Arrays.asList(client.listFiles()) // ファイル一覧取得 .stream() .filter(Objects::nonNull) // nullをひっかけて .map(FTPFile::getName) // Stream<String>にして .forEach(System.out::println);// わぁい!
Java String → Oracle varchar2 入りきらない文字列をByte単位で切り捨てるための策
今回の経緯
お仕事ではJava7とOracle11を使っている。今回の改修で問題になったのが、インポートする予定のCSVファイルのある文字列データが、何文字入るのか不定であること。
もし、入力文字列が100Byteで、受け止めるカラムでvarchar2(64 Byte)しか用意してないとき、INSERTしたらORA-12899 でエラー吐いてしまう。
対応としては以下ものがパッと考えられる。
- まずそんなデータを入力するな。データ仕様くらい決めておけ。
- ほんそれ
- CROBで受け止めればいいじゃん。&varchar2(4000)にしよう。
- データ量と速度の観点から、実用的でない。ウン十万行あるテーブルなんでできるだけ、データ容量もメモリバッファも減らしたくない。
- 文字列オーバーしたら切り捨てよう。
- え、データなくなってもいいんだ。。。そっか。。。
ということで、今回の方策としては、「文字列オーバーしたら切り捨てよう」なった。そっか。。。
そもそも入ってくるだけで使わないデータだという。でもDBには保存してほしいんだと。そっか。。。
既存のCSVtoDBの処理では、PLSQL使わずに直INSERTしているため、Javaでのバリデートである。
文字列の切り捨ての方策
String.substr()
で行こうと思ったが、少し考えればそれじゃダメなことが分かる。varchar2のサイズ指定は、デフォルトでByte単位だ。文字数じゃない。それに、DB側はMS932なのに対し、Javaの内部処理は決まってUTF-16である。これじゃいかん。
とりあえず、2通りの方法で切り捨て可能だと思う。
String.substr()
を使うなら、テーブル定義の変更で吸収する必要が出てくる。navarchar2か、varchar(20 CHAR)で宣言するか、もしくはNLS_LENGTH_SEMANTICSをCHARにするか。これまでのDB設計や、特殊文字入ることも考えると良くないなと思う。変更箇所が分かれるのは混乱を招くし、そもそもDBは専門外なんで、余計なことはしたくない(甘え)。JavaでちゃんとByte単位で切り捨ててからINSERTしてやる。これが一番健康的であろう。なら、
String.substr()
は使えない。
java.nioで切り捨てる
参考にしたのは、Qiita - Java 文字列をバイト数で切り捨てる
上記の例だと、特殊文字がうまいことエスケープできていなかったようなので、手直しした。でもこれでいいかは不安である。
import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CodingErrorAction; public class Converter { /** * @param s 文字列 * @param charset 文字コード * @param maxBytes 最大バイト数 * @return 最大バイト数に入りきる分だけの文字列 */ public String cutString(String s, Charset charset, int maxBytes) { ByteBuffer bb = ByteBuffer.allocate(maxBytes); CharBuffer cc = CharBuffer.wrap(s); CharsetEncoder encoder = charset.newEncoder() .onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE) .reset(); encoder.encode(cc, bb, true); encoder.flush(bb); bb.flip(); return charset.decode(bb).toString(); } }
いきなりjava.nio
のクラスがぞろぞろ出てきて嫌だけど、1行1行理解すれば読めると思う。
- まず
ByteBuffer bb
にmaxBytes
分の箱を用意しておく。 - つぎに
CharBuffer cb
に入力値を入れる。 - 読み取れないバイトコードをデフォルトのリプレース文字(
?
)に変更するエンコーダーを作成する。 - エンコードを通す。すると、
bb
に入る分だけ文字が入る。2バイト文字等で最後まで入りきらない場合は、あきらめてくれる。 flush()
でメモリ内に残ったデータを排出する。bb
を反転させて、データが入っているところだけを有効化する。- デコードする(ここは簡易メソッドでいいはず)。
バイトに直した後に、わざと小さい箱を用意して入れるだけ入れて、文字列に戻すという操作をする。
もっといい方法はありそうだけど、とりあえずこれでいいか相談だ。
雑感
文字コードを忌避していた節があったから、たった一つのメソッドだけど勉強になった。
参考にしたのはこの辺
すごい煽ってくるけどすごく参考になりました。ありがとうございます。
Shift-JISとMS932の違いについて解説している
あと、記事書くのに時間かかり過ぎだ。もっと気楽な気持ちで書きたいな。