読者です 読者をやめる 読者になる 読者になる

Redmineでニュースの参照可否を制御する(patch適用)

Redmine 3.3時点では、ニュースの参照に関する権限が無く、一部のユーザにニュースを見せないということが出来ません。

それをつぶやいたところ、前田さんにRedmine本家のFeatureにあがっていて、3.4.0で入るかもといった情報を頂いたので、先行してパッチを当てて試してみました。

パッチの内容

現時点のパッチは下記になっています。

軽微な変更だったのと、最新じゃないバージョンに当てたかったので、手作業で差分当てました。下記の3ファイル(合計3行)さえ直せば、すぐに試せます。(日本語の表示確認してなくて良ければ、ja.ymlもいらないです)

  • config/locales/en.yml
diff --git a/config/locales/en.yml b/config/locales/en.yml
index a8eca2dfe..f2a8cbc0b 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -497,6 +497,7 @@ en:
   permission_view_time_entries: View spent time
   permission_edit_time_entries: Edit time logs
   permission_edit_own_time_entries: Edit own time logs
+  permission_view_news: View news
   permission_manage_news: Manage news
   permission_comment_news: Comment news
   permission_view_documents: View documents
  • config/locales/ja.yml
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index a018e7240..8d1ae6ce1 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -427,6 +427,7 @@ ja:
   permission_edit_time_entries: 作業時間の編集
   permission_edit_own_time_entries: 自身が記入した作業時間の編集
   permission_manage_project_activities: 作業分類 (時間管理) の管理
+  permission_view_news: ニュースの閲覧
   permission_manage_news: ニュースの管理
   permission_comment_news: ニュースへのコメント
   permission_view_documents: 文書の閲覧
  • lib/redmine.rb
diff --git a/lib/redmine.rb b/lib/redmine.rb
index e3ff60fd1..204429cc6 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -131,7 +131,7 @@ Redmine::AccessControl.map do |map|
   end
 
   map.project_module :news do |map|
-    map.permission :view_news, {:news => [:index, :show]}, :public => true, :read => true
+    map.permission :view_news, {:news => [:index, :show]}, :read => true
     map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy], :attachments => :upload}, :require => :member
     map.permission :comment_news, {:comments => :create}
   end

対応後の画面

ロールの編集画面に「ニュースの閲覧」という権限が追加されて、制御可能になりました。実際のニュースの表示もばっちりです。

f:id:onozaty:20170514004703p:plain

Spring Boot で WebSocket (STOMPではなく、TextWebSocketHandler)を試してみる

こないだSTOMP over WebSocketを試してみましたが、今度はSTOMPを使わずに、TextWebSocketHandlerを使ったWebSocketを試してみます。題材も同じくチャットです。

ルームの情報をどのように渡そうか悩みました。コードを書き始めるまでは、WebSocketで接続する先にヘッダとかで渡せばいいかなと思っていましたが、調べてみたら渡せなかったので、やもえずURLのクエリパラメータとして渡すようにしました。

コード

テキストでのやり取りを行うので、TextWebSocketHandler を利用します。TextWebSocketHandlerの各メソッドをoverrideして必要な処理を実装するだけです。

ルームの情報は、URLのクエリとしてクライアントから送っているので、接続が確立したタイミング(afterConnectionEstablished)にて、ルーム毎にWebSocketSessionを保持するようにします。 メッセージを受け取ったら、自分のルームと同じWebSocketSessionに対して、メッセージを送るだけです。

package com.example;

import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

@Component
public class ChatHandler extends TextWebSocketHandler {

