2018年12月29日土曜日

Ubuntuのターミナルでファイルのフルパスを取得する方法

readlinkコマンドを使うとファイルのフルパスを取得できる。

取得したいファイルを、-fオプションに指定する。
$ readlink -f hoge.txt 
/home/hoge/hoge.txt

2018年12月26日水曜日

Elasticsearchに格納しているデータの一部を書き換える方法

Elasticsearch内のデータの一部の書き換えはUpdate APIを使います。
ドキュメントは、Updates with a partial documentあたりになります。

確認

書き換え対象のデータの内容です。hoge1とhoge2のキーを持っています。
$ curl -s http://localhost:9200/test_index/hoge/1 | jq
{
  "_index": "test_index",
  "_type": "hoge",
  "_id": "1",
  "_version": 3,
  "found": true,
  "_source": {
    "hoge1": "hoge1",
    "hoge2": "fuga2"
  }
}


Update APIを叩いて、hoge1の値を変更してみます。
公式ドキュメントに従い、POSTで変更対象のidの_updateを叩きます。
body部には、jsonでdoc配下に変更したい内容を定義して送ります。
$ curl -XPOST -H "Content-Type: application/json" http://localhost:9200/test_index/hoge/1/_update -d ' 
{
  "doc": {
    "hoge1": "mod-hoge1"
  }
}
'

書き換えたデータを取得すると、データの内容が変更されていることが確認できます。
$ curl -s http://localhost:9200/test_index/hoge/1 | jq
{
  "_index": "test_index",
  "_type": "hoge",
  "_id": "1",
  "_version": 4,
  "found": true,
  "_source": {
    "hoge1": "mod-hoge1",
    "hoge2": "fuga2"
  }
}

2018年12月14日金曜日

2018年10月2日火曜日

GoでCSVファイルを読み込む

Package csvを使ってCSVファイルを読み込む例になります。

読み込むCSVファイルを準備する

文字列は、「"」で囲み要素内に「,」がある内容としてみました。
"name","age"
"hoge,fuga", 100

読み込んで見る

package main

import (
 "encoding/csv"
 "fmt"
 "io"
 "log"
 "os"
)

func main() {

        // ファイルを開く
 file, err := os.Open("test.csv")
 if err != nil {
  log.Fatal(err)
 }

        // 開いたファイルの後始末
 defer file.Close()

        // CSVを読み込むためのReaderを生成
 reader := csv.NewReader(file)

 for {
                // 1行づつ読み込む
  record, err := reader.Read()
                // ファイルの末尾で処理終了
  if err == io.EOF {
   break
  }
  if err != nil {
   log.Fatal(err)
  }
  fmt.Println(record)
 }

}

出力結果

クォートが削除されて読み込まれ、要素内の「,」も正しく読み込まれていることがわかります。
[name age]
[hoge,fuga  100]

2018年9月11日火曜日

spring-boot-maven-pluginのboot:runで起動引数を指定する

spring-boot-maven-pluginのboot:run時に起動引数を指定する方法です。

起動引数は、システムプロパティのspring-boot.run.argumentsを使用して指定します。
mvn spring-boot:run -Dspring-boot.run.arguments=[引数]

2018年9月6日木曜日

[AssertJ]JUnit5拡張のsoft assertions

AssertJ 3.11.0から追加されたJUnit5拡張のsoft assertionsを試してみる。

AssertJを依存ライブラリに追加する

バージョンは、3.11.0以降を指定する必要があります。
testCompile 'org.assertj:assertj-core:3.11.1'

soft assertionsを使ってみる

実装すればいいことは、以下の2点ですね。
org.assertj.core.api.SoftAssertions.SoftAssertionsのときは、assertAllを呼び出す必要があったけどそれが不要になりますね。
  • JUnitJupiterSoftAssertionsをフィールドに定義して@RegisterExtensionを設定する
  • JUnitJupiterSoftAssertionsのassertThatを使ってアサーションを行う

public class SampleTest {
    
