groovy – こみなのメモ帳 / 趣味と実益のネタ帳 Wed, 27 Jul 2022 10:12:46 +0000 ja hourly 1 https://wordpress.org/?v=6.1.1 javamailを使ってYahoo!メール(IMAP)でフォルダ移動する /archives/1032/ /archives/1032/#respond Wed, 27 Jul 2022 10:12:46 +0000 https://www.komina.info/?p=1032 「楽天ブログに自動で記事を投稿したい」というタイトルから脱線してきたのでそのまんまの件名に変えました。

前回はIMAPプロトコルで狙ったメールの受信を行うプログラムを書きました。

メールを解析した後は不要なのでメーラを使って手作業で消していたのですが、毎回はさすがに面倒なので解析の流れでプログラムで削除まで行うことにしました。

IMAPプロトコルではフォルダ間のメッセージ移動が可能なので、受信箱に届いたメッセージをゴミ箱へ移動させることにします。

String host = 'imap.mail.yahoo.co.jp'
String user = '******'
String pass = '******'
String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory"

Properties properties = System.properties
properties.setProperty('mail.imap.socketFactory.class', SSL_FACTORY)
properties.setProperty('mail.imap.ssl.trust', '*')
properties.setProperty('mail.debug', 'true')

Session session = Session.getInstance(properties)
URLName urln = new URLName('imap', host, 993, null, user, pass)
Store store = session.getStore(urln)
store.connect()
IMAPFolder inbox = store.getFolder('Inbox')
inbox.open(Folder.READ_WRITE)

SearchTerm[] stlst = [
        new SubjectTerm('日の日記'),
        new ReceivedDateTerm(ComparisonTerm.EQ, new Date()),
        new FromStringTerm('no-reply@plaza.rakuten.co.jp')
    ]
SearchTerm st = new AndTerm(stlst)

List<Message> target = []
inbox.search(st).each { Message msg->
    println("Subject  : ${msg.getSubject()}")
    println(" From    : ${msg.getFrom()}")
    println(" ReplyTo : ${msg.getReplyTo()}")
    target.add(msg)
}

if (!target.isEmpty()) {
    Message[] targetArray = target.toArray(new Message[0])
    inbox.moveMessages(targetArray, store.getFolder('Trash'))
}

inbox.close()
store.close()

ポイントは store.getFolder('Inbox')com.sun.mail.imap.IMAPFolderで受けているところです。
メッセージの移動はIMAP固有の機能なのでjavax.mail.FolderにはmoveMessagesメソッドが定義されていません。

問題発生

これで課題解決かと思ったのですが実際に実行してみると例外が発生します。うーん?デバッグ出力を有効にしてみるとMOVEコマンドでエラーが発生していました。

A6 MOVE 15,33 Trash
A6 NO [CANNOT] MOVE It's not possible to perform specified operation

プログラムの書き方の問題ではなくIMAPサーバ側でコマンドを拒否しているようです。メッセージの移動を一切禁止するとかあるのだろうか。

ちなみに上記の A6 というのはコマンドとその応答を紐づけるものです。並列実行を考慮した実装となっているそうです。

原因の調査

さてプロトコル系でうまくいかないときは正常に動いているものを参考にするのが定石です。Thunderbirdという無料メーラーはログを出力する機能があるのでそれを利用することにしました。

環境変数 NSPR_LOG_MODULES にプロトコル種別とログレベルを設定(IMAP:3 など)、NSPR_LOG_FILE に保存先ファイルパスを設定した上でThunderbirdを起動します。(※ログは起動のたびにクリアされます)

Thunderbirdの画面からメッセージの移動を行ったところ以下のコマンドが発行されていました。(抜粋)

65 uid move 394141 "Trash"
* OK [COPYUID 4 394141 359316]
* 2 EXPUNGE
65 OK UID MOVE completed

単なる MOVEコマンドではなく、UID MOVEコマンドを使用していました。

MOVEコマンドで指定しているのはメッセージ番号、これはメールボックス内の連番でセッション内で有効な番号。一方、UID MOVEコマンドで指定しているのはUIDというものでメールボックス内で一意で永続的であることが保証された番号。

どうやらYahoo!メールのIMAPではメッセージ番号を使った移動はサポートしていないということらしい。

解決方法

