はじめに

IIIFサーバーのCantaloupeでAzure Storageを使用している際、IIIF URLのidentifierと実際のAzure Storage上のファイルパスが異なる場合があります。本記事では、この問題をdelegate scriptを使って解決する方法を詳しく解説します。

課題

以下のようなファイル構造で画像を管理しているとします:

Azure Storage Container: mycontainer
├── images/
│   ├── collection1/
│   │   ├── item001/
│   │   │   └── item001_001.jpg
│   │   └── item002/
│   │       └── item002_001.jpg
│   └── collection2/
│       └── ...

しかし、IIIF URLでは以下のようにアクセスしたい:

https://example.com/iiif/3/collection1/item001/item001_001.jpg/info.json

この場合、IIIF URLのidentifier (collection1/item001/item001_001.jpg) と実際のAzure Storageのパス (images/collection1/item001/item001_001.jpg) が異なります。

AzureStorageSourceには、S3SourceのようなPATH_PREFIX設定が存在しないため、この問題を解決するためにはdelegate scriptを使用する必要があります。

解決方法

1. Docker Compose設定

services:
  cantaloupe:
    image: islandora/cantaloupe:main
    environment:
      CANTALOUPE_SOURCE_STATIC: AzureStorageSource
      CANTALOUPE_AZURESTORAGESOURCE_ACCOUNT_NAME: ${AZURE_STORAGE_ACCOUNT_NAME}
      CANTALOUPE_AZURESTORAGESOURCE_ACCOUNT_KEY: ${AZURE_STORAGE_ACCOUNT_KEY}
      CANTALOUPE_AZURESTORAGESOURCE_CONTAINER_NAME: ${AZURE_STORAGE_CONTAINER_NAME}
      CANTALOUPE_AZURESTORAGESOURCE_LOOKUP_STRATEGY: ScriptLookupStrategy  # 重要
      CANTALOUPE_DELEGATE_SCRIPT_ENABLED: "true"
      CANTALOUPE_DELEGATE_SCRIPT_PATHNAME: "/opt/cantaloupe/delegates.rb"
    volumes:
      - "./delegates.rb:/opt/cantaloupe/delegates.rb:ro"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.cantaloupe.rule=Host(`example.com`)"
      - "traefik.http.routers.cantaloupe.entrypoints=websecure"
      - "traefik.http.routers.cantaloupe.tls=true"
      - "traefik.http.services.cantaloupe.loadbalancer.server.port=8182"
    restart: always

2. Delegate Script (delegates.rb)

開発・デバッグ版

最初はデバッグ出力を含む版で動作確認を行います:

##
# Delegate script for AzureStorageSource with ScriptLookupStrategy
#
class CustomDelegate

  # コンテキストを保存するためのアクセサー
  attr_accessor :context

  ##
  # IIIF URLのidentifierをAzure Storage blob keyに変換
  #
  def azurestoragesource_blob_key
    identifier = context['identifier'] if context
    
    if identifier
      blob_key = "images/#{identifier}"
      puts "DEBUG: Input identifier: #{identifier}"
      puts "DEBUG: Output blob key: #{blob_key}"
      return blob_key
    end
    
    return nil
  end

  ##
  # 認証メソッド
  #
  def pre_authorize(options = {})
    puts "DEBUG: pre_authorize called"
    true
  end

  def authorize(options = {})
    puts "DEBUG: authorize called"
    true
  end

  ##
  # その他必要なメソッド
  #
  def extra_iiif2_information_response_keys(options = {})
    {}
  end

  def extra_iiif3_information_response_keys(options = {})
    {}
  end

  def redactions(options = {})
    []
  end

  def metadata(options = {})
    nil
  end

end

本番用クリーン版

動作確認後、本番環境用にデバッグ出力を削除:

##
# Complete delegate script for AzureStorageSource with ScriptLookupStrategy
#
class CustomDelegate

  # コンテキストを保存するためのアクセサー
  attr_accessor :context

  ##
  # Cantaloupeから呼び出されるメイン機能
  # IIIF URLのidentifierをAzure Storage blob keyに変換
  #
  def azurestoragesource_blob_key
    identifier = context['identifier'] if context
    return identifier ? "images/#{identifier}" : nil
  end

  ##
  # 認証メソッド - すべてのリクエストを許可
  #
  def pre_authorize(options = {})
    true
  end

  def authorize(options = {})
    true
  end

  ##
  # 追加のIIIF情報レスポンスキー(使用しない場合は空)
  #
  def extra_iiif2_information_response_keys(options = {})
    {}
  end

  def extra_iiif3_information_response_keys(options = {})
    {}
  end

  ##
  # 墨塗り機能(使用しない場合は空のリスト)
  #
  def redactions(options = {})
    []
  end

  ##
  # メタデータ追加(使用しない場合はnilを返す)
  #
  def metadata(options = {})
    nil
  end

end

トラブルシューティング

よくあるエラーと対処法

1. Cannot invoke "edu.illinois.library.cantaloupe.delegate.DelegateProxy.getAzureStorageSourceBlobKey()" because the return value is null

原因 : Delegate scriptが正しく読み込まれていない、または構文エラーがある

対処法 :

  • Delegate scriptの構文を確認: ruby -c delegates.rb
  • attr_accessor :context が定義されているか確認
  • コンテナログでdelegate scriptの読み込み状況を確認

2. undefined method 'context=' for CustomDelegate

原因 : attr_accessor :context が定義されていない

対処法 : クラスの先頭に attr_accessor :context を追加

3. undefined method 'pre_authorize' など

原因 : 必要なdelegate methodsが定義されていない

対処法 : 上記の完全なdelegate scriptを参考に、必要なメソッドを追加

4. class org.jruby.RubyHash cannot be cast to class java.lang.String

原因 : metadata()メソッドが不正な型を返している

対処法 :

  • メタデータを使用しない場合はnilを返す
  • 空のハッシュ{}ではなくnilを返すことが重要
def metadata(options = {})
  nil  # 空のハッシュ{}ではなくnilを返す
end

デバッグ方法

  1. ログ監視 :
docker logs container-name -f
  1. 構文チェック :
ruby -c delegates.rb
  1. 段階的テスト :
    最初はシンプルなdelegate scriptから始めて、徐々に機能を追加

動作確認

1. サービス起動

docker compose up -d

2. テストアクセス

# Info.json取得
curl https://example.com/iiif/3/collection1/item001/item001_001.jpg/info.json

# 画像取得
curl https://example.com/iiif/3/collection1/item001/item001_001.jpg/full/300,/0/default.jpg

3. ログ確認(デバッグ版使用時)

期待されるログ出力:

DEBUG: pre_authorize called
DEBUG: authorize called
DEBUG: Input identifier: collection1/item001/item001_001.jpg
DEBUG: Output blob key: images/collection1/item001/item001_001.jpg

まとめ

Cantaloupeのdelegate scriptを使用することで、IIIF URLと実際のストレージパスの差分を吸収できます。重要なポイント:

  1. ScriptLookupStrategyの設定: AzureStorageSourceでdelegate scriptを使用するために必須
  2. attr_accessor :context : Cantaloupeがコンテキスト情報を設定するために必要
  3. azurestoragesource_blob_keyメソッド: パス変換のメインロジック
  4. その他のdelegate methods : エラーを避けるために最低限必要

本記事で紹介した方法により、柔軟なファイル管理とIIIF URLの設計が可能になります。特に、既存のファイル構造を変更せずにIIIF対応を行いたい場合に有効です。

参考リンク