POPSPRING 2017 (2017年3月25日@幕張メッセ)

POPSPRING 2017 幕張公演に、嫁さんと2人で参加してきました。フェスは初参加です。

GOLDチケットだったので、とても余裕もって見れました。最初は前の方にいたのですが、逆に人が密集していてみずらかったので、途中からちょっと後ろ目で見ていました。それでもこんな感じで十分近くに見えました。

11時から21時過ぎまでの長丁場だったので、疲れたらちょっと下がって座って見れるのも良かったです。

POPSPRINGでは、アーティストの写真撮影NGとのことだったので、今回は写真がほとんど撮れませんでした。 海外アーティストだと珍しいなぁと思いましたが、いろんなアーティストが出る都合上、NGにせざるを得ないのかもしれませんね。(周りで結構撮っている人がいたけど、特に注意が入ることもなく、、、)

タイムテーブル

Opeging

会場前から並んで参加しました。GOLDだと入場も優先だったので、会場ぎりぎりでもスムーズに入れました。

DJ SHOTA(@djshotamusic)のOpening DJで、かなりテンションあがりました。早く来たかいがありました。

続くOpening Actは FlowBack、RIRI、FAKY と日本人アーティストが続きましたが、知らなかったアーティストと出会えるのはいいですね。とくにRIRI(@riri_tone)が良かったです。

まだ17歳なのにびっくりなのと、歌のときの大人の雰囲気と、MCのときの可愛さのギャップに、すぐにファンというか、応援したくなりました。可愛くて歌はパワフルなところが、アリアナ・グランデに似てるねと、嫁さんと盛り上がりました。

Jordan Fisher

機器のトラブルでかなり遅れて、Jordan Fisherとなりました。POPSPRINGに出演するということで、初めて知ったアーティストでしたが、All About UsをYouTubeで見て、すぐに好きになりました。

Jordan Fisher - All About Us (Official Video)

ちなみにダンスみて、なぜかUsherを思い出しました。似てるかな、、

パフォーマンスを見て、これからさらに売れるんだろうなぁと確信しました。また見たい!

Da-iCE

ちょうどお昼休憩とっていて、ほとんど見逃してしまいました、、ごめんなさい。

Austin Mahone

昨年に続いてのAustin Mahone。ダンサーさんが色っぽい。

Dirty Work でブルゾンちえみが出てこないかなぁと思いましたが、出てきませんでした(笑)。

最後に発表となったアーティストで、まぁ、Austin最後に持ってこられたら、全てOKになりますよね!(日本人アーティストが多いことで、ちょっと話題になっていましたが、最後のAustinの発表で全て吹き飛んだ気が)

Sabrina Carpenter

Sabrina Carpenter もPOPSPRINGに出演するということで初めて知ったアーティストでしたが、ほんと今となってはすごい好きなアーティストになりました。とくに1stアルバムの曲に好きなのが多いです。

Can’t Blame a Girl for Trying や Eyes Wide Open とか。

Sabrina Carpenter - Eyes Wide Open (Official Video)

ステージのSabrinaはすごい輝いてました。

w-inds.

We Don’t Need To Talk Anymore を初めて聞きましたが、これはいい曲だと思いました。

We Don’t Need To Talk Anymore(MUSIC VIDEO Full ver.+15s SPOT) / w-inds.

DNCE

一番盛り上がったと思います。楽しくて、一番疲れました(笑)。ほんと弾けすぎです。

Shown Mendes

いや、ほんとすごいというか、感動しました。

曲は好きで良く聞いてましたが、ギター一本であんな聞かせるパフォーマンスするなんて。すいません、全然Shownのことわかって無かったです。

ずっとハッピ姿のままいくとも思ってませんでした。ちょっとパフォーマンスとアンマッチな気が。

Fifth Harmony

Fifth Harmonyが一番のお目当てだったので、待ちに待った… といった感じでした。

いろいろな形でのパフォーマンスを見せてくれて、疲れがふっとびました。4人みんな特徴があって、いいグループですよね!(Allyのキュートな声が、一番好きです)

終わりに