さてメッセージを移動させる方法は分かりました。しかし UID MOVEコマンドをjavamail-1.6.2がサポートしているのかというとどうも期待できなさそうです。moveuidとかcopyuidとか思わせぶりなメソッドは存在するのですが実際に発行しているコマンドは MOVEコマンドでした。

そうなると自前で発行するしかありません。
javamailには自前のプロトコル処理に差し替える機能があるようですが(プロパティmail. + プロトコル名 +.classなど)、正直そこまでやるほどの労力を使いたくありません。

そこでimap関連の機能を利用しつつ UID MOVE コマンド発行することにしました。

SearchTerm[] stlst = [
        new SubjectTerm('日の日記'),
        new ReceivedDateTerm(ComparisonTerm.EQ, new Date()),
        new FromStringTerm('no-reply@plaza.rakuten.co.jp')
    ]
SearchTerm st = new AndTerm(stlst)
IMAPMessage[] msgs = inbox.search(st)

FetchProfile fp = new FetchProfile()
fp.add(FetchProfile.Item.ENVELOPE)  
fp.add(UIDFolder.FetchProfileItem.UID)
inbox.fetch(msgs, fp)

msgs.each { IMAPMessage msg->
    println("Subject  : ${msg.getSubject()}")
    println(" From    : ${msg.getFrom()}")
    println(" ReplyTo : ${msg.getReplyTo()}")
    println(" UID     : ${msg.getUID()}")
}
if (msgs.length > 0) {
    inbox.getProtocol().simpleCommand('UID MOVE '
        + UIDSet.toString(Utility.toUIDSet(msgs)) 
        + ' "Trash"', null)
}

UID MOVE コマンドを発行するにあたりUIDが必要となるので、まずはfecthメソッドにてUIDを取得します。そしてそのあとにsimpleCommandメソッドを用いてコマンドを発行しています。

UIDSet.toString(Utility.toUIDSet(msgs))は幾つかのUIDをまとめてくれる便利メソッドです。(123,124,125といった連番であれば123:125のようにまとめてくれる)

Yahoo!メールではゴミ箱は使用容量のカウント外となるのでひとまず移動だけでオッケーですね。

おまけ

IMAPでメッセージ削除は論理削除→物理削除の順で行います。
論理削除はメッセージに対してDeletedフラグを立てる形で行われるのですが、こちらも単なるSTOREコマンドではエラーになり、UID STOREコマンドを用いる必要がありました。

if (msgs.length > 0) {
    inbox.getProtocol().simpleCommand('UID STORE '
        + UIDSet.toString(Utility.toUIDSet(msgs))
        + ' +FLAGS '
        + inbox.getProtocol().createFlagList(new Flags(Flags.Flag.DELETED))
        , null)
}
inbox.expunge()

+FLAGSでフラグON、-FLAGSでフラグOFFを意味します。UID STOREコマンドで削除フラグを立てて、expungeメソッドで物理削除しています。

関連記事

https://www.komina.info/archives/887

参考資料

]]>
/archives/1032/feed/ 0
楽天ブログに自動で記事を投稿したい (2) /archives/901/ /archives/901/#respond Wed, 20 Oct 2021 05:47:46 +0000 https://www.komina.info/?p=901 前回はpop3でYahooメールの受信箱を巡回して特定メールを読みだすプログラムを書きましたが、受信箱の中のメールをすべて走査する仕組みだったため、受信箱にいっぱいメールがあると時間がかかります。

調べてみると、IMAPというプロトコルであればサーバ側でメールの検索が行えるとのこと。処理時間や通信量の削減が期待できそうなので、さっそくテストコードを書いてみました。

IMAPサーバへの接続設定は下表になります。

設定項目入力内容
受信メール(IMAP)サーバー)imap.mail.yahoo.co.jp
受信メール(IMAP)ポート番号993
アカウント名、または、ログイン名Yahoo! JAPAN ID
パスワードYahoo! JAPAN IDのパスワード
IMAPでの設定

IMAPサーバへの接続はPOPサーバへの接続の時とあまり変わりません。メール検索条件であるSearchTermオブジェクトを生成して、メッセージを取得しているところが今回のポイントです。

  • Subjectに “の日記” が含まれていること。
  • 受信日が本日であること。(時分秒の指定は無視されるため、EQ(一致)、 new Date()で機能します)
  • Fromに “no-reply@plaza.rakuten.co.jp” が含まれていること。

