SpringアプリのテストでHikariDataSource has been closedなどと言われたのでなんとかした

TL; DR

  • Springのテスト時に使われるコンテキストキャッシュ DefaultContextCache はデフォルトの保持数が32で、それを超えると古いコンテキストが破棄される。そのときにDBのコネクションプールも破棄されてしまって困る。
  • キャッシュの保持数はSpringのプロパティspring.test.context.cache.maxSize で設定できる。

環境

本題

なにごと?

JUnitでSpring Bootアプリのテストをゴリゴリ追加していたところ、いきなりエラーが。

logger:o.s.b.w.embedded.tomcat.TomcatWebServer  LEVEL:INFO  thread:main msg:Tomcat started on port(s): 52394 (http) with context path ''
logger:xxx.xxx.XxxTest                          LEVEL:INFO  thread:main msg:Started XxxTest in 1.134 seconds (JVM running for 65.714)
logger:o.s.s.concurrent.ThreadPoolTaskExecutor  LEVEL:INFO  thread:main msg:Shutting down ExecutorService 'applicationTaskExecutor'
logger:com.zaxxer.hikari.HikariDataSource       LEVEL:INFO  thread:main msg:HikariPool-1 - Shutdown initiated...
logger:com.zaxxer.hikari.HikariDataSource       LEVEL:INFO  thread:main msg:HikariPool-1 - Shutdown completed.
logger:o.s.test.context.TestContextManager      LEVEL:WARN  thread:main msg:Caught exception while invoking 'beforeTestMethod' callback on TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@347d1586] for test method [com.intuit.karate.junit5.Karate xxx.xxx.XxxTest.testXxx()] and test instance [xxx.xxx.XxxTest.testXxx@5e462cc]
org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is java.sql.SQLException: HikariDataSource HikariDataSource (HikariPool-1) has been closed.
        at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:309)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.startTransaction(AbstractPlatformTransactionManager.java:400)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373)

下記の行に注目。DBへのコネクションを開始しようとしたのにDataSourceが閉じられてしまっているのでエラー、ということらしい。

Could not open JDBC Connection for transaction; nested exception is java.sql.SQLException: HikariDataSource HikariDataSource (HikariPool-1) has been closed.

DataSourceが勝手に閉じられるのはよろしくないので、なぜ閉じられているのかを調べることにする。

調べてみる

HikariCPで何か起こっているということはわかったので、HikariCPのログレベルを debug に変更し、ログの情報量を増やしてみる。

application.yamlを下記のように設定して再度テストを実行。

logging:
  level:
    com.zaxxer.hikari: debug

そうすると、テスト開始時にDBとのコネクションが閉じられている様子が表示される。(connection evicted) ですって。

