Skip to content

Commit 099a8a5

Browse files
author
Roman Janusz
committed
rewritten specialized GenCodec instances for BsonValue and its subclasses
* BsonInput and BsonOutput impls support BsonValue natively via BsonValueMarker * codecs for BsonValue and its subclasses always yield binary representation for non-BSON Input & Output impls
1 parent 4cedc81 commit 099a8a5

File tree

9 files changed

+84
-153
lines changed

9 files changed

+84
-153
lines changed
Lines changed: 34 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
package com.avsystem.commons
22
package mongo
33

4-
import java.nio.ByteBuffer
5-
import com.avsystem.commons.serialization.GenCodec.{ReadFailure, WriteFailure}
6-
import com.avsystem.commons.serialization.{GenCodec, GenKeyCodec, TransparentWrapping}
7-
import org.bson.codecs.{BsonValueCodec, DecoderContext, EncoderContext}
4+
import com.avsystem.commons.serialization.GenCodec.ReadFailure
5+
import com.avsystem.commons.serialization._
6+
import org.bson._
87
import org.bson.io.BasicOutputBuffer
98
import org.bson.types.{Decimal128, ObjectId}
10-
import org.bson.{BsonArray, BsonBinary, BsonBinaryReader, BsonBinaryWriter, BsonBoolean, BsonDateTime, BsonDecimal128, BsonDocument, BsonDouble, BsonElement, BsonInt32, BsonInt64, BsonNull, BsonObjectId, BsonString, BsonType, BsonValue}
9+
10+
import java.nio.ByteBuffer
1111