今回はこの3つの条件で、記事投稿用メールアドレスについてのメールかどうかを判定しています。

// https://mvnrepository.com/artifact/com.sun.mail/javax.mail
@Grab(group='com.sun.mail', module='javax.mail', version='1.6.2')

import javax.mail.Folder
import javax.mail.Message
import javax.mail.Session
import javax.mail.Store
import javax.mail.URLName
import javax.mail.search.AndTerm
import javax.mail.search.ComparisonTerm
import javax.mail.search.FromStringTerm
import javax.mail.search.ReceivedDateTerm
import javax.mail.search.SearchTerm
import javax.mail.search.SubjectTerm

String host = 'imap.mail.yahoo.co.jp'
String user = '******'
String pass = '******'
String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";

Properties properties = System.properties
properties.setProperty('mail.imap.socketFactory.class', SSL_FACTORY)
properties.setProperty('mail.imap.ssl.trust', '*')
Session session = Session.getDefaultInstance(properties)

URLName urln = new URLName('imap', host, 993, null, user, pass)
Store store = session.getStore(urln)
store.connect()
Folder inbox = store.getFolder("INBOX")
inbox.open(Folder.READ_ONLY)

SearchTerm[] stlst = [
        new SubjectTerm('日の日記'),
        new ReceivedDateTerm(ComparisonTerm.EQ, new Date()),
        new FromStringTerm('no-reply@plaza.rakuten.co.jp')
    ]
SearchTerm st = new AndTerm(stlst)
    
inbox.search(st).each { Message msg->
    println("Subject : ${msg.getSubject()}")
    println(" From    : ${msg.getFrom()}")
    println(" ReplyTo : ${msg.getReplyTo()}")
}

inbox.close()
store.close()

検索条件は抽象クラスSearchTermを継承した条件クラスのオブジェクトを生成し、単体あるいは組み合わせて指定します。私自身、すこし手こずったので全体像をメモしておきます。
ちなみに日本語で検索するのは思ったように動きませんでした。コツがあるのかもしれません。

抽象クラスメモ
javax.mail.search.SearchTerm
 AddressTerm
  FromTermfromに指定Addressが存在するか。ざっくり絞り込むならFromStringTerm の方が向いていそう。
  RecipientTermrecipientに指定Addressが存在するか。ざっくり絞り込むならRecipientStringTermの方が向いていそう。
 ComparisonTerm比較条件の抽象クラス
  DateTerm日付比較
   ReceivedDateTerm受信日で絞り込む。条件は ComparisonTerm で定義されているものを指定。日付はDate型で指定するが時分秒は無視される。
   SentDateTerm
  IntegerComparisonTerm数値比較
   MessageNumberTerm
   SizeTerm
  FlagTermメッセージのフラグ状態で抽出する。フラグの種類はjavax.mail.Flagsで定義されている。
  ModifiedSinceTerm
  OlderTerm
  YoungerTerm
  StringTerm指定の文字列が含まれているか。
   AddressStringTerm
    FromStringTerm
    RecipientStringTerm
   BodyTerm
   HeaderTerm
   MessageIDTerm
   SubjectTerm
 AndTerm2つ以上の条件オブジェクトをAND結合する。
 OrTerm2つ以上の条件オブジェクトをOR結合する。
 NotTerm指定した条件オブジェクトの論理を反転する。
型階層イメージ

関連記事

https://www.komina.info/archives/887
]]>
/archives/901/feed/ 0
楽天ブログに自動で記事を投稿したい /archives/887/ /archives/887/#respond Thu, 26 Aug 2021 09:55:11 +0000 https://www.komina.info/?p=887 楽天ブログではメールで記事を投稿する機能があります。この機能を使えばプログラムからブログ投稿が簡単にできそうです。ブラウザをコントロールしてweb画面から記事を投稿するよりはるかに簡単そうです。

ということで調べてみたのですが、「決められたアドレスへメール送信することで投稿」というものではなく、「楽天ブログから配信されるメールに書かれたアドレスへメールを送信することで投稿」できる仕組みのようなのです。

配信についてはブログの管理画面から、何曜日に送ってほしいか設定できるようになっていました。試しに設定してみると、該当する曜日の朝9時過ぎにメールが届きました。