logger:o.s.b.w.embedded.tomcat.TomcatWebServer  LEVEL:INFO  thread:main msg:Tomcat started on port(s): 54354 (http) with context path ''
logger:xxx.xxx.XxxTest    LEVEL:INFO  thread:main msg:Started XxxTest in 1.068 seconds (JVM running for 64.084)
logger:o.s.s.concurrent.ThreadPoolTaskExecutor  LEVEL:INFO  thread:main msg:Shutting down ExecutorService 'applicationTaskExecutor'
logger:com.zaxxer.hikari.HikariDataSource       LEVEL:INFO  thread:main msg:HikariPool-1 - Shutdown initiated...
logger:com.zaxxer.hikari.pool.HikariPool        LEVEL:DEBUG thread:main msg:HikariPool-1 - Before shutdown stats (total=10, active=0, idle=10, waiting=0)
logger:com.zaxxer.hikari.pool.PoolBase          LEVEL:DEBUG thread:HikariPool-1 connection closer msg:HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@36d7a68a: (connection evicted)
logger:com.zaxxer.hikari.pool.PoolBase          LEVEL:DEBUG thread:HikariPool-1 connection closer msg:HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@1eeacb6e: (connection evicted)
logger:com.zaxxer.hikari.pool.PoolBase          LEVEL:DEBUG thread:HikariPool-1 connection closer msg:HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@102d26c3: (connection evicted)
logger:com.zaxxer.hikari.pool.PoolBase          LEVEL:DEBUG thread:HikariPool-1 connection closer msg:HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@1412a01e: (connection evicted)
logger:com.zaxxer.hikari.pool.PoolBase          LEVEL:DEBUG thread:HikariPool-1 connection closer msg:HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@51102cf8: (connection evicted)
logger:com.zaxxer.hikari.pool.PoolBase          LEVEL:DEBUG thread:HikariPool-1 connection closer msg:HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@19692c0f: (connection evicted)
logger:com.zaxxer.hikari.pool.PoolBase          LEVEL:DEBUG thread:HikariPool-1 connection closer msg:HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@600b1adf: (connection evicted)
logger:com.zaxxer.hikari.pool.PoolBase          LEVEL:DEBUG thread:HikariPool-1 connection closer msg:HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@61984490: (connection evicted)
logger:com.zaxxer.hikari.pool.PoolBase          LEVEL:DEBUG thread:HikariPool-1 connection closer msg:HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@313fd3b7: (connection evicted)
logger:com.zaxxer.hikari.pool.PoolBase          LEVEL:DEBUG thread:HikariPool-1 connection closer msg:HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@61e698cb: (connection evicted)
logger:com.zaxxer.hikari.pool.HikariPool        LEVEL:DEBUG thread:main msg:HikariPool-1 - After shutdown stats (total=0, active=0, idle=0, waiting=0)
logger:com.zaxxer.hikari.HikariDataSource       LEVEL:INFO  thread:main msg:HikariPool-1 - Shutdown completed.
logger:o.s.test.context.TestContextManager      LEVEL:WARN  thread:main msg:Caught exception while invoking 'beforeTestMethod' callback on TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExe
cutionListener@7ef7161d] for test method [com.intuit.karate.junit5.Karate xxx.xxx.XxxTest.testXxx()] and test instance [xxx.xxx.XxxTest@55b5625d]
org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is java.sql.SQLException: HikariDataSource HikariDataSource (HikariPool-1) has been closed.
        at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:309)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.startTransaction(AbstractPlatformTransactionManager.java:400)

HikariCPのソースコード内を (connection evicted) で検索すると、HikariPool の下記のメソッドが見つかる。