1212
trait BsonGenCodecs {
1313
implicit def objectIdCodec: GenCodec[ObjectId] = BsonGenCodecs.objectIdCodec
1414
implicit def objectIdKeyCodec: GenKeyCodec[ObjectId] = BsonGenCodecs.objectIdKeyCodec
15-
1615
implicit def decimal128Codec: GenCodec[Decimal128] = BsonGenCodecs.decimal128Codec
1716

1817
implicit def bsonArrayCodec: GenCodec[BsonArray] = BsonGenCodecs.bsonArrayCodec
@@ -33,13 +32,13 @@ trait BsonGenCodecs {
3332
object BsonGenCodecs {
3433
// needed so that ObjectId can be used as ID type in AutoIdMongoEntity
3534
// (TransparentWrapping is used in EntityIdMode)
36-
implicit val objectIdIdentityWrapping: TransparentWrapping[ObjectId, ObjectId] =
37-
TransparentWrapping.identity
35+
implicit val objectIdIdentityWrapping: TransparentWrapping[ObjectId, ObjectId] = TransparentWrapping.identity
3836

3937
implicit val objectIdCodec: GenCodec[ObjectId] = GenCodec.nullable(
4038
i => i.readCustom(ObjectIdMarker).getOrElse(new ObjectId(i.readSimple().readString())),
4139
(o, v) => if (!o.writeCustom(ObjectIdMarker, v)) o.writeSimple().writeString(v.toHexString)
4240
)
41+
4342
implicit val objectIdKeyCodec: GenKeyCodec[ObjectId] =
4443
GenKeyCodec.create(new ObjectId(_), _.toHexString)
4544

@@ -48,104 +47,43 @@ object BsonGenCodecs {
4847
(o, v) => if (!o.writeCustom(Decimal128Marker, v)) o.writeSimple().writeBigDecimal(v.bigDecimalValue())
4948
)
5049

51-
implicit val bsonArrayCodec: GenCodec[BsonArray] = GenCodec.nullableList(
52-
li => new BsonArray(li.iterator(bsonValueCodec.read).to(JList)),
53-
(lo, ba) => ba.asScala.foreach(bsonValueCodec.write(lo.writeElement(), _))
54-
)
55-
56-
implicit val bsonBinaryCodec: GenCodec[BsonBinary] =
57-
GenCodec.ByteArrayCodec.transform[BsonBinary](_.getData, new BsonBinary(_))
58-
59-
implicit val bsonBooleanCodec: GenCodec[BsonBoolean] =
60-
GenCodec.BooleanCodec.transform[BsonBoolean](_.getValue, new BsonBoolean(_))
61-
62-
implicit val bsonDateTimeCodec: GenCodec[BsonDateTime] = GenCodec.nullableSimple(
63-
i => new BsonDateTime(i.readTimestamp()),
64-
(o, v) => o.writeTimestamp(v.getValue)
65-
)
66-
67-
implicit val bsonDocumentCodec: GenCodec[BsonDocument] = GenCodec.nullableObject(
68-
oi => new BsonDocument(oi.iterator(bsonValueCodec.read).map {
69-
case (k, v) => new BsonElement(k, v)
70-
}.to(JList)),
71-
(oo, bd) => bd.asScala.foreach { case (key, value) =>
72-
bsonValueCodec.write(oo.writeField(key), value)
50+
implicit val bsonValueCodec: GenCodec[BsonValue] = GenCodec.create(
51+
i => i.readCustom(BsonValueMarker).getOrElse {
52+
val reader = new BsonBinaryReader(ByteBuffer.wrap(i.readSimple().readBinary()))
53+
BsonValueUtils.decode(reader)
54+
},
55+
(o, bv) => if (!o.writeCustom(BsonValueMarker, bv)) {
56+
val buffer = new BasicOutputBuffer()
57+
val writer = new BsonBinaryWriter(buffer)
58+
BsonValueUtils.encode(writer, bv)
59+
writer.flush()
60+
writer.close()
61+
o.writeSimple().writeBinary(buffer.toByteArray)
7362
}
7463
)
7564

76-
implicit val bsonDecimal128Codec: GenCodec[BsonDecimal128] =
77-
decimal128Codec.transform[BsonDecimal128](_.getValue, new BsonDecimal128(_))
78-
79-
implicit val bsonDoubleCodec: GenCodec[BsonDouble] =
80-
GenCodec.DoubleCodec.transform(_.getValue, new BsonDouble(_))
81-
82-
implicit val bsonInt32Codec: GenCodec[BsonInt32] =
83-
GenCodec.IntCodec.transform(_.getValue, new BsonInt32(_))
65+
private def bsonValueSubCodec[T <: BsonValue](fromBsonValue: BsonValue => T): GenCodec[T] =
66+
bsonValueCodec.transform(identity, fromBsonValue)
8467

85-
implicit val bsonInt64Codec: GenCodec[BsonInt64] =
86-
GenCodec.LongCodec.transform(_.getValue, new BsonInt64(_))
68+
implicit val bsonArrayCodec: GenCodec[BsonArray] = bsonValueSubCodec(_.asArray())
69+
implicit val bsonBinaryCodec: GenCodec[BsonBinary] = bsonValueSubCodec(_.asBinary())
70+
implicit val bsonBooleanCodec: GenCodec[BsonBoolean] = bsonValueSubCodec(_.asBoolean())
71+
implicit val bsonDateTimeCodec: GenCodec[BsonDateTime] = bsonValueSubCodec(_.asDateTime())
72+
implicit val bsonDocumentCodec: GenCodec[BsonDocument] = bsonValueSubCodec(_.asDocument())
73+
implicit val bsonDecimal128Codec: GenCodec[BsonDecimal128] = bsonValueSubCodec(_.asDecimal128())
74+
implicit val bsonDoubleCodec: GenCodec[BsonDouble] = bsonValueSubCodec(_.asDouble())
75+
implicit val bsonInt32Codec: GenCodec[BsonInt32] = bsonValueSubCodec(_.asInt32())
76+
implicit val bsonInt64Codec: GenCodec[BsonInt64] = bsonValueSubCodec(_.asInt64())
8777

8878
implicit val bsonNullCodec: GenCodec[BsonNull] =
89-
GenCodec.create(i => {
90-
if (!i.readNull()) throw new ReadFailure("Input did not contain expected null value")
91-
BsonNull.VALUE
92-
}, (o, _) => o.writeNull())
79+
bsonValueSubCodec { bv =>
80+
if (bv.isNull) BsonNull.VALUE
81+
else throw new ReadFailure("Input did not contain expected null value")
82+
}
9383

9484
implicit val bsonObjectIdCodec: GenCodec[BsonObjectId] =
9585
objectIdCodec.transform(_.getValue, new BsonObjectId(_))
9686

9787
implicit val bsonStringCodec: GenCodec[BsonString] =
9888
GenCodec.StringCodec.transform(_.getValue, new BsonString(_))
99-
100-
private val mongoBsonValueCodec: BsonValueCodec = new BsonValueCodec()
101-
private val mongoDecoderContext: DecoderContext = DecoderContext.builder().build()
102-
private val mongoEncoderContext: EncoderContext = EncoderContext.builder().build()
103-
104-
implicit val bsonValueCodec: GenCodec[BsonValue] = GenCodec.create(
105-
i => {
106-
val bvOpt = i.readMetadata(BsonTypeMetadata) map {
107-
case BsonType.ARRAY => bsonArrayCodec.read(i)
108-
case BsonType.BINARY => bsonBinaryCodec.read(i)
109-
case BsonType.BOOLEAN => bsonBooleanCodec.read(i)
110-
case BsonType.DATE_TIME => bsonDateTimeCodec.read(i)
111-
case BsonType.DECIMAL128 => bsonDecimal128Codec.read(i)
112-
case BsonType.DOCUMENT => bsonDocumentCodec.read(i)
113-
case BsonType.DOUBLE => bsonDoubleCodec.read(i)
114-
case BsonType.INT32 => bsonInt32Codec.read(i)
115-
case BsonType.INT64 => bsonInt64Codec.read(i)
116-
case BsonType.NULL => bsonNullCodec.read(i)
117-
case BsonType.OBJECT_ID => bsonObjectIdCodec.read(i)
118-
case BsonType.STRING => bsonStringCodec.read(i)
119-
case other => throw new ReadFailure(s"Unsupported Bson type: $other")
120-
}
121-
bvOpt.getOrElse {
122-
val reader = new BsonBinaryReader(ByteBuffer.wrap(i.readSimple().readBinary()))
123-
mongoBsonValueCodec.decode(reader, mongoDecoderContext)
124-
}
125-
},
126-
(o, bv) => if (o.keepsMetadata(BsonTypeMetadata)) {
127-
bv match {
128-
case array: BsonArray => bsonArrayCodec.write(o, array)
129-
case binary: BsonBinary => bsonBinaryCodec.write(o, binary)
130-
case boolean: BsonBoolean => bsonBooleanCodec.write(o, boolean)
131-
case dateTime: BsonDateTime => bsonDateTimeCodec.write(o, dateTime)
132-
case decimal128: BsonDecimal128 => bsonDecimal128Codec.write(o, decimal128)
133-
case document: BsonDocument => bsonDocumentCodec.write(o, document)
134-
case double: BsonDouble => bsonDoubleCodec.write(o, double)
135-
case int32: BsonInt32 => bsonInt32Codec.write(o, int32)
136-
case int64: BsonInt64 => bsonInt64Codec.write(o, int64)
137-
case bNull: BsonNull => bsonNullCodec.write(o, bNull)
138-
case objectId: BsonObjectId => bsonObjectIdCodec.write(o, objectId)
139-
case string: BsonString => bsonStringCodec.write(o, string)
140-
case other => throw new WriteFailure(s"Unsupported value: $other")
141-
}
142-
} else {
143-
val buffer = new BasicOutputBuffer()
144-
val writer = new BsonBinaryWriter(buffer)
145-
mongoBsonValueCodec.encode(writer, bv, mongoEncoderContext)
146-
writer.flush()
147-
writer.close()
148-
o.writeSimple().writeBinary(buffer.toByteArray)
149-
}
150-
)
15189
}

commons-mongo/jvm/src/main/scala/com/avsystem/commons/mongo/BsonInputOutput.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ package mongo
33

44
import com.avsystem.commons.serialization.GenCodec.ReadFailure
55
import com.avsystem.commons.serialization.{InputAndSimpleInput, InputMetadata, OutputAndSimpleOutput, TypeMarker}
6-
import org.bson.{BsonInvalidOperationException, BsonType}
6+
import org.bson.{BsonInvalidOperationException, BsonType, BsonValue}
77
import org.bson.types.{Decimal128, ObjectId}
88

99
import java.nio.ByteBuffer
1010

1111
object ObjectIdMarker extends TypeMarker[ObjectId]
1212
object Decimal128Marker extends TypeMarker[Decimal128]
13+
object BsonValueMarker extends TypeMarker[BsonValue]
1314

1415
object BsonTypeMetadata extends InputMetadata[BsonType]
1516

1617
trait BsonInput extends Any with InputAndSimpleInput {
1718
def readObjectId(): ObjectId
1819
def readDecimal128(): Decimal128
20+
def readBsonValue(): BsonValue
1921

2022
protected def bsonType: BsonType
2123

@@ -71,6 +73,7 @@ trait BsonInput extends Any with InputAndSimpleInput {
7173
typeMarker match {
7274
case ObjectIdMarker => readObjectId().opt
7375
case Decimal128Marker => readDecimal128().opt
76+
case BsonValueMarker => readBsonValue().opt
7477
case _ => Opt.Empty
7578
}
7679
}
@@ -89,6 +92,7 @@ object BsonInput {
8992
trait BsonOutput extends Any with OutputAndSimpleOutput {
9093
def writeObjectId(objectId: ObjectId): Unit
9194
def writeDecimal128(decimal128: Decimal128): Unit
95+
def writeBsonValue(bsonValue: BsonValue): Unit
9296

9397
override def writeBigInt(bigInt: BigInt): Unit =
9498
if (bigInt.isValidLong) writeLong(bigInt.longValue)
@@ -110,6 +114,7 @@ trait BsonOutput extends Any with OutputAndSimpleOutput {
110114
typeMarker match {
111115
case ObjectIdMarker => writeObjectId(value); true
112116
case Decimal128Marker => writeDecimal128(value); true
117+
case BsonValueMarker => writeBsonValue(value); true
113118
case _ => false
114119
}
115120
}

commons-mongo/jvm/src/main/scala/com/avsystem/commons/mongo/BsonReaderInput.scala

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package mongo
44
import com.avsystem.commons.serialization.{FieldInput, ListInput, ObjectInput}
55
import com.google.common.collect.AbstractIterator
66
import org.bson.types.{Decimal128, ObjectId}
7-
import org.bson.{BsonReader, BsonType}
7+
import org.bson.{BsonReader, BsonType, BsonValue}
88

99
class BsonReaderInput(br: BsonReader, override val legacyOptionEncoding: Boolean = false)
1010
extends BsonInput {
@@ -57,11 +57,16 @@ class BsonReaderInput(br: BsonReader, override val legacyOptionEncoding: Boolean
5757
override def readDecimal128(): Decimal128 =
5858
expect(BsonType.DECIMAL128, br.readDecimal128())
5959

60+
override def readBsonValue(): BsonValue =
61+
BsonValueUtils.decode(br)
62+
6063
override def skip(): Unit =
6164
br.skipValue()
6265

63-
override protected final def bsonType: BsonType =
64-
br.getCurrentBsonType
66+
override protected final def bsonType: BsonType = br.getCurrentBsonType match {
67+
case null => br.readBsonType() // reader may be in a state where the type hasn't been read yet
68+
case bsonType => bsonType
69+
}
6570
}
6671

6772
final class BsonReaderFieldInput(name: String, br: BsonReader, legacyOptionEncoding: Boolean)

commons-mongo/jvm/src/main/scala/com/avsystem/commons/mongo/BsonValueInput.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.avsystem.commons
22
package mongo
33

4-
import com.avsystem.commons.serialization.GenCodec.ReadFailure
54
import com.avsystem.commons.serialization._
65
import org.bson._
76
import org.bson.types.{Decimal128, ObjectId}
@@ -46,6 +45,9 @@ class BsonValueInput(bsonValue: BsonValue, override val legacyOptionEncoding: Bo
4645
def readDecimal128(): Decimal128 =
4746
expect(BsonType.DECIMAL128, bsonValue.asDecimal128().getValue)
4847

48+
def readBsonValue(): BsonValue =
49+
bsonValue
50+
4951
def readNull(): Boolean =
5052
bsonValue == BsonNull.VALUE
5153

commons-mongo/jvm/src/main/scala/com/avsystem/commons/mongo/BsonValueOutput.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ final class BsonValueOutput(
6666

6767
override def writeDecimal128(decimal128: Decimal128): Unit =
6868
setValue(new BsonDecimal128(decimal128))
69+
70+
override def writeBsonValue(bsonValue: BsonValue): Unit =
71+
setValue(bsonValue)
6972
}
7073

7174
final class BsonValueListOutput(receiver: BsonArray => Unit, legacyOptionEncoding: Boolean)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.avsystem.commons
2+
package mongo
3+
4+
import org.bson.codecs.{BsonValueCodec, DecoderContext, EncoderContext}
5+
import org.bson.{BsonReader, BsonValue, BsonWriter}
6+
7+
object BsonValueUtils {
8+
private val bsonValueCodec = new BsonValueCodec
9+
private val encoderContext = EncoderContext.builder.build
10+
private val decoderContext = DecoderContext.builder.build
11+
12+
def encode(bw: BsonWriter, bv: BsonValue): Unit =
13+
bsonValueCodec.encode(bw, bv, encoderContext)
14+
15+
def decode(br: BsonReader): BsonValue = {
16+
br.readBsonType() // without this, `br.getCurrentBsonType` may return null (why?)
17+
bsonValueCodec.decode(br, decoderContext)
18+
}
19+
}

commons-mongo/jvm/src/main/scala/com/avsystem/commons/mongo/BsonWriterOutput.scala

Lines changed: 8 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package mongo
33

44
import com.avsystem.commons.serialization.{ListOutput, ObjectOutput}
55
import org.bson.types.{Decimal128, ObjectId}
6-
import org.bson.{BsonBinary, BsonWriter}
6+
import org.bson.{BsonBinary, BsonValue, BsonWriter}
77

88
final class BsonWriterOutput(bw: BsonWriter, override val legacyOptionEncoding: Boolean = false)
99
extends BsonOutput {
@@ -48,65 +48,24 @@ final class BsonWriterOutput(bw: BsonWriter, override val legacyOptionEncoding:
4848

4949
override def writeDecimal128(decimal128: Decimal128): Unit =
5050
bw.writeDecimal128(decimal128)
51-
}
52-
53-
final class BsonWriterNamedOutput(escapedName: String, bw: BsonWriter, override val legacyOptionEncoding: Boolean)
54-
extends BsonOutput {
55-
56-
override def writeNull(): Unit =
57-
bw.writeNull(escapedName)
58-
59-
override def writeString(str: String): Unit =
60-
bw.writeString(escapedName, str)
61-
62-
override def writeBoolean(boolean: Boolean): Unit =
63-
bw.writeBoolean(escapedName, boolean)
64-
65-
override def writeInt(int: Int): Unit =
66-
bw.writeInt32(escapedName, int)
67-
68-
override def writeLong(long: Long): Unit =
69-
if (long.isValidInt) writeInt(long.toInt)
70-
else bw.writeInt64(escapedName, long)
71-
72-
override def writeTimestamp(millis: Long): Unit =
73-
bw.writeDateTime(escapedName, millis)
74-
75-
override def writeDouble(double: Double): Unit =
76-
bw.writeDouble(escapedName, double)
77-
78-
override def writeBinary(binary: Array[Byte]): Unit =
79-
bw.writeBinaryData(escapedName, new BsonBinary(binary))
8051

81-
override def writeList(): BsonWriterListOutput = {
82-
bw.writeStartArray(escapedName)
83-
new BsonWriterListOutput(bw, legacyOptionEncoding)
84-
}
85-
86-
override def writeObject(): BsonWriterObjectOutput = {
87-
bw.writeName(escapedName) // org.bson.BsonWriter.writeStartDocument(java.lang.String) fails when writing _id in 4.0 driver
88-
bw.writeStartDocument()
89-
new BsonWriterObjectOutput(bw, legacyOptionEncoding)
90-
}
91-
92-
override def writeObjectId(objectId: ObjectId): Unit =
93-
bw.writeObjectId(escapedName, objectId)
94-
95-
override def writeDecimal128(decimal128: Decimal128): Unit =
96-
bw.writeDecimal128(escapedName, decimal128)
52+
override def writeBsonValue(bsonValue: BsonValue): Unit =
53+
BsonValueUtils.encode(bw, bsonValue)
9754
}
9855

9956
final class BsonWriterListOutput(bw: BsonWriter, legacyOptionEncoding: Boolean) extends ListOutput {
100-
override def writeElement() =
57+
override def writeElement(): BsonWriterOutput =
10158
new BsonWriterOutput(bw, legacyOptionEncoding)
10259

10360
override def finish(): Unit =
10461
bw.writeEndArray()
10562
}
10663

10764
final class BsonWriterObjectOutput(bw: BsonWriter, legacyOptionEncoding: Boolean) extends ObjectOutput {
108-
override def writeField(key: String) =
109-
new BsonWriterNamedOutput(KeyEscaper.escape(key), bw, legacyOptionEncoding)
65+
override def writeField(key: String): BsonWriterOutput = {
66+
bw.writeName(KeyEscaper.escape(key))
67+
new BsonWriterOutput(bw, legacyOptionEncoding)
68+
}
11069

11170
override def finish(): Unit =
11271
bw.writeEndDocument()

commons-mongo/jvm/src/test/scala/com/avsystem/commons/mongo/BsonInputOutputTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ class BsonInputOutputTest extends AnyFunSuite with ScalaCheckPropertyChecks {
167167
fieldInput.skip()
168168
}
169169

170-
assert(input.readMetadata(BsonTypeMetadata).isEmpty)
170+
assert(input.readMetadata(BsonTypeMetadata).contains(BsonType.DOCUMENT))
171171

172172
val objectInput = input.readObject()
173173
assert(input.readMetadata(BsonTypeMetadata).contains(BsonType.DOCUMENT))

0 commit comments

Comments
 (0)