しおしお

IntelliJ IDEAのことなんかを書いてます

IntelliJさん標準で背景画像が設定できるようになってた

IntelliJさんの最新のEAP(build no:162.646.4)にしてみたら、
標準機能でIDEの背景画像設定できるようになってたので試してみた。

背景画像を設定する画面を開く

Find Actionから「Set Background Image」を検索して、設定画面を開きます。

下の画像の設定画面が開かれるので、お好きな画像を設定します。
f:id:sioiri:20160602114411j:plain



オプションは試してないのでよくわかりません。

背景画像が設定された!

こんな感じになります。

f:id:sioiri:20160602094103p:plain

おわり。

JBeretでジョブ定義をロジックで構築して実行する

JBeretでは、ロジックでジョブ定義を構築することが出来るみたいなので試してみた。
これを使うと、xmlでジョブ定義をすることなくバッチを実行することができるようになる。
ただし、JSRで定められているわけではないので、バッチ実行時にJBeretを直接扱う必要がある。

ドキュメントはこちら→Programmatic Job Definition with Java | JBeret User Guide

ジョブ定義を構築する

JobBuilderやStepBuilderを使ってジョブ定義を構築していく感じになる。
batchletやreaderなんかに指定する名前は、xmlに定義する時と同じようにBeanの名称を設定する。

この例だと、sample-jobジョブに2つのステップがあって、batchlet-step -> chunk-stepの順に実行される。

val job = JobBuilder("sample-job")
    .step(
        StepBuilder("batchlet-step")
            .batchlet("sampleBatchlet")
            .next("chunk-step")
            .build()
    )
    .step(
        StepBuilder("chunk-step")
            .reader("sampleItemReader")
            .writer("sampleItemWriter")
            .itemCount(2)
            .build()
    )
    .build()

ジョブを実行する

ジョブを実行するには、BatchRuntimeから取得したJobOperatorをJBeretの実装(JobOperatorImpl)にダウンキャストしてあげる。
JobOperatorImplには、JobBuilderで構築したJobオブジェクトを指定してジョブを実行するメソッドが用意されているので、それを使ってジョブを実行してあげる。

val jobOperator = BatchRuntime.getJobOperator() as JobOperatorImpl
val executionId = jobOperator.start(job, null)

ソースは全体はこちら→GitHub

おわり。

Gradleで似たようなタスクをグループ化する

Gradleのグループ化機能を使うと同じ種類位のタスクをグループでまとめられるようになる。
IntelliJを使っている場合、Gradleウィンドウではグループ毎にタスクがまとめられて表示されるので、ビルドスクリプト側でグループ化しておくとIntelliJさんでもタスクが探しやすくなる感じです。

タスクをグループ化するには、タスクのgroupプロパティにグループ名を設定します。

この場合、run_batchletとrun_chunkのタスクがapplicationグループとなる。

task run_batchlet(type: JavaExec) {
  classpath = sourceSets.main.runtimeClasspath
  main = 'org.jberet.se.Main'
  args 'batchlet-next-element-sample'
}

task run_chunk(type: JavaExec) {
  classpath = sourceSets.main.runtimeClasspath
  main = 'org.jberet.se.Main'
  args 'chunk-next-element-sample'
}
[run_batchlet, run_chunk]*.group = 'application'

IntelliJさんのGradleウィンドウでの表示

applicationグループにまとまってますね。
f:id:sioiri:20160506075345p:plain

gradle tasksの結果

こちらも、ちゃんとグループにまとめられています。
ちなみにタスクの説明は、タスクのdescriptionプロパティに設定してあげると表示されるようになります。

Application tasks
-----------------
installApp - Installs the project as a JVM application along with libs and OS specific scripts.
run - Runs this project as a JVM application
run_batchlet
run_chunk

おわり。

sphinxからjavadocへのリンク簡単に貼れる拡張

Sphinxのドキュメントから、Javadocページ編のリンクを貼るのを簡単にするjavasphinx User’s Guide — javasphinx 0.9.8 documentationという拡張です。

この拡張を使うと、Javadocのトップページへのパスをconf.pyに設定して置けるので、
ここのページではクラスの完全修飾名を書くだけでよく割りと簡単にリンクが作成できます。

インストー

拡張のページに有るようにpipかeasy_installを使ってインストールします。

pip install javasphinx

easy_install -U javasphinx

javasphinx拡張を有効にする

拡張を使うためには、conf.pyのextensionsにこの拡張を追加する必要があります。

初期状態の場合、conf.pyに空の配列が設定されたextensionsがあるので以下のようにします。

extensions = ['javasphinx']

パッケージとJavadocページとのマッピング設定

この拡張機能は、パッケージ単位にJavadocのURLを設定します。
この設定は、conf.pyに対して行います。

例えば、spring(org.springframework)のJavadocページヘのマッピングは、以下のようになります。

javadoc_url_map = {
  'org.springframework' : ('http://docs.spring.io/spring-framework/docs/current/javadoc-api/', 'javadoc'),
}

リンクを書いてみる

SpringのRestTemplateへのリンクの場合、以下のように書きます。

:java:extdoc:`org.springframework.web.client.RestTemplate`

生成されたSphinxのドキュメントは、以下のようになります。
f:id:sioiri:20160403000039p:plain

リンクテキストを変えたい場合は、以下のように「リンクテキスト <クラスの完全修飾名>」とします。

:java:extdoc:`RestTemplate <org.springframework.web.client.RestTemplate>`

