[Cloud Storage API(V2)] Javaで変更通知(通知サブスクリプション)を登録・削除する

Cloud Storage API V2(Java)を使用して、
Cloud Storageの変更通知(通知サブスクリプション)を登録・削除する方法
についてご紹介します。

Cloud Storageの変更通知の受信はPub/SubというGCPのサービスを利用します。

:::note info
Pub/Sub、Cloud Functionsの準備や設定に関しては、
[⧉[Pub/Sub - Cloud Functions] Google Cloud APIからの通知を受け取る](https://qiita.com/doran/items/343dac28c46143d61917)
を参照ください。
:::

:::note info
APIを利用する環境の準備から始める場合や、コードを実行する際は、
[⧉[Cloud Storage API(V2)] JavaでCloud Storageを使う](https://qiita.com/doran/items/2a5f7ef8070fd5b8c94d)
を参照ください。
:::

|No||目次|
|---|---|---|
|1||[変更通知を登録](#1-変更通知を登録)|
||1|[スコープ](#11-スコープ)|
||2|[実行](#12-実行)|
||3|[レスポンスの内容](#13-レスポンスの内容)|
||4|[通知の内容](#14-通知の内容)|
|2||[変更通知を削除(停止)](#2-変更通知を削除停止)|
||1|[スコープ](#21-スコープ)|
||2|[実行](#22-実行)|
||3|[レスポンスの内容](#23-レスポンスの内容)|

## 1. 変更通知を登録
指定したバケットの変更通知を登録します。

### 1.1. スコープ
このAPIを実行するには、以下のいずれかのスコープを指定してください。
```
StorageScopes.CLOUD_PLATFORM
StorageScopes.DEVSTORAGE_FULL_CONTROL
```
[⧉[Cloud Storage API(V2)] JavaでCloud Storageを使う(2.2 Storageインスタンスを取得)](https://qiita.com/doran/items/2a5f7ef8070fd5b8c94d#22-storageインスタンスを取得)
でスコープを指定してください。

### 1.2. 実行

Pub/Subトピック名では、通知を受信するPub/Subのトピック名を設定します。

Pub/Subのトピック名については、
[⧉[Pub/Sub - Cloud Functions] Google Cloud APIからの通知を受け取る(3. Pub/Sub でサブスクリプションを作成)](https://qiita.com/doran/items/343dac28c46143d61917#3-pubsub-でサブスクリプションを作成)
を参照ください。

```java
public static void main(String[] args) throws Exception{
    Storage storage = getStorage();
    Storage.Notifications notifications = storage.notifications();
    
    Notification content = new Notification();
    content.setTopic("Pub/Subトピック名");
    content.setPayloadFormat("JSON_API_V1");
    
    Storage.Notifications.Insert insert = notifications.insert("バケットID", content);
    
    Notification res = insert.execute();
    System.out.println(res);
}
```
#### 1.2.1. HTTPリクエスト
POST:　https:\//storage.googleapis.com/storage/v1/b/{バケットID}/notificationConfigs
が実行されます。

#### 1.2.2. クエリパラメータ
クエリパラメータはありません。

#### 1.2.2. リクエストボディ
Notificationのsetメソッドにより、リクエストボディを追加できます。

|メソッド|引数|説明|
|---|---|---|
|setTopic|String|【必須】Pub/Subトピック名|
|setPayloadFormat|String|[変更通知で受け取るデータの内容](#変更通知で受け取るデータの内容)|
|setCustomAttributes|Map\<String,String\>|通知を受ける際に受け取る属性リスト|
|setEventTypes|List\<String\>|[通知を受け取るイベントタイプ](#通知のイベントタイプ)|
|setObjectNamePrefix|String|プレフィックス(接頭語)で始まるオブジェクト名の変更通知のみを受け取る|

#### 変更通知で受け取るデータの内容
|定義値|内容|
|---|---|
|JSON_API_V1|変更されたStorageObject([MessageのgetData()](#message))を受け取る|
|NONE|変更されたStorageObject([MessageのgetData()](#message))は受け取らない|

### 1.3. レスポンスの内容
#### Notifications
|メソッド|戻り値|説明|
|---|---|---|
|getKind|String|リソースの種類<br>固定文字列："storage#notifications"|
|getItems|List\<[Notification](#notification)\>|通知リスト|

#### Notification
|メソッド|戻り値|説明|
|---|---|---|
|getId|String|通知ID|
|getKind|String|リソースの種類<br>固定文字列："storage#notification"|
|getSelfLink|String|通知の正規URL|
|getEtag|String|サブスクリプション通知のHTTPエンティティタグ|
|getEventTypes|List\<String\>|[通知のイベントタイプ](#通知のイベントタイプ)|
|getObjectNamePrefix|String|通知されるオブジェクト名のプレフィックス(接頭語)|
|getPayloadFormat|String|ペイロードの目的のコンテンツ<br>"JSON_API_V1"、"NONE"|
|getTopic|String|通知サブスクリプションが発行するPub/Subトピック|
|getCustomAttributes|Map\<String,String\>|Pub/Subメッセージに添付する追加属性|

#### 通知のイベントタイプ
|定義値|内容|
|---|---|
|OBJECT_FINALIZE|新しいオブジェクトがバケット内に正常に作成された|
|OBJECT_METADATA_UPDATE|既存のオブジェクトのメタデータが変更された|
|OBJECT_DELETE|オブジェクトが完全に削除された|
|OBJECT_ARCHIVE|【バケットでオブジェクトのバージョニングが有効になっている場合のみ】<br><br>非現行バージョンになった<br>(オブジェクトのライブバージョンが明示的に非現行になったため、または同じ名前のオブジェクトのアップロードによって置き換えられたため)|

### 1.4. 通知の内容

#### 1.4.1 Cloud Functionsの実装例

MessageのgetData()では、StorageObjectの型で取得できます。
ただしValueの型が全て文字列であるため、JSONパースをStorageObjectではなく、
Map\<String,String\>にしてます。
値を取得する際は、[StorageObjectクラスのメンバ名](#storageobject)をキーとして取得してください。

```java
import com.google.cloud.functions.CloudEventsFunction;
import com.google.events.cloud.pubsub.v1.MessagePublishedData;
import com.google.events.cloud.pubsub.v1.Message;
import com.google.gson.Gson;
import io.cloudevents.CloudEvent;
import java.util.Base64;
import java.util.logging.Logger;
import java.util.Map;
import com.google.gson.reflect.TypeToken;

public class PubSubFunction implements CloudEventsFunction {
  private static final Logger logger = Logger.getLogger(PubSubFunction.class.getName());

   @Override
  public void accept(CloudEvent event) {
    logger.info("Event: " + event.getId());
    logger.info("Event Type: " + event.getType());

    String cloudEventData = new String(event.getData().toBytes());
    logger.info("Event Data: " + cloudEventData);

    Gson gson = new Gson();
    MessagePublishedData data = gson.fromJson(cloudEventData, MessagePublishedData.class);
    Message message = data.getMessage();
    if(message!=null){
      String encodedData = message.getData();
      if(encodedData!=null){
        String decodedData = new String(Base64.getDecoder().decode(encodedData));
        logger.info("StorageObjectのJSON表記: " + decodedData);
        
        Map<String,String> map = gson.fromJson(decodedData, new TypeToken<Map<String,String>>(){}.getType());
        logger.info("StorageObjectのgetId():" + map.get("id"));
      }
    }
  }
}
```

#### 1.4.2. Cloud Functonsで受け取るパラメータ

#### CloudEvent
|メソッド|戻り値|説明|
|---|---|---|
|getId|String|Cloud FunctionsのイベントID|
|getType|String|Cloud Functionsのイベントの種類<br><br>```google.cloud.pubsub.topic.v1.messagePublished```|
|getData|[CloudEventData](#cloudeventdata)|Cloud FunctionsのJSONイベントデータ(バイトデータ)|

#### CloudEventData
|メソッド|戻り値|説明|
|---|---|---|
|toBytes|byte配列|[MessagePublishedData](#messagepublisheddata)クラスのJSON文字列のバイト配列|

#### MessagePublishedData
|メソッド|戻り値|説明|
|---|---|---|
|getMessage|[Message](#message)|メッセージの内容|
|getSubscription|String|Pub/SubのサブスクリプションID|

#### Message
|メソッド|戻り値|説明|
|---|---|---|
|getMessageID|String|メッセージID|
|getData|String|[Base64でエンコードされたStorageObjectデータ](#storageobject)|
|getAttributes|Map\<String,String\>|[属性情報](#属性情報)|

#### 属性情報
|属性値|内容|
|---|---|
|notificationConfig|この通知をトリガーする通知設定の識別子|
|eventType|[通知のイベントタイプ](#通知のイベントタイプ)|
|payloadFormat|[変更通知で受け取るデータの内容](#変更通知で受け取るデータの内容)|
|bucketId|変更されたオブジェクトを含むバケット名|
|objectId|変更されたオブジェクトの名前|
|objectGeneration|変更されたオブジェクトの世代番号(Generation)|
|eventTime|イベントが発生した時間|

#### StorageObject
|キー|型|説明|
|---|---|---|
|id|String|オブジェクトID|
|kind|String|リソースの種類<br>固定文字列："storage#object"|
|name|String|オブジェクトの名前|
|bucket|String|バケット名|
|size|BigInteger|データサイズ(バイト単位)|
|generation|Long|世代番号(generation)|
|metageneration|Long|メタ世代番号(metageneration)|
|etag|String|オブジェクトのHTTPエンティティタグ|
|mediaLink|String|オブジェクトのデータをダウンロードするためのURL|
|selfLink|String|オブジェクトのURL|
|contentType|String|ContentオブジェクトデータのContent-Type|
|md5Hash|String|データのMD5ハッシュ値|
|crc32c|String|オブジェクトのcrc32cハッシュ値|
|storageClass|String|オブジェクトのストレージクラス|
|timeCreated|DateTime|オブジェクトの作成時刻|
|timeStorageClassUpdated|DateTime|オブジェクトのストレージクラスが最後に変更された時刻|
|updated|DateTime|オブジェクトのメタデータ変更時刻|

StorageObjectのサンプル
```json
{
  "kind": "storage#object",
  "id": "XXX/YYY/1714228298819711",
  "selfLink": "https://www.googleapis.com/storage/v1/b/XXX/o/YYY",
  "mediaLink": "https://storage.googleapis.com/download/storage/v1/b/XXX/o/YYY?generation=1714228298819711&alt=media",
  "name": "YYY",
  "bucket": "XXX",
  "generation": "387971398b98819711",
  "metageneration": "1",
  "contentType": "text/tab-separated-values",
  "storageClass": "STANDARD",
  "size": "3124",
  "md5Hash": "qi3YtM80qPZ+BLDKAD0w==",
  "crc32c": "mvelbkjA==",
  "etag": "CP/QDBLKKJAoUDEAE=",
  "timeCreated": "2024-04-27T14:31:38.880Z",
  "updated": "2024-04-27T14:31:38.880Z",
  "timeStorageClassUpdated": "2024-04-27T14:31:38.880Z"
}
```

## 2. 変更通知を削除(停止)
登録済みの変更通知を削除(停止)します。

### 2.1. スコープ
このAPIを実行するには、以下のいずれかのスコープを指定してください。
```
StorageScopes.CLOUD_PLATFORM
StorageScopes.DEVSTORAGE_FULL_CONTROL
```
[⧉[Cloud Storage API(V2)] JavaでCloud Storageを使う(2.2 Storageインスタンスを取得)](https://qiita.com/doran/items/2a5f7ef8070fd5b8c94d#22-storageインスタンスを取得)
でスコープを指定してください。

### 2.2. 実行
通知IDは、[NotificationのgetId()](#notification)で取得できる値を使用します。

```java
public static void main(String[] args) throws Exception{
    Storage storage = getStorage();
    Storage.Notifications notifications = storage.notifications();
    
    Storage.Notifications.Delete delete = notifications.delete("バケットID", "通知ID");
    
    delete.execute();
}
```
#### 2.2.1. HTTPリクエスト
DELETE:　https:\//storage.googleapis.com/storage/v1/b/{バケットID}/notificationConfigs/{通知ID}
が実行されます。

#### 2.2.2. クエリパラメータ
クエリパラメータはありません。

#### 2.2.3. リクエストボディ
リクエストボディはありません。

### 2.3. レスポンスの内容
レスポンスはありません。
削除に失敗した場合は例外が発生します。

<br>
<br>
おしまい。。