'Java - storing Pojo with Path field in MongoDB

I'm using mongo-java-driver to store POJOs in MongoDB and so far I've had no issues, with doing just this:

CodecProvider pojoCodecProvider = PojoCodecProvider.builder().automatic(true).build();
CodecRegistry pojoCodecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(pojoCodecProvider));

However when I added a Path variable to my POJO I started getting the following exception:

Exception in thread "Thread-2" org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class ...

I figured I'd write a codec for Path like this:

public class PathCodec implements Codec<Path> {

    @Override
    public void encode(BsonWriter writer, Path path, EncoderContext encoderContext) {
        writer.writeString(path.toString());    
    }

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

    @Override
    public Path decode(BsonReader reader, DecoderContext decoderContext) {
        return Paths.get(reader.readString());
    }   
    
}

and then register it like so:

CodecProvider pojoCodecProvider = PojoCodecProvider.builder().automatic(true).build();
PathCodec pathCodec = new PathCodec();      
CodecRegistry pojoCodecRegistry = fromRegistries(getDefaultCodecRegistry(), fromCodecs(pathCodec), fromProviders(pojoCodecProvider));

This doesn't seem to do the trick though i.e. the exception continues to occur.

Any point in the right direction would be much appreciated!



Solution 1:[1]

This is one of the most complex systems I've ever worked with. There are multiple ways to implement a solution, and each solution is driver version dependent. So with that caveat, here is an option for you using similar classes:

@Document(collection = "example")
public class ExampleEntity implements Serializable {

    @Id
    private Long id;
    private DateTime date;

    public ExampleEntity() {
    }

    public ExampleEntity(Long id, DateTime date) {
        this.id = id;
        this.date = date;
    }

...
}

Problem: MongoDB doesn't know how to convert joda DateTime to/from the MongoDB (BSON) data type DATE_TIME

Solution:

public class DateTimeCodec implements Codec<DateTime> {
    @Override
    public void encode(BsonWriter writer, DateTime value, EncoderContext encoderContext) {
        writer.writeDateTime(value.getMillis()); // Take the joda DateTime field from the java class, convert to long, write to BSON
    }

    @Override
    public DateTime decode(BsonReader reader, DecoderContext decoderContext) {
        return new DateTime(reader.readDateTime());
    }

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

public class ExampleEntityCodec implements Codec<ExampleEntity> {
    private final CodecRegistry codecRegistry;

    public ExampleEntityCodec(CodecRegistry codecRegistry) {
        this.codecRegistry = codecRegistry;
    }

    @Override
    public void encode(BsonWriter writer, ExampleEntity value, EncoderContext encoderContext) {
        Codec<DateTime> dateTimeCodec = codecRegistry.get(DateTime.class);
        writer.writeStartDocument();
            writer.writeName("_id");
            writer.writeInt64(value.getId());
            writer.writeName("date");
            dateTimeCodec.encode(writer, value.getDate(), encoderContext); 
        writer.writeEndDocument();

    }

    @Override
    public ExampleEntity decode(BsonReader reader, DecoderContext decoderContext) {
        Codec<DateTime> dateTimeCodec = codecRegistry.get(DateTime.class);
        reader.readStartDocument();
            reader.readName();
            Long id = reader.readInt64();
            reader.readName();
            String className = reader.readString(); // ignoring this
            reader.readName();
            DateTime dateTimeFromDB = dateTimeCodec.decode(reader, decoderContext);
            ExampleEntity exampleEntity = new ExampleEntity(id, dateTimeFromDB);
        reader.readEndDocument();
        return exampleEntity;
    }

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

Tell Java when it is time to use your Codecs:

public class ExampleEntityCodecProvider implements CodecProvider {
    @Override
    public <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) {
        if(clazz == ExampleEntity.class) {
            return (Codec<T>) new ExampleEntityCodec(registry);
        }
        return null;
    }
}

Build the codecs:

...
    static CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
            MongoClientSettings.getDefaultCodecRegistry(),  // Provides Codecs for Java's native datatypes
            CodecRegistries.fromCodecs(
                    new DateTimeCodec() // Required to convert BsonType DATE_TIME to joda.time.DateTime
            ),
            CodecRegistries.fromProviders(
                    new ExampleEntityCodecProvider(),
                    PojoCodecProvider.builder()
                            .automatic(true).build()
            )
    );
...

The first Codec Codec<DateTime> is only used on a single field, whereas the Codec<ExampleEntity> is used to imperatively write the key/value pairs for an entire class. Remember, when you're encoding you're telling Mongo which BSON data types you want to use.

In your case, it looks like you're trying to Stringify a class and then construct it again from reading the string. You should instead encode the keys and values of your Java class into the appropriate keys and values you want written to the database, and then do the opposite conversion when decoding.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 DPCII