Subject: 19日の日記
Date: Thu, 19 Aug 2021 09:06:57 +0900 (JST)
From: info@plaza.rakuten.co.jp no-reply@plaza.rakuten.co.jp
Reply-To: ******1234@mb.plaza.rakuten.co.jp
To: *******9999@yahoo.co.jp

<※このメールに返信しても日記を更新することは出来ません※>

<日記を更新する場合は、 *******1234@mb.plaza.rakuten.co.jp にメールを 送信して下さい>
<メールの件名が日記の件名、メールの本文が日記の本文として、日記が更新さ れます>
<画像を添付すると、画像付きの日記が更新できます>

<配信停止URL…

このメールに返信することで投稿を行えることは確認できました。反映もそこそこ早いみたいです。
しかしプログラムから自動でメール送信するという目標への技術的なハードルが一気に高くなってしまいました。

私は楽天用にYahooメールを使っているので、Yahooの受信箱を巡回、条件を満たすメールから記事投稿用のアドレスを取得することになります。

まずは下記のページを参考にpopアクセスを有効にしておく必要があります。(すでにメールクライアントを使っている人は不要です)

メールソフトで送受信するには(Yahoo!メールアドレスの場合) (yahoo-net.jp)

https://support.yahoo-net.jp/PccMail/s/article/H000007321
設定項目入力内容
受信メール(POP)サーバーpop.mail.yahoo.co.jp
受信メール(POP)ポート番号995
受信メール(POP)通信方法SSL(暗号化)
アカウント名、または、ログイン名Yahoo! JAPAN ID
パスワードYahoo! JAPAN IDのパスワード
popでの設定

ということで、Yahooメールの受信箱から投稿用のメールを探すプログラムを作ってみました。

// https://mvnrepository.com/artifact/com.sun.mail/javax.mail
@Grab(group='com.sun.mail', module='javax.mail', version='1.6.2')

import javax.mail.Folder
import javax.mail.Message
import javax.mail.Session
import javax.mail.Store
import javax.mail.URLName
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.regex.Matcher
import java.util.regex.Pattern
import javax.mail.Address


String host = 'pop.mail.yahoo.co.jp'
String user = '******'
String pass = '******'
String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory"

Properties properties = System.properties
properties.setProperty("mail.pop3.socketFactory.class", SSL_FACTORY)
properties.setProperty('mail.pop3.port', '995')
properties.setProperty('mail.pop3.socketFactory.port', '995')
properties.setProperty("mail.pop3.ssl.trust", "*")  // Trust all Servers

Session session = Session.getDefaultInstance(properties)

URLName urln = new URLName('pop3', host, 995, null, user, pass)
Store store = session.getStore(urln)

store.connect()
Folder inbox = store.getFolder('Inbox')
inbox.open(Folder.READ_ONLY)

DateFormat sdf = new SimpleDateFormat('yyyy/MM/dd')
Date today = new Date()
String todayYmd = sdf.format(today)
println("今日の日付 : ${todayYmd} の投稿メール")
println("メッセージ数 : ${inbox.messageCount}")

Pattern ptn = ~/^(?<day>[0-9]{1,2})日の日記$/

Message[] messages = inbox.getMessages()
messages.each { Message it->
    Matcher mt = ptn.matcher(it.subject)
    Date sentDate = it.getSentDate()
    if (mt.find() && sentDate != null) {
        println("Subject : ${it.getSubject()}")
        String sentYmd = sdf.format(sentDate)
        int day = mt.group('day').toInteger()
        if (todayYmd == sentYmd) {
            println(" From    : ${it.getFrom()}")
            println(" ReplyTo : ${it.getReplyTo()}")
        }
    }
}

inbox.close(true)
store.close()

ユーザIDはメールアドレスではなく、あくまでID部分になります。
また、本格的に使う場合は、try-catchを使ってinbox.closeやstore.closeはfinally句に書くとかした方がいいと思います。

そんなこんなで投稿用のメールアドレスの取得が確立できました。

参考にしたページ

JavaMail POP3 over SSL Connect failed;

https://stackoverflow.com/questions/22037210/javamail-pop3-over-ssl-connect-failed

java mail pop3 imap 受信