    @RegisterExtension
    public final JUnitJupiterSoftAssertions softly = new JUnitJupiterSoftAssertions();

    @Test
    void test() {
        softly.assertThat(5)
              .isEqualTo(1);
        softly.assertThat("hoge")
              .isEqualTo("fuga");
    }
}

実行結果

複数箇所でアサーションに失敗した場合でも、それぞれの結果が表示されますね。
Expected :1
Actual   :5
 <Click to see difference>

Expected :fuga
Actual   :hoge
 <Click to see difference>  

2018年8月11日土曜日

Jenkins Pipeline Utility Stepsを使ってpomの情報を読み取る

Pipeline Utility Stepsを使うことで、Jenkinsのpipeline上でpomの情報を簡単に読み取れるようです。

プラグインのインストール

Pipeline Utility Stepsプラグインをインストールします。

パイプラインでpomの情報を参照する

readMavenPomをパイプラインのscriptブロック内で使うことでpomの内容を参照できます。
参照(変更)できる内容は、こちらを参照
stage('test') {
    steps {
        script {
            pom = readMavenPom file: 'pom.xml'
            println(pom.version)
            pom.dependencies.each {
                println(it)
            }
        }
    }
}

実行結果

上のパイプラインを実行するとバージョン番号とdependenciesの内容が出力されます。
[Pipeline] readMavenPom
[Pipeline] echo
1.0.0
[Pipeline] echo
Dependency {groupId=org.springframework.boot, artifactId=spring-boot-starter-data-jpa, version=null, type=jar}
[Pipeline] echo
Dependency {groupId=org.springframework.boot, artifactId=spring-boot-starter-thymeleaf, version=null, type=jar}
[Pipeline] echo
Dependency {groupId=org.springframework.boot, artifactId=spring-boot-starter-web, version=null, type=jar}
[Pipeline] echo
Dependency {groupId=org.springframework.boot, artifactId=spring-boot-starter-actuator, version=null, type=jar}
[Pipeline] echo
Dependency {groupId=org.postgresql, artifactId=postgresql, version=null, type=jar}
[Pipeline] echo
Dependency {groupId=org.springframework.boot, artifactId=spring-boot-starter-test, version=null, type=jar}
[Pipeline] }

2018年7月15日日曜日

Concourse CIで成果物をMavenリポジトリにアップする

Concourse CIのMaven Resourceを使ってビルド成果物をMavenリポジトリにデプロイする方法です。

ジョブの構成

ジョブは、下の画像のようにアプリケーションをビルドしてnexusにデプロイする構成としています。

ジョブ定義

  • resource_typesで、maven-resourceを定義します。
  • resourcesは、resource_typesで定義したmaven-resourceの設定(urlや接続情報やartifactなど)を行います。
  • jobのtask定義ではリポジトリにデプロイする対象をoutputsで定義します。
  • put定義では、デプロイ対象の成果物(jarとpom)を定義します
# maven-resourceの定義
resource_types:
- name: maven-resource
  type: docker-image
  source:
    repository: nulldriver/maven-resource
    tag: latest

resources:
- name: app
  type: git
  source:
    uri: http://gitbucket:8080/git/root/sample-app.git

# maven-resourceのリソース定義
# urlや接続情報などを設定します
- name: nexus
  type: maven-resource
  source:
    url: http://nexus:8081/repository/maven-releases/
    snapshot_url: http://nexus:8081/repository/maven-snapshots/
    artifact: sample:sample-app:jar
    username: admin
    password: admin123
jobs:
- name: build
  plan:
  - get: app
    trigger: true
  - task: build
    config:
      platform: linux
      image_resource:
        type: docker-image
        source:
          repository: maven
          tag: '3.5.4-jdk-8'
      inputs:
        - name: app
      # putでmavenリポジトリに成果物をデプロイできるようにするためのoutputsの定義
      outputs:
        - name: target
      run:
        path: bash
        args:
        - -c
        - |
          cd app
          mvn clean package -D maven.repo.local=/tmp/.m2/repository
          cp -r target ..
  # jarとpomを指定してリポジトリにデプロイします
  - put: nexus
    params:
      file: target/sample-app*.jar
      pom_file: app/pom.xml
      