f:id:sioiri:20160403000219p:plain

メソッドへのリンクは、完全修飾名の後にドット(.)+メソッド名で書きます。
メソッドの引数も正確に書く必要があります。

* :java:extdoc:`String#toString <java.lang.String.toString()>`
* :java:extdoc:`String#replace <java.lang.String.replace(java.lang.CharSequence,%20java.lang.CharSequence)>`

生成されたSphinxのドキュメントは、以下のようになります。
f:id:sioiri:20160403001216p:plain

おわり。

IntelliJ IDEA16でMavenプロジェクトが開けない場合の対処方法

IntelliJ IDEAのバージョン16で、Mavenプロジェクトを開こううとした時に下のエラーが出た場合の対処方法。

com.intellij.execution.ExecutionException:
java.lang.UnsupportedClassVersionError: org/jetbrains/idea/maven/server/RemoteMavenServer : Unsupported major.minor version 52.0

原因

IntelliJ IDEA16からJava8でビルドさていているので、pomを開く時のJavaのバージョンも8にする必要があります。

pomを開くときにJava8でビルドされたMavenプロジェクトのAPIを呼び出しているのが原因なんだけど、
なんで対応してないバージョン普通に選べるのか謎な感じある。

対策

この手順で、pom開く際のjdkを変更する必要があります。

デフォルト設定画面を開く

Welcome画面から設定画面を開くとデフォルトの設定値を変更できるので、Welcome画面から設定画面を開く。
f:id:sioiri:20160223113310p:plain

MavenJDK for importerをJava8に変更する

画像のように変更する。
f:id:sioiri:20160223113311p:plain

おわり。

IntelliJ IDEAのタブ設定で未保存のファイルをわかるようにする

設定画面のEditor -> General -> Editor Tabsで設定する。
Tab AppearanceのMark modified tabs with asteriskのチェックをオンにすると、未保存のファイルのタブにアスタリスクが表示されるようになる。

表示のされ方はこんな感じ。
f:id:sioiri:20160127213851p:plain

おわり。

ServletInputStreamをラップして、リクエストのボディをログに出力してみる

お仕事で、リクエストボディのJSONの内容をログに出力したいんだと言われたので作ってみた。

実現方法

1. FilterでServletRequestをラップする
2. ServletRequestのラッパーでは、InputStreamのgetterでログ出力機能をもったServletInputStream実装を返す
3. ログ出力機能を持ったServletInputStream実装では、read要求の中で読み込んだデータをログに出力する。

Filterでボディの内容を全部ログに出力してから、ServletRequestラップして後続に処理を移譲するでも良いんだけど、ボディが全部到達するまでFilterで処理止まってしまうんで、read要求の中でログ出力するようにしている感じです。

WildFlyでの使う場合

WildFly9.0.2で試したところ、デフォルトの構成だとServletRequestをラップすると怒られるので、以下コマンドでラップしたRequestやResponseを許容するようにしてあげる必要があるみたいです。

$ ./jboss-cli.sh
[disconnected /] connect
[standalone@localhost:9990 /] cd subsystem=undertow/servlet-container=default
[standalone@localhost:9990 servlet-container=default] :write-attribute(name=allow-non-standard-wrappers, value=true)

ラッパーを許容しない設定の場合に発生する例外はこれ。

Exception handling request to /loggingfilter-1.0-SNAPSHOT/app: java.lang.IllegalArgumentException: UT010023: Request siosio.LoggingFilter$RequestWrapper@77ab9fb3 was not original or a wrapper
実装例
@WebFilter(urlPatterns = arrayOf("/app/*"))
class LoggingFilter : Filter {
  override fun destroy() {
  }

  override fun init(filterConfig: FilterConfig?) {
  }

  override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
    // ServletRequestをラップして後続のフィルタに処理を移譲
    chain.doFilter(RequestWrapper(request as HttpServletRequest), response)
  }

  class RequestWrapper(val request: HttpServletRequest) : HttpServletRequest by request {
    override fun getInputStream(): ServletInputStream? {
      // ログ出力機能付きのServletInputStreamを返す
      return LoggingInputStream(request.inputStream)
    }
  }

  class LoggingInputStream(val inputStream: ServletInputStream) : ServletInputStream() {

    companion object {
      val logger: Logger = Logger.getLogger(LoggingInputStream::class.java)
    }

    override fun isReady(): Boolean {
      return inputStream.isFinished
    }

    override fun isFinished(): Boolean {
      return inputStream.isFinished
    }

    override fun setReadListener(readListener: ReadListener?) {
      inputStream.setReadListener(readListener)
    }

    override fun read(b: ByteArray?): Int {
      return inputStream.read(b)
    }

    override fun read(b: ByteArray, off: Int, len: Int): Int {
      // 読み込んだ内容やバイト数をログに出力する
      val result = inputStream.read(b, off, len)
      logger.info("read http body. offset:$off, length:$result, data:${b.copyOf(result).toString("UTF-8")} ")
      return result
    }

    override fun readLine(b: ByteArray?, off: Int, len: Int): Int {
      return super.readLine(b, off, len)
    }

    override fun read(): Int {
      return inputStream.read()
    }
  }
}
動かした結果

こんな感じにログに出力されますよと。

16:59:52,263 INFO  [siosio.LoggingFilter$LoggingInputStream] (default task-4) read http body. offset:0, length:31, data:{   "name": "あいうえお" } 

おわり。