https://qiita.com/koryo/items/0fa66023d7ca8efcff41

A JavaMail POP reader example (pop3 reader)

https://alvinalexander.com/java/javamail-pop-pop3-reader-email-inbox-example/

追記

うまく動かないときはデバッグ出力を有効にしましょう。

properties.setProperty("mail.debug", "true")

IDやパスワードが正しいはずなのにどうしてもエラーになってしまう場合、こんなエラーメッセージ「(#AUTH403) Incorrect username or password.」が出ていませんか?

これは海外からのアクセス制限が有効になっているときの応答らしいです。クラウド上のPCでは、意図しないうちに海外からのアクセスになっていることがあります。下記ヘルプを参考に制限を無効化しましょう。

Yahoo!メール ヘルプ – 海外からのアクセス制限 –
海外からのメールソフトによるアクセスを制限できます。海外から、メールソフトやスマートフォンを利用してYahoo!メールを閲覧しない場合は、アクセス制限を設定することで、メールアカウントを第三者に利用されることや、メールの情報が漏えいする危険性を軽減できます。

https://support.yahoo-net.jp/SccMail/s/article/H000010027

関連記事

]]>
/archives/887/feed/ 0
[play1] 単発実行用のlauncherモジュールを作りました /archives/879/ /archives/879/#respond Thu, 29 Jul 2021 09:41:43 +0000 https://www.komina.info/?p=879 以前から自分で使っていたモジュールをgithubに公開することにしました。
play1-projects/launcher at main · komina77/play1-projects (github.com)

mavenへ登録するまでは手が回っていません。launcher/dist配下のzipをコピーしてきてローカルレポジトリに置いて、依存性解決してください。設定の書き方は /samples/say-hello/conf/dependencies.yml を参考に。

何ができるか

Webアプリケーションフレームワークとして作られているplayframework1を、Webサーバを起動しないで任意のクラスを実行することができます。

$ play launcher:run yourAppDir --class=info.komina.MyJob 

play1のJPA拡張やpojo拡張などの便利な機能を享受したJavaアプリケーションを作ることができるわけです。

リソースが相互に干渉しないように設定すれば、Webアプリケーションとして稼働しつつ、コマンドラインからジョブ実行。JenkinsやJP1のようなツールにジョブの実行を任せるような運用も可能です。

作るきっかけ

playframework1のジョブ機能を利用していて運用に不満を持ったのがきっかけです。ジョブ機能は正常に動いているときは確かに便利なのですが、実行状況を把握したり、任意のジョブをリカバリのためにリランさせたりといったことは、あらかじめプログラミングされていないとできません。

そこでジョブ類をWebアプリケーションから分離できたらな、と考えコマンドラインからの実行を思い立ちました。

Groovyも使えます

最近はGroovyも良く使うので、Groovyスクリプトも利用できるようにしています。もともとplay1ではテンプレート機能のためにGroovyの実行環境が同梱されているので、それを呼び出す部分から流用しています。

そのため、テンプレートと同様にコンパイル後キャッシュされます。

ジョブの動作テスト用に使う

モジュールのドキュメントにも書きましたが、もともとあるジョブ(play.jobs.Jobを継承)にmainメソッドを追加する方法がおすすめです。mainメソッドにはジョブを起動するだけのコードを書き、メインの処理はdoJob()内に記述します。

コマンドラインからジョブだけを直接実行できるので、試行錯誤が効率的に行えます。

]]>
/archives/879/feed/ 0
[groovy] 再帰的にファイルを検索する /archives/690/ /archives/690/#respond Sat, 23 Jan 2021 14:24:36 +0000 https://www.komina.info/?p=690 Fileオブジェクトのgroovy拡張でとても便利なメソッドがある。

配下のファイルを総当たりしたいときはFileType.FILESを、ディレクトリを総当たりしたいときはFileType.DIRECTORIESを指定する。区別せずに走査することもできる。

import static groovy.io.FileType.*

new File('.').eachFileRecurse(FILES) { 
    File file->
    if (file.name ==~ /\.html$/) {
        proc(file)
    }
}
]]>
/archives/690/feed/ 0
[groovy]do-whileの書き方 /archives/625/ /archives/625/#respond Wed, 13 Jan 2021 03:03:41 +0000 https://www.komina.info/?p=625 groovyにはdo-while構文がありません。javaにはあるのであえて捨てたということなのでしょう。