ジョブの実行と結果の確認

ジョブを実行すると、下のようなログが出力されてリポジトリに成果物がアップされていることがわかります。

[INFO] --- maven-deploy-plugin:2.7:deploy-file (default-cli) @ standalone-pom ---
Downloading: http://nexus:8081/repository/maven-snapshots/sample/sample-app/1.0.0-SNAPSHOT/maven-metadata.xml
Uploading: http://nexus:8081/repository/maven-snapshots/sample/sample-app/1.0.0-SNAPSHOT/sample-app-1.0.0-20180715.115944-1.jar
Uploaded: http://nexus:8081/repository/maven-snapshots/sample/sample-app/1.0.0-SNAPSHOT/sample-app-1.0.0-20180715.115944-1.jar (1.8 kB at 8.4 kB/s)
Uploading: http://nexus:8081/repository/maven-snapshots/sample/sample-app/1.0.0-SNAPSHOT/sample-app-1.0.0-20180715.115944-1.pom
Uploaded: http://nexus:8081/repository/maven-snapshots/sample/sample-app/1.0.0-SNAPSHOT/sample-app-1.0.0-20180715.115944-1.pom (403 B at 5.2 kB/s)
Downloading: http://nexus:8081/repository/maven-snapshots/sample/sample-app/maven-metadata.xml
Uploading: http://nexus:8081/repository/maven-snapshots/sample/sample-app/1.0.0-SNAPSHOT/maven-metadata.xml
Uploaded: http://nexus:8081/repository/maven-snapshots/sample/sample-app/1.0.0-SNAPSHOT/maven-metadata.xml (766 B at 9.9 kB/s)
Uploading: http://nexus:8081/repository/maven-snapshots/sample/sample-app/maven-metadata.xml
Uploaded: http://nexus:8081/repository/maven-snapshots/sample/sample-app/maven-metadata.xml (276 B at 4.1 kB/s)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

nexusでも成果物がアップされていることが確認できます。


mvnコマンド実行時にローカルリポジトリの場所を指定する

mvnコマンド実行時にローカルリポジトリの場所を指定したい場合はmaven.repo.localシステムプロパティを使用します。

例えば、カレントディレクトリ配下にローカルリポジトリを置きたい場合、mvn test -Dmaven.repo.local=./m2/repositoryのように指定します。

実行例

システムプロパティで指定した場所にローカルリポジトリが作られているのがわかります。

$ ls .m2
ls: '.m2' にアクセスできません: そのようなファイルやディレクトリはありません

$ mvn package -Dmaven.repo.local=./.m2/repository
[INFO] Scanning for projects...
[INFO] 
[INFO] -------------------------< sample:sample-app >--------------------------
[INFO] Building sample-app 1.0.0
[INFO] --------------------------------[ jar ]---------------------------------
*************************************** 省略 ***************************************
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:25 min
[INFO] Finished at: 2018-07-15T06:37:10+09:00
[INFO] ------------------------------------------------------------------------

$ ls .m2
repository

2018年7月11日水曜日

JacksonでイミュータブルなオブジェクトにJsonをマッピングする

com.fasterxml.jackson.annotation.JsonCreatorアノテーションを使うこと、コンストラクタかファクトリメソッドを使った構築ができる。

コンストラクタを使った場合

  • コンストラクタにJsonCreatorアノテーションを設定する。
  • 引数には、JsonPropertyアノテーションを設定し、Jsonのキー名を指定する。
public class Sample {

    public final String name;

    public final short age;

