Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB: Register codecs (Java)

I've been trying for about an hour now to register a codec I made for one of my classes in a game I am working on. The class is called Item. I tried the code and suggestions from these 3 places:

  • https://mongodb.github.io/mongo-java-driver/3.0/bson/codecs/
  • https://matteosilvestri.blogspot.ca/2015/03/overview-of-pluggable-codecs-in-mongodb.html
  • https://gist.github.com/JaiHirsch/cada9eaac3e24ef5a62a

and here is the code I cam up with:

CodecRegistry defaultCodecRegistry = MongoClient.getDefaultCodecRegistry();
MyCodecProvider myCodecProvider = new MyCodecProvider();
ItemCodec itemCodec = new ItemCodec(defaultCodecRegistry);

CodecRegistry codecRegistry = CodecRegistries.fromRegistries(CodecRegistries.fromCodecs(itemCodec), CodecRegistries.fromProviders(myCodecProvider), defaultCodecRegistry);;
MongoClientOptions options = MongoClientOptions.builder().codecRegistry(codecRegistry).build();

client = new MongoClient("localhost:27017", options);

So I built a Codec and codec provider called MyCodecProvider, so what am I doing wrong, how can this be so complicated? Am I missing something? It seems more complicated than it needs to be. If you need more code please ask. Thanks.