とりあえず1回実行、エラーならリトライ、みたいな処理で便利に使っていたのですが、逆に言うとそれくらいしか用途が無いということなのかも。。

groovyでの書き方ですがクロージャを使うと割とスッキリ書けるようです。

This is the closest it can get to purely language syntax based do-while in Groovy: —
The last statement within curly braces (within closure) is evaluated as a loop exit condition.

loops – Elegant way for do … while in groovy – Stack Overflow
while ({
    x.doIt()
    !x.isFinished()
}()) continue

クロージャの最後の式の評価値が戻り値となるので、繰り返すべきならtrueを返すようにすると。そうすると whileの評価式が true になり contiue、つまり繰り返しになります。

クロージャの中身が多いときはインラインで書かずに別途定義する方法がよさそうですね。

Closure<Boolean> somethingToDo = { foo ->
    foo.doIt()
    !foo.isFinished()
}

while (somethingToDo(x)) continue

]]>
/archives/625/feed/ 0
GroovyでMavenで単一の配布可能なjar作成 (4) /archives/506/ /archives/506/#respond Sat, 31 Oct 2020 21:00:51 +0000 https://www.komina.info/?p=506 これまでは依存したすべてのjarを一つにまとめた fat-jar を作成する方向性でした。そのため、配布された側は自分でjavaコマンドを実行する必要がありました。

windows向けバッチやunix向けスクリプトを生成してくれるプラグインがあることを知ったので、それを適用してみることにしました。

ツールを利用する側はjarの実行の仕方なんか興味ないわけで、やはり即実行可能なバッチやスクリプトの形であるに越したことないですもんね。

Maven でアプリケーション実行用バッチファイルを作る – A Memorandum

https://blog1.mammb.com/entry/20101206/1291619754

まず、fat-jarが不要になるので、project>build>plugins内のmaven-assembly-pluginの定義は削除します。

<!-- fat-jarが不要になるので、maven-assembly-pluginの定義は削除
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>single</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <descriptorRefs>
                <descriptorRef>jar-with-dependencies</descriptorRef>
            </descriptorRefs>
            <archive>
                <manifest>
                    <mainClass>MediaRqstGetter</mainClass>
                </manifest>
            </archive>
        </configuration>
    </plugin>
-->

代わりに project>dependencies内にappassembler-maven-pluginの依存定義に追加します。

<!-- dependencies -->
        <!-- https://mvnrepository.com/artifact/org.codehaus.mojo/appassembler-maven-plugin -->
        <dependency>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>appassembler-maven-plugin</artifactId>
            <version>2.1.0</version>
        </dependency>

次に project>build>plugins配下に appassembler-maven-pluginの処理の定義を記述します。
シンプルな実行スクリプトの他に、デーモン(Javaで作ったサービス)のラッパーやなんかもサポートしているみたいですが、今回は単純な方で。

    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>appassembler-maven-plugin</artifactId>
        <configuration>
            <programs>
                <program>
                    <mainClass>info.komina.MyApp</mainClass>
                    <id>runapp</id>
                </program>
            </programs>
            <binFileExtensions>
                <unix>.sh</unix>
                <windows>.cmd</windows>
            </binFileExtensions>
            <repositoryName>lib</repositoryName>
            <repositoryLayout>flat</repositoryLayout>
            <useWildcardClassPath>true</useWildcardClassPath>
            <environmentSetupFileName>setenv</environmentSetupFileName>
            <includeConfigurationDirectoryInClasspath>false</includeConfigurationDirectoryInClasspath>
        </configuration>
        <executions>
            <execution>
                <goals>
                    <goal>assemble</goal>
                </goals>
                <phase>package</phase>
            </execution>
        </executions>
    </plugin>