public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateListener
{
   // 中略
   /** {@inheritDoc} */
   @Override
   public void softEvictConnections()
   {
      connectionBag.values().forEach(poolEntry -> softEvictConnection(poolEntry, "(connection evicted)", false /* not owner */));
   }

https://github.com/brettwooldridge/HikariCP/blob/ed2da5f1f4ef19f871fac12effc0b199706905dc/src/main/java/com/zaxxer/hikari/pool/HikariPool.java#L375

どうやらDataSourceが閉じられる際には、ここを通っているらしい。 IntelliJ IDEAで上記メソッドにブレークポイントを仕掛けて、コールスタックを追ってみる。

f:id:ser1zw:20210806023511p:plain

最初の HikariPool#shutdown, HikariDataSource#close は正常なコネクションプール終了処理であり、特に気になる部分は無し。

そのまま順番に追っていくと、DefaultContextCache.LruCache#removeEldestEntryDefaultContextCache.this.remove(〜) しているところが見つかる。

f:id:ser1zw:20210806022737p:plain

public class DefaultContextCache implements ContextCache {
    // 中略
    private class LruCache extends LinkedHashMap<MergedContextConfiguration, ApplicationContext> {
        LruCache(int initialCapacity, float loadFactor) {
            super(initialCapacity, loadFactor, true);
        }

        protected boolean removeEldestEntry(Entry<MergedContextConfiguration, ApplicationContext> eldest) {
            if (this.size() > DefaultContextCache.this.getMaxSize()) {
                DefaultContextCache.this.remove((MergedContextConfiguration)eldest.getKey(), HierarchyMode.CURRENT_LEVEL); // ←これ
            }

https://github.com/spring-projects/spring-framework/blob/7c2a72c9b43d066ae9e71d4f39d7bab8f6d9c2ff/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java#L337

DefaultContextCacheはspring-testでのテストの際にアプリケーションコンテキストのキャッシュを保持するクラスで、LruCacheはその内部クラス。 LRUっていうぐらいだし、キャッシュがいっぱいになったら古いものを削除するんだろうなと想像できる。実際、削除処理の条件も if (this.size() > DefaultContextCache.this.getMaxSize()) だし。

キャッシュ保持数はデフォルトで32の様子*1。 このことから、以下のような事象が起こっていると考えられる。

  1. テスト実行時に、アプリケーションコンテキストが DefaultContextCache に格納される。32番目のテストまでは、問題なく実行される。
  2. 33番目のテストの際、アプリケーションコンテキストを DefaultContextCache に格納しようとすると、キャッシュ保持数の最大値を超えているため、古いアプリケーションコンテキストが削除される。
  3. アプリケーションコンテキストが削除されると、それに紐づくDBのコネクションプールがクローズされる。
  4. 33番目のテストでDBに接続しようとすると、3.で閉じられたコネクションプールが使われてしまい、エラーとなる。

キャッシュから削除されないようにキャッシュ保持数の最大値を大きくすれば、ひとまずは解消できそう。 DefaultContextCache のドキュメントによると、この最大値はプロパティ spring.test.context.cache.maxSize で変更できるらしい。

最大サイズは、コンストラクターの引数として指定するか、システムプロパティまたは spring.test.context.cache.maxSize という Spring プロパティを介して設定できます。

spring.pleiades.io

application.yaml で設定したり、実行時引数で指定したりすればOK。

# application.yaml
spring.test.context.cache.maxSize: 128
$ mvn test -Dspring.test.context.cache.maxSize=128

とはいえ、これだとコンテキストが破棄されないのでリソース消費が気になる…。本当はもうちょっとまともな対応方法がほしいところではあるけど、まぁテストだしこれでいいことにする。

まとめ

つかれた。

*1:DefaultContextCacheのコンストラクタにブレークポイントを仕掛けてmaxSizeの値を確認した

ruby-buildでRubyをインストールしようとしたらoptparseが無いって言われる

なにごと?

ruby-buildrbenv install 3.0.2 したら optparse が無いって言われてエラーになるんですよ。

$ rbenv install 3.0.2
Downloading ruby-3.0.2.tar.gz...
-> https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.2.tar.gz
Installing ruby-3.0.2...

BUILD FAILED (Ubuntu 20.04 using ruby-build 20210526-11-gebdcf0c)

# (略)

./tool/file2lastrev.rb:6:in `require': cannot load such file -- optparse (LoadError)
        from ./tool/file2lastrev.rb:6:in `<main>'

環境

  • Ubuntu 20.04
  • rbenv 1.1.2-61-g585ed84
  • ruby-build 20210526-11-gebdcf0c

なんとかする

今までも ruby-build は普通に使ってたし、これまでのバージョンは入ってるのだけど。

$ rbenv versions
  2.7.2
* 3.0.0 (set by /home/*****/.rbenv/version)

ってよく見ると systemRubyが無いなって思っておもむろにインストール& rbenv で使うように切り替え。

$ sudo apt-get install ruby
$ rbenv global system
$ rbenv rehash

この状態で ruby install するとちゃんとインストールできた。

$ rbenv install 3.0.2
Downloading ruby-3.0.2.tar.gz...
-> https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.2.tar.gz
Installing ruby-3.0.2...
Installed ruby-3.0.2 to /home/*****/.rbenv/versions/3.0.2

KarateでのAPIテストと同時にAssertJ-DBでデータの確認もやりたい

はじめに

Karateを使うとWeb APIのリクエスト&レスポンスのテストがとてもいい感じにできるが、そうすると今度は「POSTされたデータがちゃんとDBに入っているかどうか」とかも確認したくなってきてしまうもの。 そこでKarateでのテストシナリオ実行の際、AssertJ-DBでのデータのアサーションが行われるようにしてみる*1

環境

ポイント

Karateのfeatureファイルでは、Java.type('パッケージ+クラス名')Javaのクラスを参照できる。 これを利用し、あらかじめDBのテスト用メソッドをAssertJ-DBで書いておいて、それをfeatureファイルから呼び出すようにする。

参考: Calling Java | Karate

サンプルコード全体は下記のとおり。

github.com

やり方

JavaでDBのアサーションを行うメソッドを定義する

テストランナーのクラスにDBのアサーションを行うメソッドを定義しておく。下記サンプルの public static void assertTable(String expectedMessage) がそれ。

package demo;

// ...

@SpringBootTest(classes = DemoApplication.class)
public class DemoTestRunner {
    private static DataSource dataSource;

    @Autowired
    private void setDataSource(DataSource dataSource) {
        DemoTestRunner.dataSource = dataSource;
    }

    @Karate.Test
    Karate testAll() {
        return Karate.run().relativeTo(getClass());
    }

    public static void assertTable(String expectedMessage) {
        var table = new Table(dataSource, "messages");
        assertThat(table).hasNumberOfRows(1)
            .row(0)
            .column("message").value().isEqualTo(expectedMessage);
    }
}

Java.type() で使いたいのでメソッドは public static にする必要があり、それに合わせて DataSource をstaticフィールドにしている。 staticなDataSourceをインジェクションできるようにするため、 setDataSource() はだいぶ無理やり感がある…。

DBのアサーション自体は普通にAssertJ-DBで書けばよい。

featureファイルのシナリオでJavaのメソッドを呼び出す

こんな感じ。

Feature: Demo

  Background:
    * url baseUrl
    * def DemoTestRunner = Java.type('demo.DemoTestRunner')

  Scenario: Add a message
    Given path '/'
    And request { "message": "Hello" }
    When method post
    Then status 200
    And DemoTestRunner.assertTable('Hello')

  Scenario: Find messages
    # ...

Backgrounddef DemoTestRunner = Java.type('demo.DemoTestRunner') することで、引数で指定されたJavaのクラス(今回は DemoTestRunner)が使えるようにしておく。 シナリオでは DemoTestRunner.assertTable('Hello') のようにしてメソッドを呼び出す。

実行してみる

いつもどおりテストランナーのクラス(今回は DemoTestRunner)を実行すればよい。

IntelliJ IDEAだとテスト結果は普通にこんな感じで出てくる。

f:id:ser1zw:20210606182242p:plain

HTMLレポートも出てくる。

f:id:ser1zw:20210606182334p:plain

試しにこんな感じでテストをわざと失敗させてみると…

  Scenario: Add a message
    Given path '/'
    And request { "message": "INVALID" }
    When method post
    Then status 200
    And DemoTestRunner.assertTable('Hello')

失敗時の情報もちゃんと出てくる。よさそう。

f:id:ser1zw:20210606182713p:plain

f:id:ser1zw:20210606182812p:plain

*1:今回はAssertJ-DBを使っているが、別にAssertJ-DBである必要は無い。Javaのテスト用ライブラリなら同じように使えるはず。

Spring Bootでプロファイルごとに実装を切り替える

TL; DR

@Profile@ConditionalOnExpression を使う。ちょっとつらいけどがんばる。

サンプルコードは下記のとおり。

github.com

やりたいこと

システム日時を取得するユーティリティがあるが、テストのときに日時が毎回変わると困るので、特定の場合は固定値を返すようにしたい*1

TERASOLUNAの下記機能とほぼ同じ。ただしSpring Bootなので、XMLを使わずアノテーションでなんとかしたい。

terasolunaorg.github.io

環境

  • Spring Boot 2.5.0

解決方法

@Profile アノテーションを使用し、指定されたプロファイルに応じてインジェクションされる実装を切り替える。

例えば、インタフェース DateTimeUtils に対して本物のシステム日時を返す実装クラス SystemDateTimeUtils と、固定日時を返す実装クラス FixedDateTimeUtils を作るとする。

下記のような動作にしたい。

  • プロファイル with-fixeddatetime が指定されている場合は FixedDateTimeUtils を使う
  • そうでない場合は SystemDateTimeUtils を使う

f:id:ser1zw:20210606091828p:plain

固定日時を返す実装クラス FixedDateTimeUtils では、@Profile("with-fixeddatetime") を付与する。

@Component
@Profile("with-fixeddatetime")
public class FixedDateTimeUtils implements DateTimeUtils {
    // ...

本物のシステム日時を返す実装クラス SystemDateTimeUtils では、@Profile("!with-fixeddatetime") を付与する。

@Profile でのプロファイル指定はSpring 式言語 (SpEL)が使える。!with-fixeddatetimeで「with-fixeddatetime以外」となる。

@Component
@Profile("!with-fixeddatetime")
public class SystemDateTimeUtils implements DateTimeUtils {
    // ...

プロファイルの指定には、下記のような方法がある。

  • application.properties で指定
spring.profiles.active=with-fixeddatetime
@SpringBootTest
@ActiveProfiles("with-fixeddatetime")
class DemoApplicationWithFixedDateTimeTests {
    // ...

その他、実行時の引数や環境変数での指定なども可能。

参考:2.6. アクティブ Spring プロファイルを設定する | Spring Boot 「使い方」ガイド - リファレンスドキュメント

@Profile("!with-fixeddatetime") って書かないといけないのはつらい

つらいけどしょうがない。

本当は @Profile("!with-fixeddatetime") って書かなくても with-fixeddatetime じゃなければデフォルトで @Profile 無しのものを使ってほしいところ。 しかし @Profile を付与しない場合は「どのプロファイルでも常に使われる」という動作になるらしい。今回の例で SystemDateTimeUtils から @Profile を外すと、with-fixeddatetime プロファイルを指定した場合に下記エラーとなる。

***************************
APPLICATION FAILED TO START
***************************

Description:

Field dateTimeUtils in com.example.demo.service.impl.DemoServiceImpl required a single bean, but 2 were found:
    - fixedDateTimeUtils: defined in file [/path/to/springboot-profile-switch-demo/target/classes/com/example/demo/util/impl/FixedDateTimeUtils.class]
    - systemDateTimeUtils: defined in file [/path/to/springboot-profile-switch-demo/target/classes/com/example/demo/util/impl/SystemDateTimeUtils.class]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

@Profile("default") だと、defaultプロファイルでない場合に使われないので、これもNG。

@ComponentScanコンポーネントスキャンの対象を変更することでの対応もできそうだが、ちょっと大掛かりではある。

実装クラスが3つ以上あるんですけど???

f:id:ser1zw:20210606092559p:plain

下記のような動作にする。

  • プロファイルが with-fixeddatetime の場合は FixedDateTimeUtils を使う
  • プロファイルが with-adjusteddatetime の場合は AdjustedDateTimeUtils を使う
  • それ以外の場合は SystemDateTimeUtils を使う

AdjustedDateTimeUtils には @Profile("with-adjusteddatetime") を付与するとして…

@Component
@Profile("with-adjusteddatetime")
public class AdjustedDateTimeUtils implements DateTimeUtils {
    // ...

SystemDateTimeUtils@Profilewith-fixeddatetimewith-adjusteddatetime の両方を除外するために、@Profile("!with-fixeddatetime && !with-adjusteddatetime") みたいになる。つらい。

@Component
@Profile("!with-fixeddatetime && !with-adjusteddatetime")
public class SystemDateTimeUtils implements DateTimeUtils {
    // ...

SpELは正規表現もサポートされているので、@ConditionalOnExpression を使用して「with-〜 にマッチしない」という指定もできる。プロファイル名をルール化できる場合は、この方法がよいかもしれない。

@Component
@ConditionalOnExpression("#{ not ('${spring.profiles.active}' matches '^with-.+') }")
public class SystemDateTimeUtils implements DateTimeUtils {
    // ...

この場合、JUnitテストクラスでは @TestPropertySource でプロファイルを指定する必要がある。あまりちゃんと調べられていないけど、@ActiveProfiles("プロファイル名") だと spring.profiles.active に値が設定されず、@ConditionalOnExpression が意図通りに動かない(Environment#getProperty("spring.profiles.active")null になる)。

@SpringBootTest
@TestPropertySource(properties = "spring.profiles.active=with-fixeddatetime")
class DemoApplicationWithFixedDateTimeTests {
    // ...

SpELについては、下記のドキュメントを見るとよい。

4. Spring 式言語 (SpEL) | Spring Framework コアテクノロジー - リファレンス

演算子については下記の部分を参照。

4.3.7. 演算子 | Spring Framework コアテクノロジー - リファレンス

参考

*1:テストだけであればMockitoを使ったほうが真っ当ではあるが、そうもいかないことが稀によくある。また、テスト以外でも状況に応じて挙動を切り替えたいこともたまにある。実行環境が異なる場合とか、バッチを特定の日付で実行したい場合とか…。

IntelliJ IDEAを使い始めたときに見るとよい情報まとめ

はじめに

最近ようやくIntelliJ IDEAを使い始めたので、役に立った情報をまとめておきます。

想定環境

とりあえずはじめに読むもの

Javaプログラムの作成〜実行

pleiades.io

デバッグ

pleiades.io

便利な機能・ショートカットなど

個人的によく使うのは下記のもの*1。よく使う順。

超使う

かなり使う

その他

他にもいろいろある。下記記事などが参考になる。

blog.jetbrains.com

公式のショートカット一覧は壁に貼って毎日眺めるとよい。

qiita.com

パンくずリスト

現在見ているクラス名やメソッド名がひと目でわかる。クソデカクラス/メソッドを読んでると、自分が今どこにいるのかわからなくなるので、そういうときに便利。 環境によってはデフォルトで表示されているかもしれないが、表示されていなかったら下記記事を参考に設定する。

pleiades.io

いじってたらぶっこわれてまともに動かなくなったときに設定ファイルを消す

下記記事を参考に、ディレクトリごと削除すればよい。

support.samuraism.com

*1:ショートカットは環境によって異なるかもしれないが、リンク先の公式サイトでは上部メニューの「Shortcuts」からOS等が選択できるので、自分の環境に合ったものを確認するとよい。

クリップボード操作コマンドを活用してテキストを手軽に一括編集する

はじめに

macOSpbcopy, pbpasteLinuxxsel を使うと、クリップボードにコピーした値を sed などのコマンドで編集後、別のエディタやスプレッドシートにペースト、といった操作が簡単にできて便利という話です。

スクリプトを書くほどでもないけど手で編集するのは面倒だからシェル芸したい!という場合におすすめ。

環境

どちらの環境ともシェルは zsh です。

よくある使用例

macOSの場合

例えばソースコードに記載されたエラーコードを抜き出してスプレッドシートに貼り付けたいとき。

当該部分を Command-C でコピーして…

f:id:ser1zw:20210531212437p:plain

pbpaste で出力した結果から grepsed で目的の部分を取り出す。

$ pbpaste | grep 'ErrorCode' | sed -E 's/[^"]+"([0-9A-Z]+)",?/\1/g'
E0001
E0002
E9999

そのままパイプで pbcopy につなげれば、目的の値がクリップボードにコピーされる。

$ pbpaste | grep 'ErrorCode' | sed -E 's/[^"]+"([0-9A-Z]+)",?/\1/g' | pbcopy

あとはスプレッドシートにそのまま Command-V とかで貼り付ければOK。

f:id:ser1zw:20210531212513p:plain

Linuxの場合

macOSの例で使用した pbcopy , pbpaste をそれぞれ xsel -bi , xsel -bo に置き換えるだけ。

$ xsel -bo | grep 'ErrorCode' | sed -E 's/[^"]+"([0-9A-Z]+)",?/\1/g' | xsel -bi

ただし xsel は標準では入っていないので、事前にインストールが必要。こんな感じ。

$ sudo apt-get install xsel

コマンド説明

macOSの場合

pbcopy : 標準入力の内容をクリップボードにコピーする

下記の例では HOGEクリップボードに登録される。普通にコピーしたときと同じく、Command-V で結果を貼り付け可能。

$ echo HOGE | pbcopy

pbpaste: クリップボードの内容を標準出力に書き出す

前述の例でクリップボードに登録された HOGE が標準出力に表示される。

$ pbpaste
HOGE

Linuxの場合

xsel: 選択範囲を操作する

xselX Window System上で選択された部分を操作するコマンド。オプション -b または --clipboard をつけることで、クリップボードの操作ができる。 入力と出力の切り替えはオプション -i -o で行う。

xsel -bi: 標準入力の内容をクリップボードにコピーする

入力は -i オプションで行う。クリップボードのオプションとまとめて -bi で指定。

下記の例ではMacでの例と同じく、標準入力から受け取った HOGEクリップボードに登録される。こちらも Ctrl-V で結果を貼り付け可能。

$ echo HOGE | xsel -bi

-i オプションは省略してもOK。

$ echo HOGE | xsel -b

xsel -bo: クリップボードの内容を標準出力に書き出す

出力は -o オプション。 前述の例でクリップボードに登録された HOGE が標準出力に表示される。

$ xsel -bo
HOGE

ちなみにLinuxには xclip というコマンドもある

xsel と同じく、デフォルトでの操作対象は選択部分。-selection cクリップボードの操作になる。

$ echo HOGE | xclip -selection c
$ xclip -selection c -o
HOGE

xclip は画像などのバイナリデータを扱えたり、パイプを経由せず直接ファイルから入力ができたりと、 xsel より便利な機能もある。

$ xclip -selection c -t image/jpeg foo.jpg # 画像ファイルをクリップボードにコピー
$ xclip -selection c -o > bar.jpg # クリップボード内の画像データをファイルに出力

ただ、クリップボードを指定するためのオプションが -selection c と少し長いので、普段はやっぱり xsel を使ってしまう…。

参考

Pleiades (Eclipse)でのSpringアプリ開発時にjarファイルが削除/変更できなくなったので対処した

環境

現象

Springアプリの開発に独自のjarライブラリを使用していたのですが、jarを上書きしたり、jar更新後にGitのブランチ切り替えをしたときにこんなエラーが出ることがありました。

f:id:ser1zw:20190908034141p:plain

リソースの削除中に問題が発生しました。
  'D:\local\pleiades\workspace\***\WebContent\WEB-INF\lib\***.jar' を削除できませんでした。
    ファイルの削除中に問題が発生しました。
      D:\local\pleiades\workspace\***\WebContent\WEB-INF\lib\***.jar を削除できませんでした。
      D:\local\pleiades\workspace\***\WebContent\WEB-INF\lib\***.jar: プロセスはファイルにアクセスできません。別のプロセスが使用中です。

f:id:ser1zw:20190908034201p:plain

Could not rename file D:\local\pleiades\workspace\***\WebContent\WEB-INF\lib\._***.jar9000261316551232106.tmp to D:\local\pleiades\workspace\***\WebContent\WEB-INF\lib\***.jar

ProcessExplorerで調べてみると、EclipseのSpringプラグインらしきものがjarファイルを掴んでいる様子。

対処方法

Springの言語サーバが原因っぽいので、OFFにしてしまいます。おそらく補完などに影響が出るけど、そこは諦める方針。

メニューから「ウィンドウ」→「設定」 をクリック。

f:id:ser1zw:20190908034643p:plain

「言語サーバ」をクリックし、言語サーバのチェックを外して「適用して閉じる」*1

f:id:ser1zw:20190908034943p:plain

*1:勢いで全部OFFにしたけど、Springのやつだけでよかった気がする