diff --git a/bson/src/main/org/bson/AbstractBsonReader.java b/bson/src/main/org/bson/AbstractBsonReader.java index 6b550f5b416..89e78da241e 100644 --- a/bson/src/main/org/bson/AbstractBsonReader.java +++ b/bson/src/main/org/bson/AbstractBsonReader.java @@ -112,6 +112,12 @@ protected boolean isClosed() { */ protected abstract BsonBinary doReadBinaryData(); + /** + * Handles the logic to peek at the binary subtype. + * + * @return the binary subtype + */ + protected abstract byte doPeekBinarySubType(); /** * Handles the logic to read booleans @@ -261,6 +267,12 @@ public BsonBinary readBinaryData() { return doReadBinaryData(); } + @Override + public byte peekBinarySubType() { + checkPreconditions("readBinaryData", BsonType.BINARY); + return doPeekBinarySubType(); + } + @Override public boolean readBoolean() { checkPreconditions("readBoolean", BsonType.BOOLEAN); @@ -725,13 +737,44 @@ private void setStateOnEnd() { throw new BSONException(format("Unexpected ContextType %s.", getContext().getContextType())); } } + protected class Mark { + private State state; + private Context parentContext; + private BsonContextType contextType; + private BsonType currentBsonType; + private String currentName; + + protected Context getParentContext() { + return parentContext; + } + + protected BsonContextType getContextType() { + return contextType; + } + + protected Mark() { + state = AbstractBsonReader.this.state; + parentContext = AbstractBsonReader.this.context.parentContext; + contextType = AbstractBsonReader.this.context.contextType; + currentBsonType = AbstractBsonReader.this.currentBsonType; + currentName = AbstractBsonReader.this.currentName; + } + + protected void reset() { + AbstractBsonReader.this.state = state; + AbstractBsonReader.this.currentBsonType = currentBsonType; + AbstractBsonReader.this.currentName = currentName; + } + } + /** * The context for the reader. Records the parent context, creating a bread crumb trail to trace back up to the root context of the * reader. Also records the {@link org.bson.BsonContextType}, indicating whether the reader is reading a document, array, or other * complex sub-structure. */ - protected static class Context { + protected abstract class Context { + private final Context parentContext; private final BsonContextType contextType; diff --git a/bson/src/main/org/bson/BsonBinaryReader.java b/bson/src/main/org/bson/BsonBinaryReader.java index 46a537c7783..0eb72c74f85 100644 --- a/bson/src/main/org/bson/BsonBinaryReader.java +++ b/bson/src/main/org/bson/BsonBinaryReader.java @@ -30,6 +30,7 @@ public class BsonBinaryReader extends AbstractBsonReader { private final BsonInput bsonInput; private final boolean closeInput; + private Mark mark; /** * Construct an instance. @@ -129,6 +130,15 @@ protected BsonBinary doReadBinaryData() { return new BsonBinary(type, bytes); } + @Override + protected byte doPeekBinarySubType() { + mark(); + bsonInput.readInt32(); + byte type = bsonInput.readByte(); + reset(); + return type; + } + @Override protected boolean doReadBoolean() { return bsonInput.readByte() == 0x1; @@ -338,9 +348,41 @@ private int readSize() { protected Context getContext() { return (Context) super.getContext(); } + @Override + public void mark() { + if (mark != null) { + throw new BSONException("A mark already exists; it needs to be reset before creating a new one"); + } + mark = new Mark(); + } + @Override + public void reset() { + if (mark == null) { + throw new BSONException("trying to reset a mark before creating it"); + } + mark.reset(); + mark = null; + } - private static class Context extends AbstractBsonReader.Context { + protected class Mark extends AbstractBsonReader.Mark { + private int startPosition; + private int size; + + protected Mark() { + super(); + startPosition = BsonBinaryReader.this.getContext().startPosition; + size = BsonBinaryReader.this.getContext().size; + BsonBinaryReader.this.bsonInput.mark(Integer.MAX_VALUE); + } + + protected void reset() { + super.reset(); + BsonBinaryReader.this.bsonInput.reset(); + BsonBinaryReader.this.setContext(new Context((Context) getParentContext(), getContextType(), startPosition, size)); + } + } + protected class Context extends AbstractBsonReader.Context { private final int startPosition; private final int size; diff --git a/bson/src/main/org/bson/BsonDocumentReader.java b/bson/src/main/org/bson/BsonDocumentReader.java index 9ad1c47dee7..09dc92cff5f 100644 --- a/bson/src/main/org/bson/BsonDocumentReader.java +++ b/bson/src/main/org/bson/BsonDocumentReader.java @@ -2,7 +2,9 @@ import org.bson.types.ObjectId; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Map; /** @@ -16,6 +18,7 @@ */ public class BsonDocumentReader extends AbstractBsonReader { private BsonValue currentValue; + private Mark mark; /** * Construct a new instance. @@ -33,6 +36,11 @@ protected BsonBinary doReadBinaryData() { return currentValue.asBinary(); } + @Override + protected byte doPeekBinarySubType() { + return currentValue.asBinary().getType(); + } + @Override protected boolean doReadBoolean() { return currentValue.asBoolean().getValue(); @@ -200,23 +208,122 @@ public BsonType readBsonType() { return getCurrentBsonType(); } + @Override + public void mark() { + if (mark != null) { + throw new BSONException("A mark already exists; it needs to be reset before creating a new one"); + } + mark = new Mark(); + } + + @Override + public void reset() { + if (mark == null) { + throw new BSONException("trying to reset a mark before creating it"); + } + mark.reset(); + mark = null; + } + @Override protected Context getContext() { return (Context) super.getContext(); } + protected class Mark extends AbstractBsonReader.Mark { + private BsonValue currentValue; + private Context context; + + protected Mark() { + super(); + currentValue = BsonDocumentReader.this.currentValue; + context = BsonDocumentReader.this.getContext(); + context.mark(); + } - protected static class Context extends AbstractBsonReader.Context { - private Iterator> documentIterator; - private Iterator arrayIterator; + protected void reset() { + super.reset(); + BsonDocumentReader.this.currentValue = currentValue; + BsonDocumentReader.this.setContext(context); + context.reset(); + } + } + + private static class BsonDocumentMarkableIterator implements Iterator { + + private Iterator baseIterator; + private List markIterator = new ArrayList(); + private int curIndex; // index of the cursor + private boolean marking; + + protected BsonDocumentMarkableIterator(final Iterator baseIterator) { + this.baseIterator = baseIterator; + curIndex = 0; + marking = false; + } + + /** + * + */ + protected void mark() { + marking = true; + } + + /** + * + */ + protected void reset() { + curIndex = 0; + marking = false; + } + + + @Override + public boolean hasNext() { + return baseIterator.hasNext() || curIndex < markIterator.size(); + } + + @Override + public T next() { + T value; + //TODO: check closed + if (curIndex < markIterator.size()) { + value = markIterator.get(curIndex); + if (marking) { + curIndex++; + } else { + markIterator.remove(0); + } + } else { + value = baseIterator.next(); + if (marking) { + markIterator.add(value); + curIndex++; + } + } + + + return value; + } + + @Override + public void remove() { + // iterator is read only + } + } + + protected class Context extends AbstractBsonReader.Context { + + private BsonDocumentMarkableIterator> documentIterator; + private BsonDocumentMarkableIterator arrayIterator; protected Context(final Context parentContext, final BsonContextType contextType, final BsonArray array) { super(parentContext, contextType); - arrayIterator = array.iterator(); + arrayIterator = new BsonDocumentMarkableIterator(array.iterator()); } protected Context(final Context parentContext, final BsonContextType contextType, final BsonDocument document) { super(parentContext, contextType); - documentIterator = document.entrySet().iterator(); + documentIterator = new BsonDocumentMarkableIterator>(document.entrySet().iterator()); } public Map.Entry getNextElement() { @@ -226,6 +333,29 @@ public Map.Entry getNextElement() { return null; } } + protected void mark() { + if (documentIterator != null) { + documentIterator.mark(); + } else { + arrayIterator.mark(); + } + + if (getParentContext() != null) { + ((Context) getParentContext()).mark(); + } + } + + protected void reset() { + if (documentIterator != null) { + documentIterator.reset(); + } else { + arrayIterator.reset(); + } + + if (getParentContext() != null) { + ((Context) getParentContext()).reset(); + } + } public BsonValue getNextValue() { if (arrayIterator.hasNext()) { diff --git a/bson/src/main/org/bson/BsonDocumentWriter.java b/bson/src/main/org/bson/BsonDocumentWriter.java index 986014c2ef8..ef0112eb14b 100644 --- a/bson/src/main/org/bson/BsonDocumentWriter.java +++ b/bson/src/main/org/bson/BsonDocumentWriter.java @@ -45,6 +45,15 @@ public BsonDocumentWriter(final BsonDocument document) { setContext(new Context()); } + /** + * Gets the document that the writer is writing to. + * + * @return the document + */ + public BsonDocument getDocument() { + return document; + } + @Override protected void doWriteStartDocument() { switch (getState()) { diff --git a/bson/src/main/org/bson/BsonReader.java b/bson/src/main/org/bson/BsonReader.java index 4101d49dfd9..f16c68f3c10 100644 --- a/bson/src/main/org/bson/BsonReader.java +++ b/bson/src/main/org/bson/BsonReader.java @@ -43,6 +43,14 @@ public interface BsonReader { */ BsonBinary readBinaryData(); + /** + * Peeks the subtype of the binary data that the reader is positioned at. This operation is not permitted if the mark is already set. + * + * @return the subtype + * @see #mark() + */ + byte peekBinarySubType(); + /** * Reads a BSON Binary data element from the reader. * @@ -344,4 +352,18 @@ public interface BsonReader { * Skips the value (reader must be positioned on a value). */ void skipValue(); + + /** + * Creates a bookmark in the BsonReader's input + * + * The previous mark must be cleared before creating a new one + */ + void mark(); + + /** + * Go back to the state at the last mark and removes the mark + * + * @throws org.bson.BSONException if no mark has been set + */ + void reset(); } diff --git a/bson/src/main/org/bson/UuidRepresentation.java b/bson/src/main/org/bson/UuidRepresentation.java new file mode 100644 index 00000000000..22c9a64bc31 --- /dev/null +++ b/bson/src/main/org/bson/UuidRepresentation.java @@ -0,0 +1,57 @@ +/* + * + * * Copyright (c) 2008-2014 MongoDB, Inc. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.bson; + +/** + * The representation to use when converting a UUID to a BSON binary value. + * This class is necessary because the different drivers used to have different + * ways of encoding UUID, with the BSON subtype: \x03 UUID old. + * + * @since 3.0 + */ +public enum UuidRepresentation { + /** + * The canonical representation of UUID + * + * BSON binary subtype 4 + */ + STANDARD, + + /** + * The legacy representation of UUID used by the C# driver + * + * BSON binary subtype 3 + */ + C_SHARP_LEGACY, + + /** + * The legacy representation of UUID used by the Java driver + * + * BSON binary subtype 3 + */ + JAVA_LEGACY, + + /** + * The legacy representation of UUID used by the Python driver, which is the same + * format as STANDARD, but has the UUID old BSON subtype (\x03) + * + * BSON binary subtype 3 + */ + PYTHON_LEGACY +} \ No newline at end of file diff --git a/bson/src/main/org/bson/io/ByteBufferBsonInput.java b/bson/src/main/org/bson/io/ByteBufferBsonInput.java index 03cdbf5efff..5b10325314c 100644 --- a/bson/src/main/org/bson/io/ByteBufferBsonInput.java +++ b/bson/src/main/org/bson/io/ByteBufferBsonInput.java @@ -53,6 +53,7 @@ public int getPosition() { return buffer.position(); } + @Override public byte readByte() { ensureOpen(); diff --git a/bson/src/main/org/bson/json/JsonReader.java b/bson/src/main/org/bson/json/JsonReader.java index 13ae03e75ac..18b549dd403 100644 --- a/bson/src/main/org/bson/json/JsonReader.java +++ b/bson/src/main/org/bson/json/JsonReader.java @@ -18,6 +18,7 @@ import org.bson.AbstractBsonReader; +import org.bson.BSONException; import org.bson.BsonBinary; import org.bson.BsonBinarySubType; import org.bson.BsonContextType; @@ -61,6 +62,7 @@ public class JsonReader extends AbstractBsonReader { private final JsonScanner scanner; private JsonToken pushedToken; private Object currentValue; + private Mark mark; /** * Constructs a new instance with the given JSON string. @@ -78,6 +80,11 @@ protected BsonBinary doReadBinaryData() { return (BsonBinary) currentValue; } + @Override + protected byte doPeekBinarySubType() { + return doReadBinaryData().getType(); + } + @Override protected boolean doReadBoolean() { return (Boolean) currentValue; @@ -964,11 +971,49 @@ private Long visitNumberLongExtendedJson() { return nameToken.getValue(Long.class); } + @Override + public void mark() { + if (mark != null) { + throw new BSONException("A mark already exists; it needs to be reset before creating a new one"); + } + mark = new Mark(); + } + + @Override + public void reset() { + if (mark == null) { + throw new BSONException("trying to reset a mark before creating it"); + } + mark.reset(); + mark = null; + } + @Override protected Context getContext() { return (Context) super.getContext(); } + protected class Mark extends AbstractBsonReader.Mark { + private JsonToken pushedToken; + private Object currentValue; + private int position; + + protected Mark() { + super(); + pushedToken = JsonReader.this.pushedToken; + currentValue = JsonReader.this.currentValue; + position = JsonReader.this.scanner.getBufferPosition(); + } + + protected void reset() { + super.reset(); + JsonReader.this.pushedToken = pushedToken; + JsonReader.this.currentValue = currentValue; + JsonReader.this.scanner.setBufferPosition(position); + JsonReader.this.setContext(new Context(getParentContext(), getContextType())); + } + } + protected class Context extends AbstractBsonReader.Context { protected Context(final AbstractBsonReader.Context parentContext, final BsonContextType contextType) { super(parentContext, contextType); diff --git a/bson/src/main/org/bson/json/JsonScanner.java b/bson/src/main/org/bson/json/JsonScanner.java index ed4fadd008a..2fa6704551d 100644 --- a/bson/src/main/org/bson/json/JsonScanner.java +++ b/bson/src/main/org/bson/json/JsonScanner.java @@ -27,6 +27,20 @@ class JsonScanner { private final JsonBuffer buffer; + /** + * @param newPosition the new position of the cursor position in the buffer + */ + public void setBufferPosition(final int newPosition) { + buffer.setPosition(newPosition); + } + + /** + * @return the current location of the cursor in the buffer + */ + public int getBufferPosition() { + return buffer.getPosition(); + } + /** * Constructs a a new {@code JSONScanner} that produces values scanned from specified {@code JSONBuffer}. * diff --git a/bson/src/main/org/bson/json/JsonWriter.java b/bson/src/main/org/bson/json/JsonWriter.java index 7a3c89affcb..3b8cee4329e 100644 --- a/bson/src/main/org/bson/json/JsonWriter.java +++ b/bson/src/main/org/bson/json/JsonWriter.java @@ -64,6 +64,15 @@ public JsonWriter(final Writer writer, final JsonWriterSettings settings) { setContext(new Context(null, BsonContextType.TOP_LEVEL, "")); } + /** + * Gets the {@code Writer}. + * + * @return the writer + */ + public Writer getWriter() { + return writer; + } + @Override protected Context getContext() { return (Context) super.getContext(); diff --git a/bson/src/test/org/bson/LimitedLookaheadMarkSpecification.groovy b/bson/src/test/org/bson/LimitedLookaheadMarkSpecification.groovy new file mode 100644 index 00000000000..7797fd8dd99 --- /dev/null +++ b/bson/src/test/org/bson/LimitedLookaheadMarkSpecification.groovy @@ -0,0 +1,284 @@ +/* + * + * * Copyright (c) 2008-2014 MongoDB, Inc. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.bson + +import org.bson.io.BasicOutputBuffer +import org.bson.io.ByteBufferBsonInput +import org.bson.json.JsonReader +import org.bson.json.JsonWriter +import spock.lang.Specification + +@SuppressWarnings('UnnecessaryObjectReferences') +class LimitedLookaheadMarkSpecification extends Specification { + + def 'should throw if mark without resetting previous mark'(BsonWriter writer) { + given: + writer.with { + writeStartDocument() + writeInt64('int64', 52L) + writeEndDocument() + } + + BsonReader reader + if (writer instanceof BsonDocumentWriter) { + reader = new BsonDocumentReader(writer.document) + } else if (writer instanceof BsonBinaryWriter) { + BasicOutputBuffer buffer = (BasicOutputBuffer) writer.getBsonOutput(); + reader = new BsonBinaryReader(new ByteBufferBsonInput(buffer.getByteBuffers().get(0)), true) + } else if (writer instanceof JsonWriter) { + reader = new JsonReader(writer.writer.toString()) + } + + reader.readStartDocument() + reader.mark() + + when: + reader.mark() + + then: + thrown(BSONException) + + where: + writer << [ + new BsonDocumentWriter(new BsonDocument()), + new BsonBinaryWriter(new BasicOutputBuffer(), false), + new JsonWriter(new StringWriter()) + ] + } + + def 'should throw if reset without mark'(BsonWriter writer) { + given: + writer.with { + writeStartDocument() + writeInt64('int64', 52L) + writeEndDocument() + } + + BsonReader reader + if (writer instanceof BsonDocumentWriter) { + reader = new BsonDocumentReader(writer.document) + } else if (writer instanceof BsonBinaryWriter) { + BasicOutputBuffer buffer = (BasicOutputBuffer) writer.getBsonOutput(); + reader = new BsonBinaryReader(new ByteBufferBsonInput(buffer.getByteBuffers().get(0)), true) + } else if (writer instanceof JsonWriter) { + reader = new JsonReader(writer.writer.toString()) + } + + reader.readStartDocument() + + when: + reader.reset() + + then: + thrown(BSONException) + + where: + writer << [ + new BsonDocumentWriter(new BsonDocument()), + new BsonBinaryWriter(new BasicOutputBuffer(), false), + new JsonWriter(new StringWriter()) + ] + } + + def 'Lookahead should work at various states'(BsonWriter writer) { + given: + writer.with { + writeStartDocument() + writeInt64('int64', 52L) + writeStartArray('array') + writeInt32(1) + writeInt64(2L) + writeStartArray() + writeInt32(3) + writeInt32(4) + writeEndArray() + writeStartDocument() + writeInt32('a', 5) + writeEndDocument() + writeNull() + writeEndArray() + writeStartDocument('document') + writeInt32('a', 6) + writeEndDocument() + writeEndDocument() + } + + + when: + BsonReader reader + if (writer instanceof BsonDocumentWriter) { + reader = new BsonDocumentReader(writer.document) + } else if (writer instanceof BsonBinaryWriter) { + BasicOutputBuffer buffer = (BasicOutputBuffer) writer.getBsonOutput(); + reader = new BsonBinaryReader(new ByteBufferBsonInput(buffer.getByteBuffers().get(0)), true) + } else if (writer instanceof JsonWriter) { + reader = new JsonReader(writer.writer.toString()) + } + + then: + reader.readStartDocument() + // mark beginning of document * 1 + reader.mark() + reader.readName() == 'int64' + reader.readInt64() == 52L + reader.readStartArray() + // reset to beginning of document * 2 + reader.reset() + // mark beginning of document * 2 + reader.mark() + reader.readName() == 'int64' + reader.readInt64() == 52L + // reset to beginning of document * 3 + reader.reset() + // mark beginning of document * 3 + reader.mark() + reader.readName() == 'int64' + reader.readInt64() == 52L + reader.readName() == 'array' + reader.readStartArray() + reader.readInt32() == 1 + reader.readInt64() == 2 + reader.readStartArray() + reader.readInt32() == 3 + reader.readInt32() == 4 + reader.readEndArray() + reader.readStartDocument() + reader.readName() == 'a' + reader.readInt32() == 5 + reader.readEndDocument() + reader.readNull() + reader.readEndArray() + reader.readName() == 'document' + reader.readStartDocument() + reader.readName() == 'a' + reader.readInt32() == 6 + reader.readEndDocument() + reader.readEndDocument() + // read entire document, reset to beginning + reader.reset() + reader.readName() == 'int64' + reader.readInt64() == 52L + reader.readName() == 'array' + // mar in outer-document * 1 + reader.mark() + reader.readStartArray() + reader.readInt32() == 1 + reader.readInt64() == 2 + reader.readStartArray() + // reset in sub-document * 1 + reader.reset() + // mark in outer-document * 2 + reader.mark() + reader.readStartArray() + reader.readInt32() == 1 + reader.readInt64() == 2 + reader.readStartArray() + reader.readInt32() == 3 + // reset in sub-document * 2 + reader.reset() + reader.readStartArray() + reader.readInt32() == 1 + reader.readInt64() == 2 + reader.readStartArray() + reader.readInt32() == 3 + reader.readInt32() == 4 + // mark in sub-document * 1 + reader.mark() + reader.readEndArray() + reader.readStartDocument() + reader.readName() == 'a' + reader.readInt32() == 5 + reader.readEndDocument() + reader.readNull() + reader.readEndArray() + // reset in outer-document * 1 + reader.reset() + // mark in sub-document * 2 + reader.mark() + reader.readEndArray() + reader.readStartDocument() + reader.readName() == 'a' + reader.readInt32() == 5 + reader.readEndDocument() + reader.readNull() + reader.readEndArray() + // reset in out-document * 2 + reader.reset() + reader.readEndArray() + reader.readStartDocument() + reader.readName() == 'a' + reader.readInt32() == 5 + reader.readEndDocument() + reader.readNull() + reader.readEndArray() + reader.readName() == 'document' + reader.readStartDocument() + reader.readName() == 'a' + reader.readInt32() == 6 + reader.readEndDocument() + reader.readEndDocument() + + where: + writer << [ + new BsonDocumentWriter(new BsonDocument()), + new BsonBinaryWriter(new BasicOutputBuffer(), false), + new JsonWriter(new StringWriter()) + ] + } + + def 'should peek binary subtype'(BsonWriter writer) { + given: + writer.with { + writeStartDocument() + writeBinaryData('binary', new BsonBinary(BsonBinarySubType.UUID_LEGACY, new byte[16])) + writeInt64('int64', 52L) + writeEndDocument() + } + + when: + BsonReader reader + if (writer instanceof BsonDocumentWriter) { + reader = new BsonDocumentReader(writer.document) + } else if (writer instanceof BsonBinaryWriter) { + BasicOutputBuffer buffer = (BasicOutputBuffer) writer.getBsonOutput(); + reader = new BsonBinaryReader(new ByteBufferBsonInput(buffer.getByteBuffers().get(0)), true) + } else if (writer instanceof JsonWriter) { + reader = new JsonReader(writer.writer.toString()) + } + + reader.readStartDocument() + reader.readName() + def subType = reader.peekBinarySubType() + def binary = reader.readBinaryData() + def longValue = reader.readInt64('int64') + reader.readEndDocument() + + then: + subType == BsonBinarySubType.UUID_LEGACY.value + binary == new BsonBinary(BsonBinarySubType.UUID_LEGACY, new byte[16]) + longValue == 52L + + where: + writer << [ + new BsonDocumentWriter(new BsonDocument()), + new BsonBinaryWriter(new BasicOutputBuffer(), false), + new JsonWriter(new StringWriter()) + ] + } +} \ No newline at end of file diff --git a/driver-core/src/main/com/mongodb/codecs/BinaryToUUIDTransformer.java b/driver-core/src/main/com/mongodb/codecs/BinaryToUUIDTransformer.java deleted file mode 100644 index b8f49927213..00000000000 --- a/driver-core/src/main/com/mongodb/codecs/BinaryToUUIDTransformer.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2008-2014 MongoDB, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.mongodb.codecs; - -import org.bson.BsonBinary; - -import java.util.UUID; - -/** - * A transformer from {@code BsonBinary} to {@code UUID}. - * - * @since 3.0 - */ -public class BinaryToUUIDTransformer implements BinaryTransformer { - @Override - public UUID transform(final BsonBinary binary) { - return new UUID(readLongFromArrayLittleEndian(binary.getData(), 0), readLongFromArrayLittleEndian(binary.getData(), 8)); - } - - private static long readLongFromArrayLittleEndian(final byte[] bytes, final int offset) { - long x = 0; - x |= (0xFFL & bytes[offset]); - x |= (0xFFL & bytes[offset + 1]) << 8; - x |= (0xFFL & bytes[offset + 2]) << 16; - x |= (0xFFL & bytes[offset + 3]) << 24; - x |= (0xFFL & bytes[offset + 4]) << 32; - x |= (0xFFL & bytes[offset + 5]) << 40; - x |= (0xFFL & bytes[offset + 6]) << 48; - x |= (0xFFL & bytes[offset + 7]) << 56; - return x; - } -} \ No newline at end of file diff --git a/driver-core/src/main/com/mongodb/codecs/BinaryToUuidTransformer.java b/driver-core/src/main/com/mongodb/codecs/BinaryToUuidTransformer.java new file mode 100644 index 00000000000..f1845f13dcb --- /dev/null +++ b/driver-core/src/main/com/mongodb/codecs/BinaryToUuidTransformer.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2008-2014 MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.codecs; + +import org.bson.BSONException; +import org.bson.BsonBinary; +import org.bson.BsonBinarySubType; +import org.bson.BsonSerializationException; +import org.bson.UuidRepresentation; + +import java.util.UUID; + +import static com.mongodb.codecs.UuidCodecHelper.reverseByteArray; + +/** + * A transformer from {@code BsonBinary} to {@code UUID}. + * + * @since 3.0 + */ +public class BinaryToUuidTransformer implements BinaryTransformer { + + private UuidRepresentation uuidRepresentation = UuidRepresentation.JAVA_LEGACY; + + public BinaryToUuidTransformer() { + } + + public BinaryToUuidTransformer(final UuidRepresentation uuidRepresentation) { + this.uuidRepresentation = uuidRepresentation; + } + + @Override + public UUID transform(final BsonBinary binary) { + byte[] binaryData = binary.getData(); + + if (binaryData.length != 16) { + throw new BsonSerializationException(String.format("Expected length to be 16, not %d.", binaryData.length)); + } + if (binary.getType() == BsonBinarySubType.UUID_LEGACY.getValue()) { + switch (uuidRepresentation) { + case C_SHARP_LEGACY: + reverseByteArray(binaryData, 0, 4); + reverseByteArray(binaryData, 4, 2); + reverseByteArray(binaryData, 6, 2); + break; + case JAVA_LEGACY: + reverseByteArray(binaryData, 0, 8); + reverseByteArray(binaryData, 8, 8); + break; + case PYTHON_LEGACY: + case STANDARD: + break; + default: + throw new BSONException("Unexpected UUID representation"); + } + } + if (binary.getType() == BsonBinarySubType.UUID_LEGACY.getValue() + || binary.getType() == BsonBinarySubType.UUID_STANDARD.getValue()) { + return new UUID(readLongFromArrayBigEndian(binaryData, 0), readLongFromArrayBigEndian(binaryData, 8)); + } else { + throw new BSONException("Unexpected BsonBinarySubType"); + } + } + + private static long readLongFromArrayBigEndian(final byte[] bytes, final int offset) { + long x = 0; + x |= (0xFFL & bytes[offset + 7]); + x |= (0xFFL & bytes[offset + 6]) << 8; + x |= (0xFFL & bytes[offset + 5]) << 16; + x |= (0xFFL & bytes[offset + 4]) << 24; + x |= (0xFFL & bytes[offset + 3]) << 32; + x |= (0xFFL & bytes[offset + 2]) << 40; + x |= (0xFFL & bytes[offset + 1]) << 48; + x |= (0xFFL & bytes[offset]) << 56; + return x; + } +} \ No newline at end of file diff --git a/driver-core/src/main/com/mongodb/codecs/DocumentCodec.java b/driver-core/src/main/com/mongodb/codecs/DocumentCodec.java index 1fc6ca010b4..ac94fb366a6 100644 --- a/driver-core/src/main/com/mongodb/codecs/DocumentCodec.java +++ b/driver-core/src/main/com/mongodb/codecs/DocumentCodec.java @@ -16,6 +16,7 @@ package com.mongodb.codecs; +import org.bson.BsonBinarySubType; import org.bson.BsonDocument; import org.bson.BsonDocumentWriter; import org.bson.BsonReader; @@ -32,6 +33,7 @@ import java.util.Arrays; import java.util.Map; +import java.util.UUID; import static com.mongodb.assertions.Assertions.notNull; @@ -175,8 +177,12 @@ private Object readValue(final BsonReader reader, final DecoderContext decoderCo if (bsonType == BsonType.NULL) { reader.readNull(); return null; - } else { - return registry.get(bsonTypeClassMap.get(bsonType)).decode(reader, decoderContext); + } else if (bsonType == BsonType.BINARY) { + byte bsonSubType = reader.peekBinarySubType(); + if (bsonSubType == BsonBinarySubType.UUID_STANDARD.getValue() || bsonSubType == BsonBinarySubType.UUID_LEGACY.getValue()) { + return registry.get(UUID.class).decode(reader, decoderContext); + } } + return registry.get(bsonTypeClassMap.get(bsonType)).decode(reader, decoderContext); } } diff --git a/driver-core/src/main/com/mongodb/codecs/DocumentCodecProvider.java b/driver-core/src/main/com/mongodb/codecs/DocumentCodecProvider.java index 8369e449986..fde948758f9 100644 --- a/driver-core/src/main/com/mongodb/codecs/DocumentCodecProvider.java +++ b/driver-core/src/main/com/mongodb/codecs/DocumentCodecProvider.java @@ -102,6 +102,7 @@ private void addCodecs() { addCodec(new SymbolCodec()); addCodec(new BsonTimestampCodec()); addCodec(new BsonUndefinedCodec()); + addCodec(new UuidCodec()); } private void addCodec(final Codec codec) { diff --git a/driver-core/src/main/com/mongodb/codecs/TransformingBinaryDecoder.java b/driver-core/src/main/com/mongodb/codecs/TransformingBinaryDecoder.java index fff1a277543..4f8ff1be32a 100644 --- a/driver-core/src/main/com/mongodb/codecs/TransformingBinaryDecoder.java +++ b/driver-core/src/main/com/mongodb/codecs/TransformingBinaryDecoder.java @@ -39,7 +39,7 @@ public class TransformingBinaryDecoder implements Decoder { subTypeTransformerMap = new HashMap>(); subTypeTransformerMap.put(BsonBinarySubType.BINARY.getValue(), new BinaryToByteArrayTransformer()); subTypeTransformerMap.put(BsonBinarySubType.OLD_BINARY.getValue(), new BinaryToByteArrayTransformer()); - subTypeTransformerMap.put(BsonBinarySubType.UUID_LEGACY.getValue(), new BinaryToUUIDTransformer()); + subTypeTransformerMap.put(BsonBinarySubType.UUID_LEGACY.getValue(), new BinaryToUuidTransformer()); } @Override diff --git a/driver-core/src/main/com/mongodb/codecs/UUIDCodec.java b/driver-core/src/main/com/mongodb/codecs/UUIDCodec.java deleted file mode 100644 index 86c06fd57b0..00000000000 --- a/driver-core/src/main/com/mongodb/codecs/UUIDCodec.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2008-2014 MongoDB, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.mongodb.codecs; - -import org.bson.BsonBinary; -import org.bson.BsonBinarySubType; -import org.bson.BsonReader; -import org.bson.BsonWriter; -import org.bson.codecs.Codec; -import org.bson.codecs.DecoderContext; -import org.bson.codecs.EncoderContext; - -import java.util.UUID; - -/** - * Encodes and decodes {@code UUID} objects. - * - * @since 3.0 - */ -public class UUIDCodec implements Codec { - @Override - public void encode(final BsonWriter writer, final UUID value, final EncoderContext encoderContext) { - byte[] bytes = new byte[16]; - - writeLongToArrayLittleEndian(bytes, 0, value.getMostSignificantBits()); - writeLongToArrayLittleEndian(bytes, 8, value.getLeastSignificantBits()); - - writer.writeBinaryData(new BsonBinary(BsonBinarySubType.UUID_LEGACY, bytes)); - } - - @Override - public UUID decode(final BsonReader reader, final DecoderContext decoderContext) { - return new BinaryToUUIDTransformer().transform(reader.readBinaryData()); - } - - @Override - public Class getEncoderClass() { - return UUID.class; - } - - private static void writeLongToArrayLittleEndian(final byte[] bytes, final int offset, final long x) { - bytes[offset] = (byte) (0xFFL & (x)); - bytes[offset + 1] = (byte) (0xFFL & (x >> 8)); - bytes[offset + 2] = (byte) (0xFFL & (x >> 16)); - bytes[offset + 3] = (byte) (0xFFL & (x >> 24)); - bytes[offset + 4] = (byte) (0xFFL & (x >> 32)); - bytes[offset + 5] = (byte) (0xFFL & (x >> 40)); - bytes[offset + 6] = (byte) (0xFFL & (x >> 48)); - bytes[offset + 7] = (byte) (0xFFL & (x >> 56)); - } -} diff --git a/driver-core/src/main/com/mongodb/codecs/UuidCodec.java b/driver-core/src/main/com/mongodb/codecs/UuidCodec.java new file mode 100644 index 00000000000..e8e43162293 --- /dev/null +++ b/driver-core/src/main/com/mongodb/codecs/UuidCodec.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2008-2014 MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.codecs; + +import org.bson.BSONException; +import org.bson.BsonBinary; +import org.bson.BsonBinarySubType; +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.UuidRepresentation; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; + +import java.util.UUID; + +import static com.mongodb.codecs.UuidCodecHelper.reverseByteArray; + +/** + * Encodes and decodes {@code UUID} objects. + * + * @since 3.0 + */ +public class UuidCodec implements Codec { + + private final UuidRepresentation encoderUuidRepresentation; + private final UuidRepresentation decoderUuidRepresentation; + + /** + * The default UUIDRepresentation is JAVA_LEGACY to be compatible with existing documents + * + * @param uuidRepresentation the representation of UUID + * + * @since 3.0 + * @see org.bson.UuidRepresentation + */ + public UuidCodec(final UuidRepresentation uuidRepresentation) { + this.encoderUuidRepresentation = uuidRepresentation; + this.decoderUuidRepresentation = uuidRepresentation; + } + + /** + * The constructor for UUIDCodec, default is JAVA_LEGACY + */ + public UuidCodec() { + this.encoderUuidRepresentation = UuidRepresentation.JAVA_LEGACY; + this.decoderUuidRepresentation = UuidRepresentation.JAVA_LEGACY; + } + + @Override + public void encode(final BsonWriter writer, final UUID value, final EncoderContext encoderContext) { + byte[] binaryData = new byte[16]; + writeLongToArrayBigEndian(binaryData, 0, value.getMostSignificantBits()); + writeLongToArrayBigEndian(binaryData, 8, value.getLeastSignificantBits()); + switch (encoderUuidRepresentation) { + case C_SHARP_LEGACY: + reverseByteArray(binaryData, 0, 4); + reverseByteArray(binaryData, 4, 2); + reverseByteArray(binaryData, 6, 2); + break; + case JAVA_LEGACY: + reverseByteArray(binaryData, 0, 8); + reverseByteArray(binaryData, 8, 8); + break; + case PYTHON_LEGACY: + case STANDARD: + break; + default: + throw new BSONException("Unexpected UUID representation"); + } + // changed the default subtype to STANDARD since 3.0 + if (encoderUuidRepresentation == UuidRepresentation.STANDARD) { + writer.writeBinaryData(new BsonBinary(BsonBinarySubType.UUID_STANDARD, binaryData)); + } else { + writer.writeBinaryData(new BsonBinary(BsonBinarySubType.UUID_LEGACY, binaryData)); + } + } + + @Override + public UUID decode(final BsonReader reader, final DecoderContext decoderContext) { + BsonBinary binaryData = reader.readBinaryData(); + BinaryToUuidTransformer transformer = new BinaryToUuidTransformer(decoderUuidRepresentation); + return transformer.transform(binaryData); + } + + @Override + public Class getEncoderClass() { + return UUID.class; + } + private static void writeLongToArrayBigEndian(final byte[] bytes, final int offset, final long x) { + bytes[offset + 7] = (byte) (0xFFL & (x)); + bytes[offset + 6] = (byte) (0xFFL & (x >> 8)); + bytes[offset + 5] = (byte) (0xFFL & (x >> 16)); + bytes[offset + 4] = (byte) (0xFFL & (x >> 24)); + bytes[offset + 3] = (byte) (0xFFL & (x >> 32)); + bytes[offset + 2] = (byte) (0xFFL & (x >> 40)); + bytes[offset + 1] = (byte) (0xFFL & (x >> 48)); + bytes[offset] = (byte) (0xFFL & (x >> 56)); + } +} diff --git a/driver-core/src/main/com/mongodb/codecs/UuidCodecHelper.java b/driver-core/src/main/com/mongodb/codecs/UuidCodecHelper.java new file mode 100644 index 00000000000..34b9bbd24b6 --- /dev/null +++ b/driver-core/src/main/com/mongodb/codecs/UuidCodecHelper.java @@ -0,0 +1,34 @@ +/* + * + * * Copyright (c) 2008-2014 MongoDB, Inc. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.mongodb.codecs; + +final class UuidCodecHelper { + // reverse elements in the subarray data[start:start+length] + public static void reverseByteArray(final byte[] data, final int start, final int length) { + for (int left = start, right = start + length - 1; left < right; left++, right--) { + // swap the values at the left and right indices + byte temp = data[left]; + data[left] = data[right]; + data[right] = temp; + } + } + + private UuidCodecHelper() { + } +} \ No newline at end of file diff --git a/driver-core/src/main/com/mongodb/codecs/UuidCodecProvider.java b/driver-core/src/main/com/mongodb/codecs/UuidCodecProvider.java new file mode 100644 index 00000000000..f8839b0c87e --- /dev/null +++ b/driver-core/src/main/com/mongodb/codecs/UuidCodecProvider.java @@ -0,0 +1,58 @@ + /* + * + * * Copyright (c) 2008-2014 MongoDB, Inc. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + + package com.mongodb.codecs; + + import org.bson.UuidRepresentation; + import org.bson.codecs.Codec; + import org.bson.codecs.configuration.CodecProvider; + import org.bson.codecs.configuration.CodecRegistry; + + import java.util.UUID; + + /** + * A {@code CodecProvider} for UUID Codecs with custom UUID representations + * + * @since 3.0 + */ + public class UuidCodecProvider implements CodecProvider { + + private UuidRepresentation uuidRepresentation; + + /** + * Set the UUIDRepresentation to be used in the codec + * default is JAVA_LEGACY to be compatible with existing documents + * + * @param uuidRepresentation the representation of UUID + * + * @since 3.0 + * @see org.bson.UuidRepresentation + */ + public UuidCodecProvider(final UuidRepresentation uuidRepresentation) { + this.uuidRepresentation = uuidRepresentation; + } + + @Override + @SuppressWarnings("unchecked") + public Codec get(final Class clazz, final CodecRegistry registry) { + if (clazz == UUID.class) { + return (Codec) (new UuidCodec(uuidRepresentation)); + } + return null; + } + } \ No newline at end of file diff --git a/driver-core/src/test/unit/com/mongodb/codecs/BinaryToUUIDTransformerSpecification.groovy b/driver-core/src/test/unit/com/mongodb/codecs/BinaryToUuidTransformerSpecification.groovy similarity index 84% rename from driver-core/src/test/unit/com/mongodb/codecs/BinaryToUUIDTransformerSpecification.groovy rename to driver-core/src/test/unit/com/mongodb/codecs/BinaryToUuidTransformerSpecification.groovy index ae5e15bc564..f07ad8f6b82 100644 --- a/driver-core/src/test/unit/com/mongodb/codecs/BinaryToUUIDTransformerSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/codecs/BinaryToUuidTransformerSpecification.groovy @@ -25,12 +25,12 @@ import spock.lang.Subject import static java.nio.ByteBuffer.wrap -class BinaryToUUIDTransformerSpecification extends Specification { +class BinaryToUuidTransformerSpecification extends Specification { @Subject - private final BinaryToUUIDTransformer binaryToUUIDTransformer = new BinaryToUUIDTransformer(); + private final BinaryToUuidTransformer binaryToUUIDTransformer = new BinaryToUuidTransformer(); - def 'should read little endian encoded longs'() { + def 'should read big endian encoded longs'() { given: byte[] binaryTypeWithUUIDAsBytes = [ 0, 0, 0, 0, // document @@ -38,8 +38,8 @@ class BinaryToUUIDTransformerSpecification extends Specification { 95, 105, 100, 0, // "_id" 16, 0, 0, 0, // int "16" (length) 4, // type (B_UUID_STANDARD) - 2, 0, 0, 0, 0, 0, 0, 0, // - 1, 0, 0, 0, 0, 0, 0, 0, // 8 bytes for long, 2 longs for UUID + 0, 0, 0, 0, 0, 0, 0, 2, // + 0, 0, 0, 0, 0, 0, 0, 1, // 8 bytes for long, 2 longs for UUID 0]; // EOM BsonBinaryReader reader = new BsonBinaryReader(new ByteBufferBsonInput(new ByteBufNIO(wrap(binaryTypeWithUUIDAsBytes))), true); diff --git a/driver-core/src/test/unit/com/mongodb/codecs/DocumentCodecSpecification.groovy b/driver-core/src/test/unit/com/mongodb/codecs/DocumentCodecSpecification.groovy index e4ce833e796..8cf49a1caad 100644 --- a/driver-core/src/test/unit/com/mongodb/codecs/DocumentCodecSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/codecs/DocumentCodecSpecification.groovy @@ -20,15 +20,20 @@ import org.bson.BsonBinaryReader import org.bson.BsonBinaryWriter import org.bson.BsonDbPointer import org.bson.BsonDocument +import org.bson.BsonDocumentReader import org.bson.BsonDocumentWriter +import org.bson.BsonReader import org.bson.BsonRegularExpression import org.bson.BsonTimestamp import org.bson.BsonUndefined +import org.bson.BsonWriter import org.bson.ByteBufNIO import org.bson.codecs.DecoderContext import org.bson.codecs.EncoderContext import org.bson.io.BasicOutputBuffer import org.bson.io.ByteBufferBsonInput +import org.bson.json.JsonReader +import org.bson.json.JsonWriter import org.bson.types.Binary import org.bson.types.Code import org.bson.types.MaxKey @@ -37,6 +42,7 @@ import org.bson.types.ObjectId import org.bson.types.Symbol import org.mongodb.CodeWithScope import org.mongodb.Document +import spock.lang.Shared import spock.lang.Specification import java.nio.ByteBuffer @@ -44,7 +50,10 @@ import java.nio.ByteBuffer import static java.util.Arrays.asList class DocumentCodecSpecification extends Specification { - def 'should encode and decode all default types'() { + @Shared BsonDocument bsonDoc = new BsonDocument() + @Shared StringWriter stringWriter = new StringWriter() + + def 'should encode and decode all default types with all readers and writers' (BsonWriter writer) { given: def originalDocument = new Document() originalDocument.with { @@ -68,15 +77,22 @@ class DocumentCodecSpecification extends Specification { put('undefined', new BsonUndefined()) put('binary', new Binary((byte) 80, [5, 4, 3, 2, 1] as byte[])) put('array', asList(1, 1L, true, [1, 2, 3], new Document('a', 1), null)) + put('uuid', new UUID(1L, 2L)) put('document', new Document('a', 2)) } when: - def buffer = new BasicOutputBuffer() - BsonBinaryWriter writer = new BsonBinaryWriter(buffer, false) new DocumentCodec().encode(writer, originalDocument, EncoderContext.builder().build()) - BsonBinaryReader reader = new BsonBinaryReader(new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap(buffer.toByteArray()))), - true) + BsonReader reader + if (writer instanceof BsonDocumentWriter) { + reader = new BsonDocumentReader(bsonDoc) + } else if (writer instanceof BsonBinaryWriter) { + BasicOutputBuffer buffer = (BasicOutputBuffer)writer.getBsonOutput(); + reader = new BsonBinaryReader(new ByteBufferBsonInput(new ByteBufNIO( + ByteBuffer.wrap(buffer.toByteArray()))), true) + } else { + reader = new JsonReader(stringWriter.toString()) + } def decodedDoc = new DocumentCodec().decode(reader, DecoderContext.builder().build()) then: @@ -99,8 +115,15 @@ class DocumentCodecSpecification extends Specification { decodedDoc.get('timestamp') == originalDocument.get('timestamp') decodedDoc.get('undefined') == originalDocument.get('undefined') decodedDoc.get('binary') == originalDocument.get('binary') + decodedDoc.get('uuid') == originalDocument.get('uuid') decodedDoc.get('array') == originalDocument.get('array') decodedDoc.get('document') == originalDocument.get('document') + where: + writer << [ + new BsonDocumentWriter(bsonDoc), + new BsonBinaryWriter(new BasicOutputBuffer(), false), +// new JsonWriter(stringWriter) + ] } def 'should respect encodeIdFirst property in encoder context'() { diff --git a/driver-core/src/test/unit/com/mongodb/codecs/TransformingBinaryDecoderTest.java b/driver-core/src/test/unit/com/mongodb/codecs/TransformingBinaryDecoderTest.java index e0b2428f62b..d8c749a1a24 100644 --- a/driver-core/src/test/unit/com/mongodb/codecs/TransformingBinaryDecoderTest.java +++ b/driver-core/src/test/unit/com/mongodb/codecs/TransformingBinaryDecoderTest.java @@ -42,7 +42,7 @@ public void testDecode() { writer.writeBinaryData("subtype2", new BsonBinary(BsonBinarySubType.OLD_BINARY, new byte[]{2})); writer.writeName("subtype3"); - new UUIDCodec().encode(writer, UUID.randomUUID(), EncoderContext.builder().build()); + new UuidCodec().encode(writer, UUID.randomUUID(), EncoderContext.builder().build()); writer.writeBinaryData("subtype4", new BsonBinary(BsonBinarySubType.UUID_STANDARD, new byte[]{4})); writer.writeBinaryData("subtype5", new BsonBinary(BsonBinarySubType.MD5, new byte[]{5})); diff --git a/driver-core/src/test/unit/com/mongodb/codecs/UUIDCodecTest.java b/driver-core/src/test/unit/com/mongodb/codecs/UUIDCodecTest.java deleted file mode 100644 index 27c5b13dde0..00000000000 --- a/driver-core/src/test/unit/com/mongodb/codecs/UUIDCodecTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2008-2014 MongoDB, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.mongodb.codecs; - -import org.bson.BsonBinaryWriter; -import org.bson.codecs.EncoderContext; -import org.bson.io.BasicOutputBuffer; -import org.junit.Before; -import org.junit.Test; - -import java.io.IOException; -import java.util.UUID; - -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; - -public class UUIDCodecTest { - - private UUIDCodec uuidCodec; - private BasicOutputBuffer outputBuffer; - - @Before - public void setUp() throws Exception { - uuidCodec = new UUIDCodec(); - outputBuffer = new BasicOutputBuffer(); - } - - @Test - public void shouldEncodeLongAsLittleEndian() throws IOException { - // Given - UUID uuid = new UUID(2L, 1L); - BsonBinaryWriter bsonWriter = new BsonBinaryWriter(outputBuffer, false); - try { - bsonWriter.writeStartDocument(); - bsonWriter.writeName("_id"); - - // When - uuidCodec.encode(bsonWriter, uuid, EncoderContext.builder().build()); - } finally { - bsonWriter.close(); - } - - // Then - byte[] expectedList = {0, 0, 0, 0, //Start of document - 5, // type (BINARY) - 95, 105, 100, 0, // "_id" - 16, 0, 0, 0, // int "16" (length) - 3, // type (B_UUID_LEGACY) - 2, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0}; //8 bytes for long, 2 longs for UUID, Little Endian - - assertThat(outputBuffer.toByteArray(), is(expectedList)); - } - -} diff --git a/driver-core/src/test/unit/com/mongodb/codecs/UuidCodecSpecification.groovy b/driver-core/src/test/unit/com/mongodb/codecs/UuidCodecSpecification.groovy new file mode 100644 index 00000000000..76eccfc2cba --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/codecs/UuidCodecSpecification.groovy @@ -0,0 +1,158 @@ +/* + * + * * Copyright (c) 2008-2014 MongoDB, Inc. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.mongodb.codecs + +import org.bson.BsonBinaryReader +import org.bson.BsonBinaryWriter +import org.bson.ByteBufNIO +import org.bson.UuidRepresentation +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import org.bson.io.BasicOutputBuffer +import org.bson.io.ByteBufferBsonInput +import spock.lang.Shared +import spock.lang.Specification + +import java.nio.ByteBuffer + +/** + * + */ +class UuidCodecSpecification extends Specification { + + @Shared private UuidCodec uuidCodec; + @Shared private BasicOutputBuffer outputBuffer; + + def setup() { + uuidCodec = new UuidCodec(); + outputBuffer = new BasicOutputBuffer(); + } + + def 'should decode different types of UUID'(UuidCodec codec, byte[] list) throws IOException { + + given: + + ByteBufferBsonInput inputBuffer = new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap(list))) + BsonBinaryReader bsonReader = new BsonBinaryReader(inputBuffer, false) + UUID expectedUuid = UUID.fromString('08070605-0403-0201-100f-0e0d0c0b0a09') + + bsonReader.readStartDocument() + bsonReader.readName() + + when: + UUID actualUuid = codec.decode(bsonReader, DecoderContext.builder().build()) + + then: + expectedUuid == actualUuid + + cleanup: + bsonReader.close() + + where: + + codec << [ + new UuidCodec(), + new UuidCodec(UuidRepresentation.STANDARD), + new UuidCodec(UuidRepresentation.PYTHON_LEGACY), + new UuidCodec(UuidRepresentation.C_SHARP_LEGACY), + ] + + list << [ + [0, 0, 0, 0, //Start of document + 5, // type (BINARY) + 95, 105, 100, 0, // "_id" + 16, 0, 0, 0, // int "16" (length) + 3, // type (B_UUID_LEGACY) JAVA_LEGACY + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16], //8 bytes for long, 2 longs for UUID, Little Endian + + [0, 0, 0, 0, //Start of document + 5, // type (BINARY) + 95, 105, 100, 0, // "_id" + 16, 0, 0, 0, // int "16" (length) + 4, // type (UUID) + 8, 7, 6, 5, 4, 3, 2, 1, + 16, 15, 14, 13, 12, 11, 10, 9], //8 bytes for long, 2 longs for UUID, Big Endian + + [0, 0, 0, 0, //Start of document + 5, // type (BINARY) + 95, 105, 100, 0, // "_id" + 16, 0, 0, 0, // int "16" (length) + 3, // type (B_UUID_LEGACY) PYTHON_LEGACY + 8, 7, 6, 5, 4, 3, 2, 1, + 16, 15, 14, 13, 12, 11, 10, 9], //8 bytes for long, 2 longs for UUID, Big Endian + + [0, 0, 0, 0, //Start of document + 5, // type (BINARY) + 95, 105, 100, 0, // "_id" + 16, 0, 0, 0, // int "16" (length) + 3, // type (B_UUID_LEGACY) CSHARP_LEGACY + 5, 6, 7, 8, 3, 4, 1, 2, + 16, 15, 14, 13, 12, 11, 10, 9], //8 bytes for long, 2 longs for UUID, Big Endian + ] + + } + + def 'should encode different types of UUIDs'(Byte bsonSubType, + UuidCodec codec, + UUID uuid) throws IOException { + given: + + byte[] encodedDoc = [0, 0, 0, 0, //Start of document + 5, // type (BINARY) + 95, 105, 100, 0, // "_id" + 16, 0, 0, 0, // int "16" (length) + 0, // bsonSubType + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16] //8 bytes for long, 2 longs for UUID + + encodedDoc[13] = bsonSubType + + BsonBinaryWriter bsonWriter = new BsonBinaryWriter(outputBuffer, false) + bsonWriter.writeStartDocument() + bsonWriter.writeName('_id') + + when: + codec.encode(bsonWriter, uuid, EncoderContext.builder().build()) + + then: + outputBuffer.toByteArray() == encodedDoc + + cleanup: + bsonWriter.close() + + where: + + bsonSubType << [3, 4, 3, 3] + + codec << [ + new UuidCodec(), + new UuidCodec(UuidRepresentation.STANDARD), + new UuidCodec(UuidRepresentation.PYTHON_LEGACY), + new UuidCodec(UuidRepresentation.C_SHARP_LEGACY), + ] + + uuid << [ + UUID.fromString('08070605-0403-0201-100f-0e0d0c0b0a09'), // Java legacy UUID + UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10'), // simulated standard UUID + UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10'), // simulated Python UUID + UUID.fromString('04030201-0605-0807-090a-0b0c0d0e0f10') // simulated C# UUID + ] + } +} \ No newline at end of file diff --git a/driver/src/main/com/mongodb/DBObjectCodec.java b/driver/src/main/com/mongodb/DBObjectCodec.java index 8113be64221..06e2b7b74e4 100644 --- a/driver/src/main/com/mongodb/DBObjectCodec.java +++ b/driver/src/main/com/mongodb/DBObjectCodec.java @@ -17,7 +17,7 @@ package com.mongodb; import com.mongodb.codecs.BinaryToByteArrayTransformer; -import com.mongodb.codecs.BinaryToUUIDTransformer; +import com.mongodb.codecs.BinaryToUuidTransformer; import com.mongodb.codecs.CollectibleCodec; import com.mongodb.codecs.IdGenerator; import com.mongodb.codecs.ObjectIdGenerator; @@ -267,8 +267,9 @@ private Object readBinary(final BsonReader reader) { return new BinaryToByteArrayTransformer().transform(binary); } else if (binary.getType() == BsonBinarySubType.OLD_BINARY.getValue()) { return new BinaryToByteArrayTransformer().transform(binary); - } else if (binary.getType() == BsonBinarySubType.UUID_LEGACY.getValue()) { - return new BinaryToUUIDTransformer().transform(binary); + } else if (binary.getType() == BsonBinarySubType.UUID_LEGACY.getValue() + || binary.getType() == BsonBinarySubType.UUID_STANDARD.getValue()) { + return new BinaryToUuidTransformer().transform(binary); } else { return new Binary(binary.getType(), binary.getData()); } diff --git a/driver/src/main/com/mongodb/DBObjectCodecProvider.java b/driver/src/main/com/mongodb/DBObjectCodecProvider.java index 296e6730eae..e2d44792d47 100644 --- a/driver/src/main/com/mongodb/DBObjectCodecProvider.java +++ b/driver/src/main/com/mongodb/DBObjectCodecProvider.java @@ -27,7 +27,7 @@ import com.mongodb.codecs.PatternCodec; import com.mongodb.codecs.ShortCodec; import com.mongodb.codecs.StringCodec; -import com.mongodb.codecs.UUIDCodec; +import com.mongodb.codecs.UuidCodec; import org.bson.BsonDbPointer; import org.bson.BsonType; import org.bson.BsonUndefined; @@ -120,7 +120,7 @@ private void addCodecs() { addCodec(new ShortCodec()); addCodec(new ByteArrayCodec()); addCodec(new FloatCodec()); - addCodec(new UUIDCodec()); + addCodec(new UuidCodec()); } private void addCodec(final Codec codec) { diff --git a/driver/src/main/com/mongodb/util/JSONSerializers.java b/driver/src/main/com/mongodb/util/JSONSerializers.java index 8e8dae6cda8..f409cf60e4a 100644 --- a/driver/src/main/com/mongodb/util/JSONSerializers.java +++ b/driver/src/main/com/mongodb/util/JSONSerializers.java @@ -104,7 +104,7 @@ static ClassMapBasedObjectSerializer addCommonSerializers() { serializer.addObjectSerializer(ObjectId.class, new ObjectIdSerializer(serializer)); serializer.addObjectSerializer(Pattern.class, new PatternSerializer(serializer)); serializer.addObjectSerializer(String.class, new StringSerializer()); - serializer.addObjectSerializer(UUID.class, new UUIDSerializer(serializer)); + serializer.addObjectSerializer(UUID.class, new UuidSerializer(serializer)); serializer.addObjectSerializer(BsonUndefined.class, new UndefinedSerializer(serializer)); return serializer; } @@ -389,9 +389,9 @@ public void serialize(final Object obj, final StringBuilder buf) { } } - private static class UUIDSerializer extends CompoundObjectSerializer { + private static class UuidSerializer extends CompoundObjectSerializer { - UUIDSerializer(final ObjectSerializer serializer) { + UuidSerializer(final ObjectSerializer serializer) { super(serializer); } diff --git a/driver/src/test/unit/com/mongodb/DBObjectCodecSpecification.groovy b/driver/src/test/unit/com/mongodb/DBObjectCodecSpecification.groovy new file mode 100644 index 00000000000..d4edf45f545 --- /dev/null +++ b/driver/src/test/unit/com/mongodb/DBObjectCodecSpecification.groovy @@ -0,0 +1,53 @@ +/* + * + * * Copyright (c) 2008-2014 MongoDB, Inc. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.mongodb + +import org.bson.BsonDocument +import org.bson.BsonDocumentReader +import org.bson.BsonDocumentWriter +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import org.bson.codecs.configuration.CodecProvider +import org.bson.codecs.configuration.RootCodecRegistry +import spock.lang.Shared +import spock.lang.Specification + +/** + * + */ +class DBObjectCodecSpecification extends Specification { + + @Shared BsonDocument bsonDoc = new BsonDocument() + + def 'should encode and decode UUIDs'() { + given: + UUID uuid = UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10') + DBObjectCodec dbObjectCodec = new DBObjectCodec(null, new BasicDBObjectFactory(), + new RootCodecRegistry(Arrays.asList(new DBObjectCodecProvider())), + DBObjectCodecProvider.createDefaultBsonTypeClassMap()); + BasicDBObject uuidObj = new BasicDBObject('uuid', uuid) + BsonDocumentWriter writer = new BsonDocumentWriter(bsonDoc) + dbObjectCodec.encode(writer, uuidObj, EncoderContext.builder().build()) + BsonDocumentReader reader = new BsonDocumentReader(bsonDoc) + DBObject decodedUuid = dbObjectCodec.decode(reader, DecoderContext.builder().build()) + + expect: + decodedUuid.get('uuid') == uuid + } +} \ No newline at end of file