1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
+++
date = "2015-03-19T14:27:51-04:00"
title = "Codec and CodecRegistry"
[menu.main]
parent = "BSON"
weight = 40
pre = "<i class='fa'></i>"
+++
## Codec and CodecRegistry
In the last section we saw how to use the [`BsonReader`]({{< apiref "org/bson/BsonReader" >}}) and
[`BsonWriter`]({{< apiref "org/bson/BsonWriter" >}}) API to read and write BSON documents. But writing code at that
low a level is tedious and error-prone, so in practice these algorithms are packaged in implementations of the
[`Codec`]({{< apiref "org/bson/codecs/Codec" >}}) interface.
### Codec
The `Codec` interface abstracts the processes of decoding a BSON value into a Java object using a `BsonReader` and encoding a Java object
into a BSON value using a `BsonWriter`. The BSON value can be as simple as a boolean or as complex as a document or array.
Let's look at a simple `Codec` implementation that encodes a Java `Integer` to a BSON Int32, and vice versa:
```java
public class IntegerCodec implements Codec<Integer> {
@Override
public void encode(final BsonWriter writer, final Integer value, final EncoderContext encoderContext) {
writer.writeInt32(value);
}
@Override
public Integer decode(final BsonReader reader, final DecoderContext decoderContext) {
return reader.readInt32();
}
@Override
public Class<Integer> getEncoderClass() {
return Integer.class;
}
}
```
The `encode` method takes a `BsonWriter` and an `Integer` and calls the `writeInt32` method on the `BsonWriter` with the value of the
`Integer`, while the `decode` method takes a `BsonReader` and calls the `readInt32` method on the `BsonReader`, returning the value as an
`Integer`.
A `Codec` implementation than encodes to and decodes from a BSON document or array is more complicated, and would typically
rely on a set of simpler `Codec` implementations for the basic BSON value types. For this, it can rely on a `CodecRegistry`.
### CodecRegistry
A [`CodecRegistry`]({{< apiref "org/bson/codecs/configuration/CodecRegistry" >}}) contains a set of `Codec` instances that are accessed
according to the Java classes that they encode from and decode to. Instances of `CodecRegistry` are generally created via static factory
methods on the [`CodecRegistries`]({{< apiref "org/bson/codecs/configuration/CodecRegistries" >}}) class. Consider the simplest of these
methods, one that takes a list of `Codec`s:
```java
CodecRegistry registry = CodecRegistries.fromCodecs(new IntegerCodec(), new LongCodec(), ...);
```
This returns an immutable `CodecRegistry` instance containing all the `Codec` instances passed to the `fromCodecs` method. They can be
accessed like this:
```java
Codec<Integer> integerCodec = codecRegistry.get(Integer.class);
Codec<Long> longCodec = codecRegistry.get(Long.class);
```
Now consider a `Codec` for the `Document` class. This `Codec` implementation, in order to decode and
encode the values for each field in the document, must be constructed with a `CodecRegistry` to look up the `Codec` instances for each type
of value. But how could one construct an instance of that `Codec`? You would have to pass an instance to the
`CodecRegistries.fromCodecs` method, but you don't have a `CodecRegistry` yet to pass to the constructor. You need some way to delay the
construction of the `Document` `Codec` until after the `CodecRegistry` has been constructed. For that we use a `CodecProvider`.
### CodecProvider
A [`CodecProvider`]({{< apiref "org/bson/codecs/configuration/CodecProvider" >}}) is a factory for `Codec` instances. Unlike
`CodecRegistry`, its `get` method takes not only a Class, but also a `CodecRegistry`, allowing a `CodecProvider` implementation to
construct `Codec` instances that require a `CodecRegistry` to look up `Codec` instances for the values contained within it. Consider a
`CodecProvider` for the `Document` class:
```java
public class DocumentCodecProvider implements CodecProvider {
@Override
public <T> Codec<T> get(final Class<T> clazz, final CodecRegistry registry) {
if (clazz == Document.class) {
// construct DocumentCodec with a CodecRegistry
return (Codec<T>) new DocumentCodec(registry);
}
// CodecProvider returns null if it's not a provider for the requresed Class
return null;
}
}
```
The `DocumentCodec`, because it is constructed with a `CodecRegistry`, can now use that registry to look up `Codec` instances for the
values contained in each Document that it encodes.
One more problem remains, however. Consider the problem of encoding values to a BSON DateTime. An application may want to
encode to a BSON DateTime instances of both the original Java `Date` class as well as the Java 8 `Instant` class. It's easy to create
implemenations of `Codec<Date>` and `Codec<Instant>`, and either one can be used for encoding. But when decoding, a Document `Codec`
also has to choose which Java type to decode a BSON DateTime to. Rather than hard-coding it in the `DocumentCodec`, the decision is
abstracted via the `BsonTypeClassMap` class.
### BsonTypeClassMap
The [`BsonTypeClassMap`]({{< apiref "org/bson/codecs/BsonTypeClassMap" >}}) class simply maps each value in the `BsonType`
enumeration to a Java class. It contains a sensible set of default mappings that can easily be changed by passing an a `Map<BsonType,
Class<?>>` instance to the constructor with any replacement mappings to apply. Consider the case where an application wants to decode
all BSON DateTime values to a Java 8 `Instant` instead of the default `Date`:
```java
Map<BsonType, Class<?>> replacements = new HashMap<BsonType, Class<?>>();
replacements.put(BsonType.DATE_TIME, Instant.class);
BsonTypeClassMap bsonTypeClassMap = new BsonTypeClassMap(replacements);
```
This will replace the default mapping of BSON DateTime to `Date` to one from BSON DateTime to `Instant`.
Putting it all together, we can added a BsonTypeClassMap to the DocumentCodecProvider shown above:
```java
public class DocumentCodecProvider implements CodecProvider {
private final BsonTypeClassMap bsonTypeClassMap;
public DocumentCodecProvider(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 DocumentCodec(registry, bsonTypeClassMap);
}
return null;
}
}
```
The `DocumentCodec`, because it is constructed with both a `BsonTypeClassMap` and a `CodecRegistry`, can first use the `BsonTypeClassMap`
to determine with type to decode each BSON value to, then use the `CodecRegistry` to look up the `Codec` for that Java type.
Finally, we create a `CodecRegistry` instance
```bash
CodecRegistry defaultCodecRegistry = ...
DocumentCodecProvider documentCodecProvider = ...
Codec<Instant> instantCodec = ...
codecRegistry = CodecRegistries.fromRegistries(CodecRegistries.fromCodecs(instantCodec),
CodecRegistries.fromProviders(documentCodecProvider),
defaultCodecRegistry);
```
using two additional static factory methods from the `CodecRegistries` class: one that takes a list of `CodecProvider`s and one which
takes a list of `CodecRegistry`s.
|