自分が知らなかったアーティストに出会えたこともあって、ほんと値段以上の価値を感じたフェスでした。 また来年も行きたいです!!(サマソニにも興味がわいてきています…)

参考リンク

Lombokの@Builder(toBuilder = true)で、オブジェクトの複製を作成する

前回の記事の続きで。

@Builder(toBuilder = true)とすると、オブジェクトからBuilderを生成できます。

どういうときに使えるかというと、オブジェクトの複製を作成して、一部のプロパティのみ変えたいといった場合です。

package com.example.lombok;

import lombok.Builder;
import lombok.ToString;

public class BuilderExample {

    public static void main(String[] args) {

        Customer customer1 = new Customer("Taro", "Urashima", "ura@exmple.com", "111-111-1111");

        System.out.println(customer1);

        Customer customer2 = customer1.toBuilder()
                .lastName("Yamada")
                .build();

        System.out.println(customer2);
    }
}

@Builder(toBuilder = true)
@ToString
class Customer {

    private String firstName;

    private String lastName;

    private String mailAddress;

    private String telephone;
}

実行結果は下記の通りです。lastNameだけが異なるオブジェクトが生成されます。

Customer(firstName=Taro, lastName=Urashima, mailAddress=ura@exmple.com, telephone=111-111-1111)
Customer(firstName=Taro, lastName=Yamada, mailAddress=ura@exmple.com, telephone=111-111-1111)

Lombokで生成されるのは、下記のようなコードになります。(delombokで確認)