    @JsonCreator
    public Sample(
            @JsonProperty("name") final String name,
            @JsonProperty("age") final short age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Sample{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

ファクトリメソッドを使った場合

ファクトリメソッドの場合には、コンストラクタに設定していたアノテーションをファクトリメソッドにそのまま持ってくれば良い。
public class Sample {

    public final String name;

    public final short age;

    private Sample(final String name, final short age) {
        this.name = name;
        this.age = age;
    }

    @JsonCreator
    public static Sample create(
            @JsonProperty("name") final String name,
            @JsonProperty("age") final short age) {
        return new Sample(name, age);
    }

    @Override
    public String toString() {
        return "Sample{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

動作結果

動作確認に使ったコード
final ObjectMapper objectMapper = new ObjectMapper();
final Sample sample = objectMapper.readValue("{\"name\": \"なまえ\", \"age\": 100}", Sample.class);
System.out.println("sample = " + sample);

標準出力の結果
sample = Sample{name='なまえ', age=100}

2018年6月10日日曜日

zshのviモードでCtrl+Rでコマンドの履歴検索をできるようにする

これでCtrl+Rでコマンドの履歴検索ができるようになる。

bindkey '^R' history-incremental-pattern-search-backward

2018年5月28日月曜日

Spring MVCとThymeleafの組み合わせでcheckboxのデフォルト値を設定する

Spring MVCとThymeleafの組み合わせでcheckboxを使った場合の動きを理解する。

checkboxの値をBooleanで受け取る場合

Form

public class Form {

    private Boolean check;

    // ほかは省略
}

Controller

checkboxの値を確認できるように標準出力に値を出力するだけに
@PostMapping("hello")
public String hello(Form form) {
    System.out.println("form.check = " + form.check);
    return "input";
}

View

<form th:action="@{/hello}" method="post" th:object="${form}">
  <input type="checkbox" th:field="*{check}" />
  <button type="submit">submit</button>
</form>

動かしてみた結果

出力されるhtmlには、checkboxとcheckboxに対応したhiddenが出力されます。このhiddenがあることでcheckboxがチェックされなかった場合でもFormに値が設定されます。(Booleanなのでfalseが設定される)
これは、thymeleafのth:fieldで自動的に出力されます。
<form action="/hello" method="post">
  <input type="checkbox" id="check1" name="check" value="true" /><input type="hidden" name="_check" value="on"/>
  <button type="submit">submit</button>
</form>

checkboxをチェックしないで送信した時は標準出力に↓が出力されます。
form.check = false

ちなみに、th:fieldを使わないと下のようにhiddenが出力されないので、checkboxがチェックされなかった場合はFormには値が設定されないのでnullのままとなります。
<form action="/hello" method="post">
  <input type="checkbox" name="checkbox" />
  <button type="submit">submit</button>
</form>

checkboxの値をBoolean以外で受け取る場合

単一のcheckboxの値をBooleanで受け取ることはないと思うけど、Stringで受け取った場合の動きを確認してみます。

Stringの場合も、Booleanと同じようにth:fieldを使うと、↓が出力されます。だけど、Stringの場合はth:fieldが出力するアンダースコア付きのhiddenに対応していないので、このままだと未チェック時はFormの値はnullのままになります。

<form action="/hello" method="post">
  <input type="checkbox" value="on" id="check1" name="check"/><input type="hidden" name="_check" value="on"/>
  <button type="submit">submit</button>
</form>

未送信時に送信する値は、別途↓のようにhiddenで定義します。デフォルト設定だと、対応するcheckboxのnameの先頭に!をつけた名前でhiddenを定義する必要があります。
プレフィックスの値はWebDataBinderのJavadocが参考になります。
<form th:action="@{/hello}" method="post" th:object="${form}">
  <input type="checkbox" th:field="*{check}" value="on" />
  <input type="hidden" name="!check" value="off" />
  <button type="submit">submit</button>
</form>

これで、未チェック時にはhiddenで指定した値をFormで受け取れるようになります。
form.check = off

2018年5月2日水曜日

Spring Securityで認証後にかならず決まったページに遷移させる

defaultSuccessurlの2番目の引数にtrueを指定すると、ログイン成功後に必ず最初に指定したパスにリダイレクトされる。
@Override
protected void configure(final HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .anyRequest()
        .authenticated()
        .and()
        .formLogin()
        .defaultSuccessUrl("/index", true)
        .and()
        .logout()
        .invalidateHttpSession(true);
}

2018年4月3日火曜日

Spring BootとFlywayでテーブルなどがあるスキーマに対してマイグレーションを実行する

application.propertiesに下を追加して、既存の状態をバージョン001とすることでマイグレーションが可能になる。
アプリ側のSQLファイルのバージョンは、V002__Create_hoge_table.sqlのようにベースバージョンより大きいバージョンにする。

spring.flyway.baseline-on-migrate=true
spring.flyway.baseline-version=001



なお、デフォルト状態だとFound non-empty schema "PUBLIC" without metadata table!のエラーが出て実行できない。

2018年3月25日日曜日

SQL Serverの計算列

SQL Serverでは、テーブル作成時にカラム名の後にasで計算式を書くことで計算結果をそのカラムに自動的に設定できる。
この計算列は、計算結果が格納されるのでINSERTやUPDATEに含めることはできない。

テーブル定義

create table test
(
  id   int not null
    primary key,
  num1 int,
  num2 int,
  num3 as [num1] + [num2]
)


insertの実行

計算列のnum3は含めずに登録を行う。
insert  into test (id, num1, num2) values (1, 1, 2)

結果の確認

num3には、num1とnum2を元に計算した結果が格納されているのがわかる。
1> select * from test
2> go
id          num1        num2        num3       
----------- ----------- ----------- -----------
          1           1           2           3

(1 rows affected)

2018年3月21日水曜日

Tomcatでポストサイズの上限を設定する

HTTP ConnectorにmaxPostSizeを設定するとポストサイズの上限を設定できるようなので動きを確認してみた。

確認用Servlet

確認用のServletではpostされた内容を標準出力にだしています。
override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) {
    println("Content-Length: ${req.contentLengthLong}")
    println("parameter count: ${req.parameterMap.size}")
    req.parameterMap.forEach {
        println("${it.key}: ${it.value.joinToString(", ")}")
    }
    doGet(req, resp)
}

デフォルト設定で実行

デフォルト設定だと上限が2MBとなるのでエラーなどは発生しないでうまく処理が行われる。
➜  ~ curl http://localhost:8080/sample/hello --data-urlencode "param_key=値" -X POST -i     
HTTP/1.1 200 

サーバの標準出力の内容
Content-Length: 19
parameter count: 1
param_key: 値

maxPoolSizeを指定して実行

conf/server.xmlのConnector部分にmaxPostSizeの設定を追加する。簡単に上限を超えるように設定値は1バイトに。
<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           maxPostSize="1"
           redirectPort="8443" />

デフォルトの設定だと、クライアントには200が返ってくる。
➜  ~ curl http://localhost:8080/sample/hello --data-urlencode "param_key=値" -X POST -i 
HTTP/1.1 200 

サーバのログを見ると、パラメータが存在しない状態でServletが呼び出されている。
このような場合、リクエストスコープのorg.apache.catalina.parameter_parse_failed_reasonの値(org.apache.tomcat.util.http.Parameters.FailReason)を見ると何が起きたかがわかるらしい。
Content-Length: 19
parameter count: 0

FailedRequestFilterを適用して実行

web.xmlにFailedRequestFilterを追加する。
<filter>
  <filter-name>failedRequestFilter</filter-name>
  <filter-class>org.apache.catalina.filters.FailedRequestFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>failedRequestFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

Filterで指定サイズを超えた場合は処理を中断し、413 Request Entity Too Largeが返されるようになる。
➜  ~ curl http://localhost:8080/sample/hello --data-urlencode "param_key=値" -X POST -i 
HTTP/1.1 413 


ちなみにこのフィルターはapplication/x-www-form-urlencoded以外には影響を与えないようです。例えば、application/jsonの場合には下のように正常に処理が終わります。
➜  ~ curl http://localhost:8080/sample/hello -H "Content-Type: application/json" -d "{}"  -i   
HTTP/1.1 200 

2018年3月6日火曜日

Versions Maven Pluginを使ってparentのバージョンを変更する

org.codehaus.mojo:versions-maven-plugin:2.5:update-parent
で参照可能な最新バージョンに変更できる。

parentVersionを使用して、バージョン番号の範囲を指定できる。

例えば、下のように1.0.0か1.1.0のように指定する。
org.codehaus.mojo:versions-maven-plugin:2.5:update-parent -DparentVersion=[1.0.0,1.1.0]

2018年1月31日水曜日

Versions Maven Pluginを使ってバージョン番号からSNAPSHOTを削除する

setゴール実行時にremoveSnapshotにtrueを指定することで、SNAPSHOTが削除できる。
「1.0.0-SNAPSHOT」の場合は、「1.0.0」になる。
mvn org.codehaus.mojo:versions-maven-plugin:2.5:set -DremoveSnapshot=true

2018年1月30日火曜日

Apache Maven Clean Pluginを使ってデフォルト以外のファイルを削除する

Apache Maven Clean Pluginでは、project.build.directoryなどを自動的にクリーニングしてくれるけど、それ以外のファイルをクリーニングしたい場合がある。
その場合は、下のようにconfigurationにfilesetesを指定する。

この例では、「プロジェクトディレクトリ/temp」配下の「*.log」がクリーニング対象になる。
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-clean-plugin</artifactId>
  <version>3.0.0</version>
  <configuration>
    <filesets>
      <fileset>
        <directory>temp</directory>
        <includes>
          <include>*.log</include>
        </includes>
      </fileset>
    </filesets>
  </configuration>
</plugin>

2018年1月27日土曜日

はじめてのjackson-dataformat-xml

jackson-dataformat-xmlのメモ。

使うライブラリ

compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.4'

xmlに対応したクラスの定義

@JacksonXmlRootElement(localName = "root")
public class Simple {
    
    private Child child;

    public Child getChild() {
        return child;
    }

    public void setChild(final Child child) {
        this.child = child;
    }

    @Override
    public String toString() {
        return "Simple{" +
                "child=" + child +
                '}';
    }
}

public class Child {
    @JacksonXmlText
    private String text;
    
    @JacksonXmlProperty(isAttribute = true)
    private String attr;

    @Override
    public String toString() {
        return "Child{" +
                "text='" + text + '\'' +
                ", attr='" + attr + '\'' +
                '}';
    }
}

xml→Java

XmlMapperのreadValueを使う
final String xml = "あたい";
final XmlMapper xmlMapper = new XmlMapper();

final Simple simple = xmlMapper.readValue(xml, Simple.class);
System.out.println("simple = " + simple);  // simple = Simple{child=Child{text='あたい', attr='hoge'}}

java→xml

final Simple input = new Simple();
final Child child = new Child();
child.text = "値";
child.attr = "属性";
input.child = child;
System.out.println(xmlMapper.writeValueAsString(input)); // 

kotlin data classを使う

以下を追加する。
compile 'com.fasterxml.jackson.module:jackson-module-kotlin:2.9.4'

KotlinModuleを有効化することでdata classを使えるようになる。
@JacksonXmlRootElement(localName = "r")
data class Hoge(val hoge:String)

val xmlMapper = XmlMapper()
xmlMapper.registerModule(KotlinModule())
println(xmlMapper.writeValueAsString(Hoge("fugafuga")))

2018年1月4日木曜日

[SQL Server]sequenceのSTART WITHは必ず指定する

SQL Serverでシーケンスオブジェクトを作成した場合、START WITHは指定した型の最小値となるので、START WITHは必ず指定しよう。
指定しなかった場合は、デフォルトの型がbigintなので、START WITHは-9223372036854775808となるので、想定外な値から値が採番される…

START WITHを指定しない場合

1> create sequence test 
2> go
1> 
1> select start_value from sys.sequences where name = 'test'
2> go
start_value
-----------
-9223372036854775808


START WITHを指定した場合


1> create sequence test start with 1
2> go
1> 
1> select start_value from sys.sequences where name = 'test'
2> go
start_value
-----------
1