Java Mongo Driver Codec を登録する


YouTube Data API の 戻り値を Jackson を使用して Map に変換し、結果を MongoDB に登録しようとしたところ、以下のエラーが発生しました。

org.bson.codecs.configuration.CodecConfigurationException: 
Can't find a codec for class com.google.api.client.util.DateTime.

Codec が見つからないということなので、Codec を実装し登録してみます。


参考サイト


Library version

関連 Liblary の Version は以下の通りです。

  • YouTube Data API (v3)
    <dependency>
        <groupId>com.google.apis</groupId>
        <artifactId>google-api-services-youtube</artifactId>
        <version>v3-rev178-1.22.0</version>
    </dependency>
  • Java Mongo Driver
    <dependency>
        <groupId>org.mongodb</groupId>
        <artifactId>mongo-java-driver</artifactId>
        <version>3.2.2</version>
    </dependency>
  • Jackson
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.7.4</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.6.3</version>
    </dependency>

そもそもの発生理由 (推測)

何故、Codec でエラーとなるのか推測ではありますが、処理の流れとともに記載します。

1.処理の流れ

以下のような処理の流れで、YouTube Data API (v3) の取得結果を、MongoDB に登録しています。

1-1. YouTube Data API で 検索、戻り値 の List<SearchResult> を取得

YouTube Data API で 検索後、 戻り値 を SearchResult の List 形式で取得しています。

1-2. SearchResult を Jackson を 使用して、Map<String, Object> に変換

ObjectMapper を使用して Map に変換しています。

    /**
     * convertValue
     *
     * @param <T>
     * @param object
     * @param clazz
     * @return
     */
    public static Map<String, Object> convertValue(Object object) {
        ObjectMapper objectMapper = new ObjectMapper();
        T result = objectMapper.convertValue(object, Map.class);
        return result;
    }

1-3. MongoDriver で、 2 で変換生成した Map を MongoDB に insert

Map を BasicDBObject に渡して登録しています。

2.SearchResult のどこに、DateTime が存在するか?

Class 図は以下のようになります。
SearchResult Class Diagram

3.MongoDriver は Codec 登録されていない Object の処理はできない。

基本的な型の Object については、Mongo Driver 側が Codec を保持しており、変換できます。
しかし 末端の階層の Object に Java の基本型を持たない Object は変換できないようです。

SearchResult は、内部で ResourceId というクラスも参照していますが、こちらは、内部のフィールドが String 型のみなので、変換が可能です。

mongo-java-driver/bson/src/main/org/bson/codecs at master · mongodb/mongo-java-driver
にデフォルトで登録されている(と思われる) Codec クラスが存在します。

4.うまく動かす方法

修正の仕方として、以下2つ考えられます。

1.ObjectMapper で Mapping する際に、DateTime をあらかじめ MongoDB が解釈できる型に変えておく。

2.解釈できない型の Codec を登録する。

今回は 2. の Codec 登録する方法を試してみました。


Codec クラスを作成 と 登録

1. Codec クラスを作成する

以下の通り、Codec クラスを作成しました。
String 型への相互変換用の、DateTime#parseRfc3339()DateTime#toStringRfc3339() がいましたのでそれを使用しています。

  • GoogleDateTimeCodec.java
    import com.google.api.client.util.DateTime;
    import org.bson.BsonReader;
    import org.bson.BsonWriter;
    import org.bson.codecs.Codec;
    import org.bson.codecs.DecoderContext;
    import org.bson.codecs.EncoderContext;
    
    /**
     * GoogleDateTimeCodec
     */
    public class GoogleDateTimeCodec implements Codec<DateTime> {
    
        @Override
        public DateTime decode(BsonReader reader, DecoderContext decoderContext) {
            String date = reader.readString();
            return DateTime.parseRfc3339(date);
        }
    
        @Override
        public void encode(BsonWriter writer, DateTime value, EncoderContext encoderContext) {
            writer.writeString(value.toStringRfc3339());
        }
    
        @Override
        public Class<DateTime> getEncoderClass() {
            return DateTime.class;
        }
    }
    

2. Codecクラスの登録

Guice の Provider を使用しています。

MongoClient の インスタンス生成時に、MongoClientOptions をパラメータとして設定、MongoClientOptions に設定する CodecRegistry に GoogleDateTimeCodec を 追加 しました。
MongoDB に 登録する末端フィールドの変換ができないだけであれば、以下のような登録の仕方でうまくいきました。
末端フィールドではない場合は、StackOverFlowにある通り、Object の依存関係に従って登録する必要がありそうです。

  • MongoDatabaseProvider.java
    import com.google.inject.Provider;
    import com.mongodb.MongoClient;
    import com.mongodb.MongoClientOptions;
    import com.mongodb.ServerAddress;
    import com.mongodb.client.MongoDatabase;
    import org.bson.codecs.configuration.CodecRegistries;
    import org.bson.codecs.configuration.CodecRegistry;
    import xyz.monotalk.festivals4partypeople.models.mongo.codec.GoogleDateTimeCodec;
    
    import java.util.Locale;
    import java.util.ResourceBundle;
    
    /**
     * MongoDatabaseProvider
     *
     * @author Kem
     */
    public class MongoDatabaseProvider implements Provider<MongoDatabase> {
    
        private static final ResourceBundle RB = ResourceBundle.getBundle("mongoDb", Locale.JAPAN);
    
        @Override
        public MongoDatabase get() {
            // New ServerAddress
            ServerAddress serverAddress = newServerAddress();
            // New MongoClientOptions
            MongoClientOptions options = newMongoClientOptions();
            MongoClient client = new MongoClient(serverAddress, options);
    
            // New Database
            String databaseName = RB.getString("databaseName");
            MongoDatabase mongoDatabase = client.getDatabase(databaseName);
            return mongoDatabase;
        }
    
        /**
         * newServerAddress
         *
         * @return
         */
        private ServerAddress newServerAddress() {
            String host = RB.getString("host");
            int port = Integer.valueOf(RB.getString("port"));
            ServerAddress serverAddress = new ServerAddress(host, port);
            return serverAddress;
        }
    
        /**
         * newMongoClientOptions
         *
         * @return
         */
        private MongoClientOptions newMongoClientOptions() {
            CodecRegistry codecRegistry =
                    CodecRegistries.fromRegistries(
                            CodecRegistries.fromCodecs(new GoogleDateTimeCodec()),
                            MongoClient.getDefaultCodecRegistry());
            return MongoClientOptions.builder()
                    .codecRegistry(codecRegistry).build();
        }
    }
    

以上です。

コメント