class Customer {
    private String firstName;
    private String lastName;
    private String mailAddress;
    private String telephone;

    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    Customer(final String firstName, final String lastName, final String mailAddress, final String telephone) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.mailAddress = mailAddress;
        this.telephone = telephone;
    }


    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    public static class CustomerBuilder {
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String firstName;
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String lastName;
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String mailAddress;
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String telephone;

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        CustomerBuilder() {
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder firstName(final String firstName) {
            this.firstName = firstName;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder lastName(final String lastName) {
            this.lastName = lastName;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder mailAddress(final String mailAddress) {
            this.mailAddress = mailAddress;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder telephone(final String telephone) {
            this.telephone = telephone;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public Customer build() {
            return new Customer(firstName, lastName, mailAddress, telephone);
        }

        @java.lang.Override
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public java.lang.String toString() {
            return "Customer.CustomerBuilder(firstName=" + this.firstName + ", lastName=" + this.lastName + ", mailAddress=" + this.mailAddress + ", telephone=" + this.telephone + ")";
        }
    }

    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    public static CustomerBuilder builder() {
        return new CustomerBuilder();
    }

    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    public CustomerBuilder toBuilder() {
        return new CustomerBuilder().firstName(this.firstName).lastName(this.lastName).mailAddress(this.mailAddress).telephone(this.telephone);
    }

    @java.lang.Override
    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    public java.lang.String toString() {
        return "Customer(firstName=" + this.firstName + ", lastName=" + this.lastName + ", mailAddress=" + this.mailAddress + ", telephone=" + this.telephone + ")";
    }
}

Lombokの@BuilderでBuilderを簡単に作成する

多数のプロパティを持つクラスを生成するときに、Builderが有用だと思っているのですが、普通に書くとそれなりにコード書く必要があります。そこでLombokの@Builderです。

package com.example.lombok;

import lombok.Builder;
import lombok.ToString;

public class BuilderExample {

    public static void main(String[] args) {

        // 引数が多くなると、どのプロパティに対するものかわかりずらい
        Customer customer1 = new Customer("Taro", "Urashima", "ura@exmple.com", "111-111-1111");

        System.out.println(customer1);

        // Builder使うことによって、どのプロパティに対しての値なのかわかりやすくなる
        Customer customer2 = Customer.builder()
                .firstName("Taro")
                .lastName("Urashima")
                .mailAddress("ura@exmple.com")
                .telephone("111-111-1111")
                .build();

        System.out.println(customer2);
    }
}

@Builder
@ToString
class Customer {

    private String firstName;

    private String lastName;

    private String mailAddress;

    private String telephone;
}

上記によってLombokで生成されるのは、下記のようなコードになります。(delombokで確認)

class Customer {
    private String firstName;
    private String lastName;
    private String mailAddress;
    private String telephone;

    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    Customer(final String firstName, final String lastName, final String mailAddress, final String telephone) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.mailAddress = mailAddress;
        this.telephone = telephone;
    }


    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    public static class CustomerBuilder {
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String firstName;
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String lastName;
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String mailAddress;
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        private String telephone;

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        CustomerBuilder() {
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder firstName(final String firstName) {
            this.firstName = firstName;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder lastName(final String lastName) {
            this.lastName = lastName;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder mailAddress(final String mailAddress) {
            this.mailAddress = mailAddress;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public CustomerBuilder telephone(final String telephone) {
            this.telephone = telephone;
            return this;
        }

        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public Customer build() {
            return new Customer(firstName, lastName, mailAddress, telephone);
        }

        @java.lang.Override
        @java.lang.SuppressWarnings("all")
        @javax.annotation.Generated("lombok")
        public java.lang.String toString() {
            return "Customer.CustomerBuilder(firstName=" + this.firstName + ", lastName=" + this.lastName + ", mailAddress=" + this.mailAddress + ", telephone=" + this.telephone + ")";
        }
    }

    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    public static CustomerBuilder builder() {
        return new CustomerBuilder();
    }

    @java.lang.Override
    @java.lang.SuppressWarnings("all")
    @javax.annotation.Generated("lombok")
    public java.lang.String toString() {
        return "Customer(firstName=" + this.firstName + ", lastName=" + this.lastName + ", mailAddress=" + this.mailAddress + ", telephone=" + this.telephone + ")";
    }
}

Lombokで生成されたメソッドに対してアノテーションを設定する

毎回調べているような気がするのでメモ。

Lombok+Jackson(Spring MVCやJerseyなどで使ってたり)を使っている場合に、@JsonIgnoreで一部のフィールドを対象外にしたい場合に、privateなフィールドに設定してもうまく動きません。

Lombokで生成したsetterまたはgetterに設定してあげる必要があり、下記のような書き方をします。(java - @JsonIgnore with @Getter Annotation - Stack Overflowより引用)

@Getter
@Setter
public class User {

    private userName;

    @Getter(onMethod = @__(@JsonIgnore))
    @Setter
    private password;
}

ちょっと特殊な書き方なので、いつも忘れます…

なお、Lombokで生成されたメソッドに対してアノテーションを指定する方法は、下記の3パターンになります。

  1. 生成されるメソッドに対して: @Setter(onMethod = @__(@ExampleAnnotation))
  2. 生成されるメソッドの引数に対して: @Setter(onParam = @__(@ExampleAnnotation))
  3. 生成されるコンストラクタに対して: @AllArgsConstructor(onConstructor = @__(@ExampleAnnotation))

Lombokのドキュメントだと、下記に記載があります。

Greasemonkeyでテキストエリアに入力補完を追加する

Redmineのtextile記法で、コードハイライトは<pre><code class="java"></code></pre>のような書き方をするのですが、これを入力するのが面倒になってきたので、Greasemonkeyを使ってテキストエリアで入力補完を行ってみました。

なお、Redmine 3.3 からは、ツールバーにコードハイライト用のボタンが追加されていますので、それを使うことによっても手間は軽減できるかと思います。

実装方法

テキストエリアでの入力補完は、カーソル位置を取るのが面倒なため、他のライブラリを利用します。

いくつかよさそうなものがありましたが、手軽そうなものということで、At.jsを今回は利用しました。

Greasemonkeyの@requireで外部JavaScriptを読み込めるので、cdnjsにあるAt.jsと、At.jsが依存しているCaret.jsというライブラリを読み込むようにしました。At.jsはjQueryに依存していますが、Redmineの各画面でも読み込んでいるので、指定しなくて大丈夫です。

At.js用のスタイルは、直接styleタグとしてヘッダに追加しました。

後は対象のテキストエリアを指定して、入力補完の設定をしていくだけです。 At.jsでは、atwhoという関数に対して、必要な情報を指定することよって、補完が行われるようになります。 とてもシンプルなので、迷うことはありませんでした。

$('.atwho-inputor').atwho({
  at: "@",
  data: ["one", "two", "three"],
}).atwho({
  at: ":",
  data: ["+1", "-1", "smile"]
});

動作イメージ

f:id:onozaty:20170228025858g:plain

スクリプト

ということで出来上がったスクリプトは、下記のようになります。

// ==UserScript==
// @name        Redmine wiki textcomplete
// @namespace   com.enjoyxstudy.redmine.wiki.textcomplete
// @version     1
// @grant       none
// @require     https://cdnjs.cloudflare.com/ajax/libs/at.js/1.5.2/js/jquery.atwho.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/Caret.js/0.3.1/jquery.caret.min.js
// ==/UserScript==
(function($) {
  $('textarea.wiki-edit').atwho({
    at: '<',
    data: [
      {name: 'java', content: '<pre><code class="java">\n</code></pre>'},
      {name: 'sql', content: '<pre><code class="sql">\n</code></pre>'}],
    insertTpl: '${content}',
    suffix: ''
  }).atwho({
    at: '{',
    data: [
      {name: 'collapse', content: '{{collapse(詳細を表示...)\n}}'}],
    insertTpl: '${content}',
    suffix: ''
  }).atwho({
    at: 'a',
    data: [
      {name: 'attachment', content: 'attachment:'}],
    insertTpl: '${content}',
    suffix: ''
  }).atwho({
    at: 'c',
    data: [
      {name: 'commit', content: 'commit:'}],
    insertTpl: '${content}',
    suffix: ''
  });

  $('head').append(
    `<style>
.atwho-view {
    position:absolute;
    top: 0;
    left: 0;
    display: none;
    margin-top: 18px;
    background: white;
    color: black;
    border: 1px solid #DDD;
    border-radius: 3px;
    box-shadow: 0 0 5px rgba(0,0,0,0.1);
    min-width: 120px;
    z-index: 11110 !important;
}

.atwho-view .atwho-header {
    padding: 5px;
    margin: 5px;
    cursor: pointer;
    border-bottom: solid 1px #eaeff1;
    color: #6f8092;
    font-size: 11px;
    font-weight: bold;
}

.atwho-view .atwho-header .small {
    color: #6f8092;
    float: right;
    padding-top: 2px;
    margin-right: -5px;
    font-size: 12px;
    font-weight: normal;
}

.atwho-view .atwho-header:hover {
    cursor: default;
}

.atwho-view .cur {
    background: #3366FF;
    color: white;
}
.atwho-view .cur small {
    color: white;
}
.atwho-view strong {
    color: #3366FF;
}
.atwho-view .cur strong {
    color: white;
    font:bold;
}
.atwho-view ul {
    /* width: 100px; */
    list-style:none;
    padding:0;
    margin:auto;
    max-height: 200px;
    overflow-y: auto;
}
.atwho-view ul li {
    display: block;
    padding: 5px 10px;
    border-bottom: 1px solid #DDD;
    cursor: pointer;
    /* border-top: 1px solid #C8C8C8; */
}
.atwho-view small {
    font-size: smaller;
    color: #777;
    font-weight: normal;
}
    </style>`
  );
})(jQuery);

おわりに

いろいろ候補を追加して、自分好みのものに変えていこうと思っています。

Spring Boot: Scheduledアノテーションを使用して、スケジュールされたタイミングでメソッドを実行する

Scheduledアノテーションを使うと、スケジュールされたタイミングでメソッドを実行することができます。 これで周期実行的なものは、簡単に実装できます。

実装方法

実行したいメソッドにScheduledアノテーションを付けます。

@Component
public class Scheduler {

    @Scheduled(fixedRate = 5000)
    public void doSomething() {
        // 5秒周期で行いたい処理
    }
}

Scheduledアノテーションによる実行を有効とするためには、EableSchedulingアノテーションを付けます。

@SpringBootApplication
@EnableScheduling
public class SchedulerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SchedulerApplication.class, args);
    }
}

これだけで周期実行が実装できます。簡単ですね!

Scheduledで指定できるパターン

指定方法は大きく分けて3パターン用意されています。

  1. fixedDelay : 前回タスクの実行完了時点から指定時間後にタスクを実行する。単位はms。
  2. fixedRate : 前回タスクの実行開始時点から指定時間後にタスクを実行する。単位はms。
  3. cron : 指定した周期でタスクを実行する。(Linuxのcronと似た書式)

また、fixedDelayfixedRateでは、初回のタスクの実行開始時間を指定するものとして、initialDelayがあります。

下記のようにすると、初回はアプリケーション起動から30秒後に実行され、その次からは、前回タスクの実行完了から60秒後に実行されることになります。

@Scheduled(fixedDelay = 60000, initialDelay = 30000)
public void doSomething() {
}

cronでは、zoneというフィールドで、cronの起動時間のタイムゾーンを指定できます。(未指定の場合は、デフォルトのタイムゾーン)

下記のようにすると、東京のタイムゾーンで、8時と9時と10時に実行されます。(8時から10時の間で、0秒、0分のタイミングで実行といった指定になっている)

@Scheduled(cron = "0 0 8-10 * * *", zone = "Asia/Tokyo")
public void doSomething() {
}

設定ファイルで指定する

ソース上に固定値で設定するのではなく、設定ファイルに記載することができます。

${設定名}で指定しておいて、

@Scheduled(cron = "${scheduler.cron}")
public void doSomething() {
}

application.properties で設定値を書きます。

scheduler.cron=*/5 * * * * *

fixedDelayfixedRateinitialDelayも、設定値とできるように、fixedDelayStringfixedRateStringinitialDelayStringというものがフィールドとしてあるので、そちらを使えばOKです。

@Scheduled(fixedRateString = "${scheduler.fixed-rate}")
public void doFixedRateString() throws InterruptedException {
}

ユニットテストの際に、周期実行を抑止する

テストの時には、勝手に周期実行が動いて欲しく無い場合もあるかと思います。

fixedDelayStringfixedRateString ならば、テスト時の設定値を変えてとても大きな数字にしておけば、抑止と同等のことができそうですが、cronだと、ぜったいに実行されないタイミングを指定するのは難しそうです。

こういった場合は、EnableSchedulingが指定されないように設定してあげれば回避できます。

下記のようなクラスでSpring Bootを起動しているならば、

@SpringBootApplication
@EnableScheduling
public class SchedulerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SchedulerApplication.class, args);
    }
}

テスト用に別の起動クラスを作成し、そちらでは@EnableSchedulingを指定しないようにします。 また、コンポーネントスキャンで、@EnableSchedulingを指定しているものを拾ってしまうと有効になってしまうので、該当のクラスを除外するように指定しておきます。

@SpringBootApplication
@ComponentScan(excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, value = SchedulerApplication.class))
public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
}