Edit: The exact error I get is org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class [Lnet.hollowbit.archipeloserver.items.Item;. Also, I am trying to parse an Item array, do I need to make a codec specifically for the array too?

like image 431
vedi0boy Avatar asked Jan 12 '17 16:01

vedi0boy


People also ask

What is codec registry in MongoDB?

The default codec registry is a set of CodecProvider classes that specify conversion between commonly-used Java and MongoDB types. The driver automatically uses the default codec registry unless you specify a different one.

How does MongoDB connect to MongoClient in Java?

To connect: MongoClient client = MongoClients. create("<<MongoDB URI>>"); To connect to MongoDB on your local instance and default port, you can just omit the URI part of the above, or use a URI like 'mongodb://localhost:27017'.

Can we connect MongoDB with Java?

Before you start using MongoDB in your Java programs, you need to make sure that you have MongoDB CLIENT and Java set up on the machine. You can check Java tutorial for Java installation on your machine. Now, let us check how to set up MongoDB CLIENT. You need to download the jar mongodb-driver-3.11.


2 Answers

You can do Mongo codecs for arrays using ArrayList as follows:

com.example.model.Order

This is the class representing an Order. It includes an ArrayList of items.

package com.example.model;

import java.util.ArrayList;
import org.bson.types.ObjectId;

/**
 * Class representing an Order.
 */
public class Order
{
    private ObjectId id;
    private ArrayList<Item> items;

    /**
     * Default constructor. Needed for testing.
     */
    public Order() {
        this.items = new ArrayList<>();
    }

    /**
     * Sets the id of the Order.
     * @param id The new id of the Order.
     */
    public void setId(ObjectId id) {
        this.id = id;
    }

    /**
     * Gets the id of the Order.
     * @return The id of the Order.
     */
    public ObjectId getId() {
        return this.id;
    }

    /**
     * Sets the items for the Order.
     * @param items The items for the Order.
     */
    public void setItems(ArrayList<Item> items) {
        this.items = items;
    }

    /**
     * Gets the items for the Order.
     * @return The items for the Order.
     */
    public ArrayList<Item> getItems() {
        return items;
    }

    /**
     * Adds an item to the Order.
     * @param item The new Item to add to the Order.
     */
    public void addItem(Item item) {
        this.items.add(item);
    }
}

com.example.model.Item

This is the class representing an Order Item. There can be any number of items that are a part of an order. Items are embedded within the Order document in Mongo.

package com.example.model;

import org.bson.types.ObjectId;

/**
 * Class representing an order item.
 */
public class Item
{
    private ObjectId id;
    private String name;

    /**
     * Constructor.
     */
    public Item() {
        //
    }

    /**
     * Sets the id of the Item.
     * @param id The new id of the Item.
     */
    public void setId(ObjectId id) {
        this.id = id;
    }

    /**
     * Gets the id of the Item.
     * @return The id of the Item.
     */
    public ObjectId getId() {
        return this.id;
    }

    /**
     * Sets the name of the Item.
     * @param name The new name of the Item.
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Gets the name of the Item.
     * @return The name of the Item.
     */
    public String getName() {
        return this.name;
    }
}

com.example.mongo.ItemConverter

Simple converter class that converts Items to/from Documents.

package com.example.mongo;

import com.example.model.Item;
import org.bson.Document;

/**
 * Converts Mongo Documents to/from Items.
 */
public class ItemConverter {
    /**
     * Convert the passed Item into a Mongo Document.
     * @param item The Item that you want to convert into a Mongo Document.
     * @return Returns the Document that was created from the passed Item.
     */
    public Document convert(Item item) {
        Document document = new Document();
        document.put("_id", item.getId());
        document.put("name", item.getName());

        return document;
    }

    /**
     * Convert the passed Mongo Document into an Item.
     * @param document The Document that you want to convert into an Item.
     * @return Returns the Item that was created from the passed Mongo Document.
     */
    public Item convert(Document document) {
        Item item = new Item();
        item.setId(document.getObjectId("_id"));
        item.setName(document.getString("name"));

        return item;
    }
}

com.example.mongo.ItemCodec

Codec for encoding and decoding Items.

package com.example.mongo;

import com.example.model.Item;
import com.mongodb.MongoClient;
import org.bson.BsonReader;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.BsonWriter;
import org.bson.Document;
import org.bson.codecs.Codec;
import org.bson.codecs.CollectibleCodec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.types.ObjectId;

/**
 * Mongo Decoder for Items.
 */
public class ItemCodec implements CollectibleCodec<Item> {

    private final CodecRegistry registry;
    private final Codec<Document> documentCodec;
    private final ItemConverter converter;

    /**
     * Default constructor.
     */
    public ItemCodec() {
        this.registry = MongoClient.getDefaultCodecRegistry();
        this.documentCodec = this.registry.get(Document.class);
        this.converter = new ItemConverter();
    }

    /**
     * Codec constructor.
     * @param codec The existing codec to use.
     */
    public ItemCodec(Codec<Document> codec) {
        this.documentCodec = codec;
        this.registry = MongoClient.getDefaultCodecRegistry();
        this.converter = new ItemConverter();
    }

    /**
     * Registry constructor.
     * @param registry The CodecRegistry to use.
     */
    public ItemCodec(CodecRegistry registry) {
        this.registry = registry;
        this.documentCodec = this.registry.get(Document.class);
        this.converter = new ItemConverter();
    }

    /**
     * Encode the passed Item into a Mongo/BSON document.
     * @param writer The writer to use for encoding.
     * @param item The Item to encode.
     * @param encoderContext The EncoderContext to use for encoding.
     */
    @Override
    public void encode(
                    BsonWriter writer,
                    Item item,
                    EncoderContext encoderContext
                ) {
        Document document = this.converter.convert(item);

        documentCodec.encode(writer, document, encoderContext);
    }

    /**
     * Get the class that this Codec works with.
     * @return Returns the class that this Codec works with.
     */
    @Override
    public Class<Item> getEncoderClass() {
        return Item.class;
    }

    /**
     * Decodes a Mongo/BSON document into an Item.
     * @param reader The reader containing the Document.
     * @param decoderContext The DecoderContext to use for decoding.
     * @return Returns the decoded Item.
     */
    @Override
    public Item decode(BsonReader reader, DecoderContext decoderContext) {
        Document document = documentCodec.decode(reader, decoderContext);
        Item item = this.converter.convert(document);

        return item;
    }

    /**
     * Generates a new ObjectId for the passed Item (if absent).
     * @param item The Item to work with.
     * @return Returns the passed Item with a new id added if there
     * was none.
     */
    @Override
    public Item generateIdIfAbsentFromDocument(Item item) {
        if (!documentHasId(item)) {
            item.setId(new ObjectId());
        }

        return item;
    }

    /**
     * Returns whether or not the passed Item has an id.
     * @param Item The Item that you want to check for
     * the presence of an id.
     * @return Returns whether or not the passed Item has an id.
     */
    @Override
    public boolean documentHasId(Item Item) {
        return (Item.getName() != null);
    }

    /**
     * Gets the id of the passed Item. If there is no id, it will
     * throw an IllegalStateException (RuntimeException).
     * @param Item The Item whose id you want to get.
     * @return Returns the id of the passed Item as a BsonValue.
     */
    @Override
    public BsonValue getDocumentId(Item Item)
    {
        if (!documentHasId(Item)) {
            throw new IllegalStateException("The document does not contain an _id");
        }

        return new BsonString(Item.getName());
    }

}

com.example.mongo.OrderCodec

Codec for encoding/decoding Orders.

package com.example.mongo;

import com.example.model.Item;
import com.example.model.Order;
import com.mongodb.MongoClient;
import java.util.ArrayList;
import org.bson.BsonReader;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.BsonWriter;
import org.bson.Document;
import org.bson.codecs.Codec;
import org.bson.codecs.CollectibleCodec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.types.ObjectId;

/**
 * Mongo decoder for Orders.
 */
public class OrderCodec implements CollectibleCodec<Order> {

    private final CodecRegistry registry;
    private final Codec<Document> documentCodec;
    private final ItemConverter itemConverter;

    /**
     * Default constructor.
     */
    public OrderCodec() {
        this.registry = MongoClient.getDefaultCodecRegistry();
        this.documentCodec = this.registry.get(Document.class);
        this.itemConverter = new ItemConverter();
    }

    /**
     * Codec constructor.
     * @param codec The existing codec to use.
     */
    public OrderCodec(Codec<Document> codec) {
        this.registry = MongoClient.getDefaultCodecRegistry();
        this.documentCodec = codec;
        this.itemConverter = new ItemConverter();
    }

    /**
     * Registry constructor.
     * @param registry The CodecRegistry to use.
     */
    public OrderCodec(CodecRegistry registry) {
        this.registry = registry;
        this.documentCodec = this.registry.get(Document.class);
        this.itemConverter = new ItemConverter();
    }

    /**
     * Encode the passed Order into a Mongo/BSON document.
     * @param writer The writer to use for encoding.
     * @param order The Order to encode.
     * @param encoderContext The EncoderContext to use for encoding.
     */
    @Override
    public void encode(
                    BsonWriter writer,
                    Order order,
                    EncoderContext encoderContext
                ) {
        Document document = new Document();
        document.put("_id", order.getId());
        document.put("items", order.getItems());

        documentCodec.encode(writer, document, encoderContext);
    }

    /**
     * Get the class that this Codec works with.
     * @return Returns the class that this Codec works with.
     */
    @Override
    public Class<Order> getEncoderClass() {
        return Order.class;
    }

    /**
     * Decodes a Mongo/BSON document into an Order.
     * @param reader The reader containing the Document.
     * @param decoderContext The DecoderContext to use for decoding.
     * @return Returns the decoded Order.
     */
    @Override
    public Order decode(BsonReader reader, DecoderContext decoderContext) {
        Document document = documentCodec.decode(reader, decoderContext);

        Order order = new Order();

        order.setId(document.getObjectId("_id"));

        ArrayList<Document> docArr = (ArrayList) document.get("items");
        for (Document doc : docArr) {
            Item item = this.itemConverter.convert(doc);
            order.addItem(item);
        }

        return order;
    }

    /**
     * Generates a new ObjectId for the passed Order (if absent).
     * @param order The Order to work with.
     * @return Returns the passed Order with a new id added if there
     * was none.
     */
    @Override
    public Order generateIdIfAbsentFromDocument(Order order) {
        if (!documentHasId(order)) {
            order.setId(new ObjectId());
        }

        return order;
    }

    /**
     * Returns whether or not the passed Order has an id.
     * @param order The Order that you want to check for
     * the presence of an id.
     * @return Returns whether or not the passed Order has an id.
     */
    @Override
    public boolean documentHasId(Order order) {
        return (order.getId() != null);
    }

    /**
     * Gets the id of the passed Order. If there is no id, it will
     * throw an IllegalStateException (RuntimeException).
     * @param order The Order whose id you want to get.
     * @return Returns the id of the passed Order as a BsonValue.
     */
    @Override
    public BsonValue getDocumentId(Order order) {
        if (!documentHasId(order)) {
            throw new IllegalStateException("The document does not contain an _id");
        }

        return new BsonString(order.getId().toHexString());
    }
}

com.example.main.Main

Main class for the app. Here we register the codecs and create our MongoClient.

package com.example.main;

import com.example.model.Item;
import com.example.model.Order;
import com.example.mongo.ItemCodec;
import com.example.mongo.OrderCodec;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.ServerAddress;
import org.bson.Document;
import org.bson.codecs.Codec;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;

/**
 * Main class.
 */
public class Main {

    /**
     * Main function for the app.
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        CodecRegistry codecRegistry = MongoClient.getDefaultCodecRegistry();
        Codec<Document> documentCodec = codecRegistry.get(Document.class);
        Codec<Item> itemCodec = new ItemCodec(codecRegistry);
        Codec<Order> orderCodec = new OrderCodec(codecRegistry);
        codecRegistry = CodecRegistries.fromRegistries(
                                            MongoClient.getDefaultCodecRegistry(),
                                            CodecRegistries.fromCodecs(
                                                documentCodec,
                                                itemCodec,
                                                orderCodec
                                            )
                                        );

        MongoClientOptions options = MongoClientOptions.builder().codecRegistry(codecRegistry).build();
        MongoClient mongo = new MongoClient(new ServerAddress("localhost", 27018), options);

        // Your code here.
    }
}

From there you can read/write orders and items to/from Mongo.

like image 74
Collin Krawll Avatar answered Sep 29 '22 10:09

Collin Krawll


A better approach will be to implement your own CodecProvider. This way you can manage all used codec implementation. Using your case:

Create MyCodecProvider

public class MyCodecProvider implements CodecProvider {

private final BsonTypeClassMap bsonTypeClassMap;

public MyCodecProvider(final BsonTypeClassMap bsonTypeClassMap) {
    this.bsonTypeClassMap = bsonTypeClassMap;
}

@Override
public <T> Codec<T> get(final Class<T> clazz, final CodecRegistry registry) {
    if (clazz == Document.class) {
        // construct DocumentCodec with a CodecRegistry and a BsonTypeClassMap
        return (Codec<T>) new org.bson.codecs.DocumentCodec(registry, bsonTypeClassMap);
    }
    else if (clazz == Order.class) {
        return (Codec<T>) new OrderCodec(registry);
    }
    else if (clazz == Item.class) {
        return (Codec<T>) new ItemCodec(registry);
    }

    return null;
  }
}

Now you may simple do this in your main class.

Map<BsonType, Class<?>> replacements = new HashMap<BsonType, Class<?>>();
    replacements.put(BsonType.DATE_TIME, Timestamp.class);

// replacements.put(BsonType.DATE_TIME, Timestamp.class); in case you have complex java type

    BsonTypeClassMap bsonTypeClassMap = new BsonTypeClassMap(replacements);


    MyCodecProvider provider = new MyCodecProvider(bsonTypeClassMap);
    CodecRegistry pojoCodecRegistry = fromRegistries(MongoClient.getDefaultCodecRegistry(),
            fromProviders(PojoCodecProvider.builder().automatic(true).build()));
    CodecRegistry codecRegistry = MongoClient.getDefaultCodecRegistry();
    Codec<Document> documentCodec = codecRegistry.get(Document.class);

    codecRegistry = CodecRegistries.fromRegistries(
            CodecRegistries.fromProviders(provider),
            CodecRegistries.fromRegistries(pojoCodecRegistry),
            CodecRegistries.fromCodecs(documentCodec));

    MongoClientOptions options = MongoClientOptions.builder().codecRegistry(codecRegistry).build();
    MongoClient mongo = new MongoClient(new ServerAddress("localhost", 27018), options);
like image 22
Tunde Pizzle Avatar answered Sep 29 '22 10:09

Tunde Pizzle