javamongodbcodec

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:

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?


Solution

  • 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.