このクラスを@SpringBootTestに指定してあげれば、@EnableSchedulingが指定されずに、周期実行を抑止することが出来ます。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SchedulerTest.TestApplication.class)
public class SchedulerTest {

Redmine: リポジトリタブでデフォルト表示されるbranchを変更する(View customize plugin)

View customizeで『「リポジトリ」タブをクリック時にデフォルトで表示されるブランチをmasterではなく、ある特定のブランチに設定したい』といった問い合わせをいただいたので対応してみました。

ブランチの指定は、revというパラメータで行われているので、「リポジトリ」タブのリンクを、パラメータ付きのものに変えて対応します。

View customize の設定内容

Path pattern

全画面を対象にします。

.*

Code

Type:JavaScriptとして下記を設定します。

$(function() {
  var branchName = '3.3-stable'; // デフォルト
  var baseUrl = $('a.repository').attr('href');
  
  $('a.repository').attr('href', baseUrl + '?rev=' + encodeURIComponent(branchName));
});

もし特定のプロジェクトに対してのみ指定したい場合には、body.project-{プロジェクト名}と指定すると対象を絞ることができます。

$('body.project-xxx a.repository')

設定後のイメージ

リポジトリタブのリンク先が、指定したブランチになりました。

f:id:onozaty:20170212172229p:plain