    private ConcurrentHashMap<String, Set<WebSocketSession>> roomSessionPool = new ConcurrentHashMap<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {

        String roomName = session.getUri().getQuery();

        roomSessionPool.compute(roomName, (key, sessions) -> {

            if (sessions == null) {
                sessions = new CopyOnWriteArraySet<>();
            }
            sessions.add(session);

            return sessions;
        });
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

        String roomName = session.getUri().getQuery();

        for (WebSocketSession roomSession : roomSessionPool.get(roomName)) {
            roomSession.sendMessage(message);
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {

        String roomName = session.getUri().getQuery();

        roomSessionPool.compute(roomName, (key, sessions) -> {

            sessions.remove(session);
            if (sessions.isEmpty()) {
                // 1件もない場合はMapからクリア
                sessions = null;
            }

            return sessions;
        });
    }
}

WebSocketの設定として、URLとHandlerを紐付けます。

package com.example;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

import lombok.AllArgsConstructor;

@Configuration
@EnableWebSocket
@AllArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer {

    private final ChatHandler chatHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(chatHandler, "/endpoint");
    }

}

クライアント側では、下記のようなコードになりました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>チャット</title>
<link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap.min.css" />
<link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap-theme.min.css" />
</head>
<body>
  <div class="container">
    <h2>チャット</h2>
    <div class="form-horizontal">
      <div class="form-group">
        <label for="roomName" class="col-sm-2 control-label">ルーム</label>
        <div class="col-sm-2">
          <input id="roomName" type="text" class="form-control" value="example" />
        </div>
        <div class="col-sm-3">
          <button id="connectButton" type="button" class="btn btn-default">接続</button>
          <button id="disconnectButton" class="btn btn-default">切断</button>
        </div>
      </div>
      <div class="form-group">
        <label for="message" class="col-sm-2 control-label">メッセージ</label>
        <div class="col-sm-4">
          <input id="message" type="text" class="form-control" />
        </div>
        <div class="col-sm-2">
          <button id="sendButton" type="button" class="btn btn-default">送信</button>
        </div>
      </div>
      <div class="row">
        <div class="col-sm-4 col-sm-offset-2">
          <ul id="messageList" class="list-unstyled">
          </ul>
        </div>
      </div>
    </div>
  </div>
  <script src="/webjars/jquery/1.12.4/jquery.min.js"></script>
  <script src="/webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
  <script>
    $(function() {
      var endpoint = 'ws://' + location.host + '/endpoint';
      var webSocket = null;

      $('#connectButton').click(function() {

        $("#messageList").empty();

        webSocket = new WebSocket(endpoint + '?' + encodeURIComponent($('#roomName').val()));
        webSocket.onopen = function() {
          $('#roomName').prop('disabled', true);
          $('#connectButton').prop('disabled', true);
          $('#disconnectButton').prop('disabled', false);
        };
        webSocket.onclose = function() {
        };
        webSocket.onmessage = function(message) {
          $('#messageList').prepend($('<li>').text(message.data));
        };
        webSocket.onerror = function() {
          alert('エラーが発生しました。');
        };
      });

      $('#disconnectButton').click(function() {

        webSocket.close();
        webSocket = null;

        $('#roomName').prop('disabled', false);
        $('#connectButton').prop('disabled', false);
        $('#disconnectButton').prop('disabled', true);
      });

      $('#sendButton').click(function() {
        if (!webSocket) {
          alert('未接続です。');
          return;
        }

        webSocket.send($('#message').val());
      });
    });
  </script>
</body>
</html>

STOMPの時のほうが、いろいろシンプルに書けるので、ブラウザがクライアントならば、STOMPを使わない理由はないかなと思っています。

Spring Boot で STOMP Over WebSocket を試してみる

Spring BootSTOMP Over WebSocketを使って、試しにチャットっぽいものを作ってみました。

f:id:onozaty:20170503222047p:plain

同じルームに接続しているクライアントに、同じメッセージを配信するだけならば、Controllerの定義はいりません。

WebSocketConfigとして、エンドポイントとSimpleBrokerでハンドリングする宛先を定義するだけです。

package com.example;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@EnableWebSocketMessageBroker
@Configuration
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/endpoint");
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic");
    }
}

画面側では、配信と購読で同じ宛先を指定しておきます。自分で送ったものも受け取ることになります。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>チャット</title>
<link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap.min.css" />
<link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap-theme.min.css" />
</head>
<body>
  <div class="container">
    <h2>チャット</h2>
    <div class="form-horizontal">
      <div class="form-group">
        <label for="roomName" class="col-sm-2 control-label">ルーム</label>
        <div class="col-sm-2">
          <input id="roomName" type="text" class="form-control" value="example" />
        </div>
        <div class="col-sm-3">
          <button id="connectButton" type="button" class="btn btn-default">接続</button>
          <button id="disconnectButton" class="btn btn-default">切断</button>
        </div>
      </div>
      <div class="form-group">
        <label for="message" class="col-sm-2 control-label">メッセージ</label>
        <div class="col-sm-4">
          <input id="message" type="text" class="form-control" />
        </div>
        <div class="col-sm-2">
          <button id="sendButton" type="button" class="btn btn-default">送信</button>
        </div>
      </div>
      <div class="row">
        <div class="col-sm-4 col-sm-offset-2">
          <ul id="messageList" class="list-unstyled">
          </ul>
        </div>
      </div>
    </div>
  </div>
  <script src="/webjars/jquery/1.12.4/jquery.min.js"></script>
  <script src="/webjars/stomp-websocket/2.3.3-1/stomp.js"></script>
  <script src="/webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
  <script>
    $(function() {
      var endpoint = 'ws://' + location.host + '/endpoint';
      var subscribePrefix = '/topic/';
      var stompClient = null;

      $('#connectButton').click(function() {

        $("#messageList").empty();

        stompClient = Stomp.over(new WebSocket(endpoint));
        stompClient.connect({}, function() {
          stompClient.subscribe(
              subscribePrefix + $('#roomName').val(),
              function(message) {
                $('#messageList').prepend($('<li>').text(message.body));
              });

          $('#roomName').prop('disabled', true);
          $('#connectButton').prop('disabled', true);
          $('#disconnectButton').prop('disabled', false);
        });
      });

      $('#disconnectButton').click(function() {

        stompClient.disconnect();
        stompClient = null;

        $('#roomName').prop('disabled', false);
        $('#connectButton').prop('disabled', false);
        $('#disconnectButton').prop('disabled', true);
      });

      $('#sendButton').click(function() {
        if (!stompClient) {
          alert('未接続です。');
          return;
        }

        stompClient.send(subscribePrefix + $('#roomName').val(), {}, $('#message').val());
      });
    });
  </script>
</body>
</html>

クライアント間でメッセージをやり取りするといったことが、少しのコードで簡単に実現できますね。

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のドキュメントだと、下記に記載があります。