configulationタグ説明
programs>program>mainClass実行クラスを完全修飾で指定
programs>program>idスクリプトの名前になります
binFileExtensions任意。生成するスクリプトの拡張子をプラットフォーム毎に指定します。デフォルトでunixは無し、windowsはbatです。
repositoryName任意。依存するjarを配置するフォルダ名です。デフォルトは/repoです。
repositoryLayout任意。依存するjarの置き方です。デフォルトは階層構造です。ここでは階層構造だとクラスパスの文字列長が長くなり問題が起きるので、useWildcardClassPathのtrue設定と合わせてflat指定しています。
useWildcardClassPath任意。クラスパスでワイルドカードを使用するかの設定。デフォルトはfalseです。
environmentSetupFileName任意。環境設定用スクリプトの名前です。生成されるスクリプトを実行する際にファイルが存在すれば呼び出されます。
includeConfigurationDirectoryInClasspath任意。コンフィグファイル用のディレクトリをクラスパスに含める機能がありますが今回は不要なのでfalseにしています。

ここで環境設定用スクリプトという言葉が出てきますが、これは生成されるスクリプトを実行時に呼び出されるユーザ定義のスクリプトのことです。

細かい制御をしたいとき、追加で環境変数を設定したいとき、などに利用できます。もちろんunix/windows必要なスクリプトを自分で用意しなければなりません。

環境設定用スクリプトは生成されるスクリプトと同じフォルダに置かれている必要がありますが、mavenの出力先はcleanなどの実行でしょっちゅう削除される場所ですので、別の場所からビルドのたびにコピーを行います。

ファイルのコピーはantプラグインにやらせるのが簡単ですので、maven-antrun-plugin定義に下記処理を追加します。

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <executions>
            :
            <execution>
                <goals>
                    <goal>run</goal>
                </goals>
                <phase>package</phase>
                <configuration>
                    <tasks>
                        <echo message="copy files for appassembler"/>
                        <copy toDir="${build.directory}/appassembler/bin" overwrite="yes">
                            <fileset dir="${basedir}/resources-appassembler" />
                        </copy>
                    </tasks>
                </configuration>
            </execution>
        </executions>
    </plugin>

これでpackage処理の際に/resources-appassembler配下に置いたファイルが /target/appassembler/bin 配下にコピーされるようになります。

/target/appassembler配下のファイルをまとめて配布することで、javaやgroovyで作ったプログラムを簡単に実行できる形で提供できます。

あ、unixのスクリプトについてはchmodで実行属性を付けるのを忘れずに。(環境設定用スクリプトの方は付けなくても大丈夫でした)

以上です。

参考

Appassembler :: Maven Plug-In – appassembler:assemble
(設定についての説明)

https://www.mojohaus.org/appassembler/appassembler-maven-plugin/assemble-mojo.html

Mavenのappassembler-maven-pluginでJAVA_HOMEを設定する方法 – Qiita

https://qiita.com/Rozsa777/items/a44b88a6daf8e1fc98e7

Re: [mojo-user] Appassembler-maven-plugin JAVA_HOME question

https://www.mail-archive.com/user@mojo.codehaus.org/msg03990.html

https://www.komina.info/archives/482
]]>
/archives/506/feed/ 0
GroovyでMavenで単一の配布可能なjar作成 (3) /archives/482/ /archives/482/#respond Wed, 14 Oct 2020 06:41:55 +0000 https://www.komina.info/?p=482 タイトルとは少しずれてきましたが、WSDLからjavaソースを生成するというレアなタスクをmavenで対応できたのでメモしておきます。

もともとのプロジェクトではantビルドでexecタスクからバッチファイルを実行していました。

	<target name="wsdl_to_java">
		<exec dir="./jaxb" executable="cmd">
			<arg line="/c wsdlToJava.bat ../${SRC_DIR} ../${WSDL_FILE}"/>
		</exec>
	</target>
set LIB_HOME=.
set JAXB_LIBS=%LIB_HOME%\jaxb-api.jar
set JAXB_LIBS=%JAXB_LIBS%;%LIB_HOME%\jaxb-xjc.jar
set JAXB_LIBS=%JAXB_LIBS%;%LIB_HOME%\jaxb-impl.jar

java -Dfile.encoding=UTF-8 -cp %JAXB_LIBS% com.sun.tools.xjc.XJCFacade -d %1 %2 -b custom-binding.xml -wsdl -target 2.1

こんな感じです。

既にgroovyをコンパイルするために maven-antrun-plugin プラグインを導入済ですので、ここへタスクを追加します。

生成されるjavaソースはgroovy側から参照するので、groovyのコンパイルより前にタスクを追加します。

