/* * 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 com.mongodb.BasicDBObject; import org.bson.io.BasicOutputBuffer; import org.bson.io.OutputBuffer; import org.bson.types.CodeWScope; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Date; import java.util.List; import static com.mongodb.util.Util.hexMD5; import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; public class BSONTest { @Before public void setUp() { BSON.clearAllHooks(); } @After public void tearDown() { BSON.clearAllHooks(); } @Test public void testSimpleDocuments() throws IOException { checkEncodingAndDecoding(new BasicBSONObject("x", true), 9, "6fe24623e4efc5cf07f027f9c66b5456"); checkEncodingAndDecoding(new BasicBSONObject("x", null), 8, "12d43430ff6729af501faf0638e68888"); checkEncodingAndDecoding(new BasicBSONObject("x", 5.2), 16, "aaeeac4a58e9c30eec6b0b0319d0dff2"); checkEncodingAndDecoding(new BasicBSONObject("x", "eliot"), 18, "331a3b8b7cbbe0706c80acdb45d4ebbe"); checkEncodingAndDecoding(new BasicBSONObject("x", 5.2).append("y", "truth") .append("z", 1.1), 40, "7c77b3a6e63e2f988ede92624409da58"); checkEncodingAndDecoding(new BasicBSONObject("a", new BasicBSONObject("b", 1.1)), 24, "31887a4b9d55cd9f17752d6a8a45d51f"); checkEncodingAndDecoding(new BasicBSONObject("x", 5.2).append("y", new BasicBSONObject("a", "eliot").append("b", true)) .append("z", null), 44, "b3de8a0739ab329e7aea138d87235205"); checkEncodingAndDecoding(new BasicBSONObject("x", 5.2).append("y", new Object[]{"a", "eliot", "b", true}) .append("z", null), 62, "cb7bad5697714ba0cbf51d113b6a0ee8"); checkEncodingAndDecoding(new BasicBSONObject("x", 4), 12, "d1ed8dbf79b78fa215e2ded74548d89d"); } @Test public void testArray() throws IOException { checkEncodingAndDecoding(new BasicBSONObject("x", new int[]{1, 2, 3, 4}), 41, "e63397fe37de1349c50e1e4377a45e2d"); } @Test public void testCode() throws IOException { BSONObject scope = new BasicBSONObject("x", 1); CodeWScope c = new CodeWScope("function() { x += 1; }", scope); BSONObject document = new BasicBSONObject("map", c); checkEncodingAndDecoding(document, 53, "52918d2367533165bfc617df50335cbb"); } @Test public void testBinary() throws IOException { byte[] data = new byte[10000]; for (int i = 0; i < 10000; i++) { data[i] = 1; } BSONObject document = new BasicBSONObject("bin", data); checkEncodingAndDecoding(document, 10015, "1d439ba5b959ecfe297a7862bf95bc10"); } private void checkEncodingAndDecoding(final BSONObject toEncodeAndDecode, final int expectedEncodedSize, final String expectedHash) throws IOException { // check encoding BSONEncoder bsonEncoder = new BasicBSONEncoder(); OutputBuffer buf = new BasicOutputBuffer(); bsonEncoder.set(buf); bsonEncoder.putObject(toEncodeAndDecode); assertEquals(expectedEncodedSize, buf.size()); assertEquals(expectedHash, hexMD5(buf.toByteArray())); bsonEncoder.done(); // check decoding BSONDecoder bsonDecoder = new BasicBSONDecoder(); BSONCallback callback = new BasicBSONCallback(); int numberOfBytesDecoded = bsonDecoder.decode(new ByteArrayInputStream(buf.toByteArray()), callback); assertEquals(expectedEncodedSize, numberOfBytesDecoded); assertEquals(callback.get(), toEncodeAndDecode); // I believe this is an obscure way of checking the decoded object is the the one we expect OutputBuffer buf2 = new BasicOutputBuffer(); bsonEncoder.set(buf2); bsonEncoder.putObject((BSONObject) callback.get()); assertEquals(expectedEncodedSize, buf2.size()); assertEquals(expectedHash, hexMD5(buf2.toByteArray())); } @Test public void testOBBig1() { BasicOutputBuffer a = new BasicOutputBuffer(); StringBuilder b = new StringBuilder(); for (final String x : prepareData()) { a.write(x.getBytes()); b.append(x); } assertEquals(new String(a.toByteArray(), Charset.forName("UTF-8")), b.toString()); } private List<String> prepareData() { List<String> data = new ArrayList<String>(); for (int x = 8; x < 2048; x *= 2) { StringBuilder buf = new StringBuilder(); while (buf.length() < x) { buf.append(x); } data.add(buf.toString()); } return data; } @Test(expected = IllegalArgumentException.class) public void shouldClearCustomEncoders() throws IOException { // given BSON.addEncodingHook(TestDate.class, new TestDateTransformer()); BSONEncoder encoder = new BasicBSONEncoder(); encoder.set(new BasicOutputBuffer()); // when BSON.clearEncodingHooks(); encoder.putObject(new BasicBSONObject("date", new TestDate(2009, 1, 23, 10, 53, 42))); } @Test public void shouldTransformTestDateToUtilDateWithTestDateTransformer() throws IOException { // given Transformer transformer = new TestDateTransformer(); // when Object transformedDate = transformer.transform(new TestDate(2009, 1, 23, 10, 53, 42)); // then assertThat(transformedDate, is(instanceOf(java.util.Date.class))); } @Test public void shouldUseCustomEncodersWhenDecodingObjectOfRegisteredClass() throws IOException { // given StubTransformer stubTransformer = new StubTransformer(); BSON.addEncodingHook(TestDate.class, stubTransformer); BSONEncoder encoder = new BasicBSONEncoder(); encoder.set(new BasicOutputBuffer()); BSONObject document = new BasicBSONObject("date", new TestDate(2009, 1, 23, 10, 53, 42)); // when encoder.putObject(document); encoder.done(); // then assertThat(stubTransformer.transformCalled, is(true)); } @Test public void shouldReturnRegisteredCustomEncoders() throws IOException { // when Transformer transformer = new TestDateTransformer(); BSON.addEncodingHook(TestDate.class, transformer); // then assertThat(BSON.hasEncodeHooks(), is(true)); List<Transformer> encodingHooks = BSON.getEncodingHooks(TestDate.class); assertThat(encodingHooks, is(notNullValue())); assertThat(encodingHooks, is(asList(transformer))); } @Test public void shouldRemoveSpecificRegisteredCustomEncoders() throws IOException { Transformer transformer = new TestDateTransformer(); BSON.addEncodingHook(TestDate.class, transformer); // when BSON.removeEncodingHook(TestDate.class, transformer); // then assertThat(BSON.getEncodingHooks(TestDate.class), not(contains(transformer))); } @Test public void shouldClearCustomDecoders() throws IOException { // given BSON.addDecodingHook(Date.class, new TestDateTransformer()); byte[] encodedDocument = encodeDocumentToByteArray(new BasicBSONObject("date", new Date())); BSONCallback bsonCallback = new BasicBSONCallback(); // when BSON.clearDecodingHooks(); new BasicBSONDecoder().decode(new ByteArrayInputStream(encodedDocument), bsonCallback); // then BSONObject decodedDocument = (BSONObject) bsonCallback.get(); assertThat(decodedDocument.get("date"), is(instanceOf(java.util.Date.class))); } @Test public void shouldUseCustomDecodersWhenDecodingObjectOfRegisteredClass() throws IOException { // given @SuppressWarnings("deprecation") byte[] encodedDocument = encodeDocumentToByteArray(new BasicBSONObject("date", new Date(2009, 01, 23, 10, 53, 42))); BSONCallback bsonCallback = new BasicBSONCallback(); // when BSON.addDecodingHook(Date.class, new TestDateTransformer()); new BasicBSONDecoder().decode(new ByteArrayInputStream(encodedDocument), bsonCallback); // then BSONObject decodedDocument = (BSONObject) bsonCallback.get(); assertThat(decodedDocument.get("date"), is(instanceOf(TestDate.class))); assertThat((TestDate) decodedDocument.get("date"), is(new TestDate(2009, 01, 23, 10, 53, 42))); } @Test public void shouldReturnRegisteredCustomDecoders() throws IOException { // when Transformer transformer = new TestDateTransformer(); BSON.addDecodingHook(Date.class, transformer); // then assertThat(BSON.hasDecodeHooks(), is(true)); List<Transformer> decodingHooks = BSON.getDecodingHooks(Date.class); assertThat(decodingHooks, is(notNullValue())); assertThat(decodingHooks, is(asList(transformer))); } @Test public void shouldRemoveSpecificRegisteredCustomDecoders() throws IOException { // given Transformer transformer = new TestDateTransformer(); BSON.addDecodingHook(Date.class, transformer); // when BSON.removeDecodingHook(Date.class, transformer); // expect assertThat(BSON.getDecodingHooks(Date.class), not(contains(transformer))); } @Test public void testEquals() { assertThat(new BasicBSONObject("a", 1111111111111111111L), is(not(new BasicBSONObject("a", 1111111111111111112L)))); assertThat(new BasicBSONObject("a", 100.1D), is(not(new BasicBSONObject("a", 100.2D)))); assertThat(new BasicBSONObject("a", 100.1F), is(not(new BasicBSONObject("a", 100.2F)))); assertEquals(new BasicBSONObject("a", 100.1D), new BasicBSONObject("a", 100.1D)); assertEquals(new BasicBSONObject("a", 100.1F), new BasicBSONObject("a", 100.1F)); assertEquals(new BasicBSONObject("a", 100L), new BasicBSONObject("a", 100L)); } @Test public void testRandomRoundTrips() { roundTrip(new BasicBSONObject("a", "")); roundTrip(new BasicBSONObject("a", "a")); roundTrip(new BasicBSONObject("a", "b")); } private byte[] encodeDocumentToByteArray(final BSONObject document) { OutputBuffer outputBuffer = new BasicOutputBuffer(); BSONEncoder encoder = new BasicBSONEncoder(); encoder.set(outputBuffer); encoder.putObject(document); encoder.done(); return outputBuffer.toByteArray(); } private void roundTrip(final BSONObject o) { assertEquals(o, BSON.decode(BSON.encode(o))); } @Test public void testEncodingDecode() { BasicDBObject inputDoc = new BasicDBObject("_id", 1); byte[] encoded = BSON.encode(inputDoc); assertEquals(inputDoc, BSON.decode(encoded)); } @Test public void testToInt() { assertEquals(1, BSON.toInt(Boolean.TRUE)); assertEquals(0, BSON.toInt(Boolean.FALSE)); assertEquals(12, BSON.toInt(12.23f)); assertEquals(21, BSON.toInt(21.32d)); assertEquals(13, BSON.toInt(13)); } private static class StubTransformer implements Transformer { private boolean transformCalled = false; @Override public Object transform(final Object objectToTransform) { transformCalled = true; return true; } } private class TestDateTransformer implements Transformer { @SuppressWarnings("deprecation") public Object transform(final Object objectToTransform) { if (objectToTransform instanceof TestDate) { TestDate td = (TestDate) objectToTransform; return new java.util.Date(td.year, td.month, td.date, td.hour, td.minute, td.second); } else if (objectToTransform instanceof java.util.Date) { Date d = (Date) objectToTransform; return new TestDate(d.getYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds()); } else { return objectToTransform; } } } private class TestDate { private final int year; private final int month; private final int date; private final int hour; private final int minute; private final int second; TestDate(final int year, final int month, final int date, final int hour, final int minute, final int second) { this.year = year; this.month = month; this.date = date; this.hour = hour; this.minute = minute; this.second = second; } @Override public boolean equals(final Object other) { if (this == other) { return true; } if (!(other instanceof TestDate)) { return false; } TestDate otherTestDate = (TestDate) other; return (otherTestDate.year == this.year && otherTestDate.month == this.month && otherTestDate.date == this.date && otherTestDate.hour == this.hour && otherTestDate.minute == this.minute && otherTestDate.second == this.second ); } @Override public int hashCode() { int result = year; result = 31 * result + month; result = 31 * result + date; result = 31 * result + hour; result = 31 * result + minute; result = 31 * result + second; return result; } @Override public String toString() { return year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second; } } }