web-dev-qa-db-fra.com

Le codec BSO MongoDB n'est pas utilisé lors de l'encodage de l'objet

J'essaie de stocker un objet dans une base de données MongoDB (en utilisant MongoDB 3.0.2) et j'obtiens un CodecConfigurationException lorsque j'essaie de coder l'objet avec un message d'erreur

Can't find a codec for class Java.time.LocalDate. 

J'ai écrit et inclus un codec pour les objets LocalDate. Les détails suivent.

L'objet, DutyBlock, que j'essaie de stocker a ces variables membres:

public class DutyBlock {
    private LocalDate startDate;
    private LocalDate endDate; //Inclusive
    private int blockLength;
    private double pointValue;
    private ArrayList<Ra> assigned;
}

J'ai écrit le codec suivant pour encoder les objets DutyBlock dans la base de données:

public class DutyBlockCodec implements Codec<DutyBlock> {

    @Override
    public void encode(BsonWriter writer, DutyBlock t, EncoderContext ec) {
        Document document = new Document();
        document.append("startDate", t.getStartDate());
        document.append("endDate", t.getEndDate());
        document.append("blockLength", t.getBlockLength());
        document.append("pointValue", t.getPointValue());
        document.append("assigned", t.getRasOnDuty());

        writer.writeString(document.toJson());  //Line 27 in the error message.
    }

    @Override
    public Class<DutyBlock> getEncoderClass() {
        return DutyBlock.class;
    }

    @Override
    public DutyBlock decode(BsonReader reader, DecoderContext dc) {
        String json = reader.readString();
        return new DutyBlock(Document.parse(json));
    }

}

Puisque MongoDB ne prend actuellement pas en charge le Java.time.LocalDate class, J'ai écrit le codec suivant pour coder les objets LocalDate dans la base de données:

public class LocalDateCodec implements Codec<LocalDate> {

    @Override
    public void encode(BsonWriter writer, LocalDate t, EncoderContext ec) {
        writer.writeString(t.toString());
    }

    @Override
    public Class<LocalDate> getEncoderClass() {
        return LocalDate.class;
    }

    @Override
    public LocalDate decode(BsonReader reader, DecoderContext dc) {
        String date = reader.readString();
        return LocalDate.parse(date);
    }
}

J'ai ajouté les deux Codec (avec un pour le type Ra) au CodecRegistry au niveau MongoClient lors de l'instanciation du MongoClient.

public class DutyScheduleDB {
    private MongoClient mongoClient;
    private MongoDatabase db;

    public DutyScheduleDB() {
        CodecRegistry codecRegistry = 
                CodecRegistries.fromRegistries(
                        CodecRegistries.fromCodecs(new LocalDateCodec(), new DutyBlockCodec(), new RaCodec()),
                        MongoClient.getDefaultCodecRegistry());
        MongoClientOptions options = MongoClientOptions.builder()
                .codecRegistry(codecRegistry).build();
        mongoClient = new MongoClient(new ServerAddress(), options);
        db = mongoClient.getDatabase("DutySchedulerDB");
    }
    . (More code not shown)
    .
    .
}

J'essaie de stocker un ArrayList d'objets DutyBlock dans le cadre d'un org.bson.Document dans la base de données MongoDB.

public void storeScheduledCalendar(String id,
        String calendarName,
        ArrayList<DutyBlock> cal) {
    //Access collection of scheduled calendars.
    MongoCollection collection = db.getCollection("ScheduledCalendars");
    //Query parameter is uuid + calendarName.
    Document doc = new Document("name", id + calendarName);
    doc.append("dutyBlocks", cal);
    //Insert doc to collection.
    collection.insertOne(doc); //Line 59 in the error message.
}

Cependant, je rencontre ce message d'erreur:

Exception in thread "main" org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class Java.time.LocalDate.
at org.bson.codecs.configuration.CodecCache.getOrThrow(CodecCache.Java:46)
at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.Java:63)
at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.Java:37)
at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.Java:174)
at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.Java:189)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.Java:131)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.Java:45)
at org.bson.Document.toJson(Document.Java:294)
at org.bson.Document.toJson(Document.Java:268)
at org.bson.Document.toJson(Document.Java:255)
at SchedulingHeuristic.DutyBlockCodec.encode(DutyBlockCodec.Java:27)
at SchedulingHeuristic.DutyBlockCodec.encode(DutyBlockCodec.Java:16)
at org.bson.codecs.EncoderContext.encodeWithChildContext(EncoderContext.Java:91)
at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.Java:175)
at org.bson.codecs.DocumentCodec.writeIterable(DocumentCodec.Java:197)
at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.Java:170)
at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.Java:189)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.Java:131)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.Java:45)
at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.Java:63)
at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.Java:29)
at com.mongodb.connection.InsertCommandMessage.writeTheWrites(InsertCommandMessage.Java:99)
at com.mongodb.connection.InsertCommandMessage.writeTheWrites(InsertCommandMessage.Java:43)
at com.mongodb.connection.BaseWriteCommandMessage.encodeMessageBody(BaseWriteCommandMessage.Java:112)
at com.mongodb.connection.BaseWriteCommandMessage.encodeMessageBody(BaseWriteCommandMessage.Java:35)
at com.mongodb.connection.RequestMessage.encode(RequestMessage.Java:132)
at com.mongodb.connection.BaseWriteCommandMessage.encode(BaseWriteCommandMessage.Java:89)
at com.mongodb.connection.WriteCommandProtocol.sendMessage(WriteCommandProtocol.Java:170)
at com.mongodb.connection.WriteCommandProtocol.execute(WriteCommandProtocol.Java:73)
at com.mongodb.connection.InsertCommandProtocol.execute(InsertCommandProtocol.Java:66)
at com.mongodb.connection.InsertCommandProtocol.execute(InsertCommandProtocol.Java:37)
at com.mongodb.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.Java:155)
at com.mongodb.connection.DefaultServerConnection.executeProtocol(DefaultServerConnection.Java:219)
at com.mongodb.connection.DefaultServerConnection.insertCommand(DefaultServerConnection.Java:108)
at com.mongodb.operation.MixedBulkWriteOperation$Run$2.executeWriteCommandProtocol(MixedBulkWriteOperation.Java:416)
at com.mongodb.operation.MixedBulkWriteOperation$Run$RunExecutor.execute(MixedBulkWriteOperation.Java:604)
at com.mongodb.operation.MixedBulkWriteOperation$Run.execute(MixedBulkWriteOperation.Java:363)
at com.mongodb.operation.MixedBulkWriteOperation$1.call(MixedBulkWriteOperation.Java:148)
at com.mongodb.operation.MixedBulkWriteOperation$1.call(MixedBulkWriteOperation.Java:141)
at com.mongodb.operation.OperationHelper.withConnectionSource(OperationHelper.Java:186)
at com.mongodb.operation.OperationHelper.withConnection(OperationHelper.Java:177)
at com.mongodb.operation.MixedBulkWriteOperation.execute(MixedBulkWriteOperation.Java:141)
at com.mongodb.operation.MixedBulkWriteOperation.execute(MixedBulkWriteOperation.Java:72)
at com.mongodb.Mongo.execute(Mongo.Java:747)
at com.mongodb.Mongo$2.execute(Mongo.Java:730)
at com.mongodb.MongoCollectionImpl.executeSingleWriteRequest(MongoCollectionImpl.Java:482)
at com.mongodb.MongoCollectionImpl.insertOne(MongoCollectionImpl.Java:277)
at DutyScheduleDB.storeScheduledCalendar(DutyScheduleDB.Java:59)
at DutyScheduleDB.main(DutyScheduleDB.Java:106)

Il semble que mon codec pour LocalDate ne soit pas utilisé lors de la tentative de codage des objets DutyBlock, bien que j'ai vérifié que la collection que j'essaie de stocker le org.bson.Document in contient en effet le LocalDateCodec via un

System.out.println(collection.getCodecRegistry().get(LocalDate.class));

Quelqu'un peut-il expliquer pourquoi cela se produit?

26
desrepair

Après plusieurs jours de recherche, j'ai trouvé une solution.

Le DutyBlockCodec dépend du LocalDateCodec (que j'ai créé) pour encoder/décoder. Cette dépendance n'est pas satisfaite simplement en ajoutant les deux codecs dans le même registre de codecs. La solution consiste à passer un objet CodecRegistry contenant les codecs dont DutyBlockCodec dépend (par exemple un CodecRegistry contenant en son sein le LocalDateCodec) au DutyBlockCodec, qui est stocké en tant que variable membre. Afin d'utiliser la LocalDateCodec pour encoder, j'utilise la méthode EncoderContext.encodeWithChildContext(), en passant le codec, le scripteur et l'élément à encoder. De plus, j'écris des champs individuels plutôt que d'écrire un Document en tant que String (comme dans mon code d'origine). Ainsi, le codec DutyBlock finit par ressembler à ceci:

public class DutyBlockCodec implements Codec<DutyBlock> {
    private final CodecRegistry codecRegistry;

    public DutyBlockCodec(final CodecRegistry codecRegistry) {
        this.codecRegistry = codecRegistry;
    }

    @Override
    public void encode(BsonWriter writer, DutyBlock t, EncoderContext ec) {
        writer.writeStartDocument();
            Codec dateCodec = codecRegistry.get(LocalDate.class);
            writer.writeName("startDate");
            ec.encodeWithChildContext(dateCodec, writer, t.getStartDate());
            writer.writeName("endDate");
            ec.encodeWithChildContext(dateCodec, writer, t.getEndDate());
            writer.writeName("blockLength");
            writer.writeInt32(t.getBlockLength());
            writer.writeName("pointValue");
            writer.writeDouble(t.getPointValue());

            //Writing ArrayList of RAs
            writer.writeName("assigned");
            writer.writeStartArray();
                for (Ra ra : t.getRasOnDuty()) {
                    Codec raCodec = codecRegistry.get(Ra.class);
                    ec.encodeWithChildContext(raCodec, writer, ra);
                }
            writer.writeEndArray();
        writer.writeEndDocument();
    }

    @Override
    public Class<DutyBlock> getEncoderClass() {
        return DutyBlock.class;
    }

    @Override
    public DutyBlock decode(BsonReader reader, DecoderContext dc) {
        reader.readStartDocument();
            Codec<LocalDate> dateCodec = codecRegistry.get(LocalDate.class);
            reader.readName();
            LocalDate startDate = dateCodec.decode(reader, dc);
            reader.readName();
            LocalDate endDate = dateCodec.decode(reader, dc);
            reader.readName();
            int blockLength = reader.readInt32();
            reader.readName();
            double pointValue = reader.readDouble();

            //Reading ArrayList of RAs
            reader.readName();
            Codec<Ra> raCodec = codecRegistry.get(Ra.class);
            ArrayList<Ra> rasOnDuty = new ArrayList<>();
            reader.readStartArray();
                while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
                    rasOnDuty.add(raCodec.decode(reader, dc));
                }
            reader.readEndArray();
        reader.readEndDocument();

        return new DutyBlock(startDate, endDate, blockLength, pointValue, rasOnDuty);
    }

}

DutyBlockCodec dépend d'un autre codec, et nécessite donc un CodecRegistry à transmettre à son constructeur. Bien que je pense qu'il est possible de créer un CodecRegistry avec le LocalDateCodec, passez-le comme argument au constructeur de DutyBlockCodec, puis créez un autre CodecRegistry contenant à la fois LocalDateCodec et DutyBlockCodec, c'est assez déroutant, et MongoDB fournit une fonctionnalité, CodecProvider pour faciliter ce processus.

En utilisant l'interface CodecProvider, j'ai écrit un DutyBlockCodecProvider

public class DutyBlockCodecProvider implements CodecProvider {
    @Override
    public <T> Codec<T> get(Class<T> type, CodecRegistry cr) {
        if (type == DutyBlock.class) {
            return (Codec<T>) new DutyBlockCodec(cr);
        }
        return null;
    }
}

J'ai ajouté ces CodecProviders au client MongoDB en utilisant la méthode CodecRegistries.fromProviders().

CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
            CodecRegistries.fromCodecs(new LocalDateCodec()),
            CodecRegistries.fromProviders(
                    new RaCodecProvider(),
                    new DutyBlockCodecProvider(),
                    new ScheduledDutyCodecProvider()),
            MongoClient.getDefaultCodecRegistry());  
    MongoClientOptions options = MongoClientOptions.builder()
            .codecRegistry(codecRegistry).build();
    mongoClient = new MongoClient(new ServerAddress(), options);
    db = mongoClient.getDatabase("DutySchedulerDB");

Mon code source pour ce projet se trouve à https://github.com/desrepair/DutyScheduler Je suis prêt à répondre à toutes les questions que les gens peuvent avoir.

22
desrepair