バッチファイルにはなっていますが、Ant:java プログラムなので javaタスクを使用します。実行に必要なjarは諸般の理由で java6フォルダからプロジェクトの/toolsフォルダへコピーしてあります。

<tasks>
    <java classname="com.sun.tools.xjc.XJCFacade"
        classpath="${basedir}/tools/jaxb-api.jar;
                   ${basedir}/tools/jaxb-xjc.jar;
                   ${basedir}/tools/jaxb-impl.jar;" >
        <sysproperty key="file.encoding" value="UTF-8" />
        <arg value="-d" />
        <arg value="${basedir}/src/main/java"/>
        <arg value="${basedir}/src/main/WSDL/xxxx/xxxx_External.wsdl"/>
        <arg value="-b"/>
        <arg value="${basedir}/tools/custom-binding.xml"/>
        <arg value="-wsdl"/>
        <arg value="-target"/>
        <arg value="2.1"/>
    </java>
    <mkdir dir="${basedir}/src/main/groovy" />
    <taskdef name="groovyc"
        classname="org.codehaus.groovy.ant.Groovyc">
  続く

/src/main/java へ生成されたjavaソースが出力されます。コンパイルについては記述の必要はなかったです。

参考にしたサイト

Ant「javaタスク」メモ(Hishidama’s ant-java Memo)

https://www.ne.jp/asahi/hishidama/home/tech/ant/tag/java.html

]]>
/archives/482/feed/ 0
GroovyでMavenで単一の配布可能なjar作成 (2) /archives/470/ /archives/470/#respond Mon, 12 Oct 2020 08:04:26 +0000 https://www.komina.info/?p=470 前回で配布可能なjar作成については解決したかと思ったのですが、またまた問題が発生。解決に至れましたのでメモしておきます。

そもそも、こういった依存するライブラリをすべて含めた配布可能な単一のjarのことをfat-jarと呼ぶそうです。検索するときは fatjar で調べると有用な情報が見つかります。

さてどんな問題が起きたかなんですが、mavenレポジトリに置いてないようなライブラリ(ojdbc.jar、マイナーなjar、個人的なjarなど)の場合、dependenciesには以下のように <scope>system</scope> とし、配置場所を<systemPath>タグでダイレクトに指定します。(プロジェクトディレクトリを参照すると警告が出ちゃうけど気にせずに)

Mavenでローカルjarを追加する

https://qiita.com/inuDrill/items/bd2843e812016a3a900b
        <dependency>
            <groupId>com.oracle.jdbc</groupId>
            <artifactId>ojdbc</artifactId>
            <version>1</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/ojdbc6_g.jar</systemPath>
        </dependency>

これでコンパイルしたりする分には十分なのですが、これらのライブラリはjar-with-dependenciesの対象外となっているため、このままではfatjarは作れません。

そこで maven-dependency-plugin というプラグイン定義を追加します。

            <!-- https://maven.apache.org/plugins/maven-dependency-plugin/usage.html -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>unpack-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/classes</outputDirectory>
                            <includeArtifactIds>ojdbc</includeArtifactIds>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

こうすることで、systemライブラリがclasses配下に展開され、まとめてjar作成されるようになります。

参考

Maven Dependency Plugin で system スコープの jar ファイルもひとつにまとめる

https://qiita.com/niwasawa/items/766ba558678ac9bb85aa

Apache Maven Dependency Plugin – Usage

https://maven.apache.org/plugins/maven-dependency-plugin/usage.html

https://www.komina.info/archives/482
次へ
]]>
/archives/470/feed/ 0
Mavenのテストでsrc/test/resoucesにあるファイルを参照する /archives/389/ /archives/389/#respond Mon, 05 Oct 2020 05:08:50 +0000 https://www.komina.info/?p=389 GroovyでMavenプロジェクトの続き。
Groovyでテストを書いてみて、資材を使ったテストを記述する方法が分かったのでメモ。ファイルを食わせて結果を検証する系のテスト用。

import spock.lang.*

class AppTest extends Specification {
    def "ファイル参照"() {
        // 絶対パスの取得
        URL url = this.class.getResource('sample.txt')
        println "sample.txtの絶対パス: ${url.path}"

        // ファイルの読出し
        this.class.getResourceAsStream('sample.txt').eachLine {
            println it
        }
        expect:
        0==0
    }
}

]]>
/archives/389/feed/ 0