Java tutorial
// Copyright 2010-2011 Michel Kraemer // // 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 de.undercouch.bson4jackson; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; import org.bson.BSONEncoder; import org.bson.BSONObject; import org.bson.BasicBSONEncoder; import org.bson.BasicBSONObject; import org.bson.types.BSONTimestamp; import org.bson.types.Binary; import org.bson.types.Code; import org.bson.types.CodeWScope; import org.bson.types.Symbol; import org.junit.Test; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.MappingIterator; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; import de.undercouch.bson4jackson.types.JavaScript; import de.undercouch.bson4jackson.types.ObjectId; import de.undercouch.bson4jackson.types.Timestamp; /** * Tests {@link BsonParser} * @author Michel Kraemer */ public class BsonParserTest { /** * Simple test class for {@link BsonParserTest#parseRootObjectArray()} */ public static class SimpleClass { public String name; } /** * Simple test class for {@link BsonParserTest#parseBinaryObject()} */ public static class BinaryClass { public byte[] barr; } /** * Simple test class for {@link BsonParserTest#parseObjectId()} */ public static class ObjectIdClass { public org.bson.types.ObjectId oid; } private <T> T parseBsonObject(BSONObject o, Class<T> cls, Module... modules) throws IOException { BSONEncoder enc = new BasicBSONEncoder(); byte[] b = enc.encode(o); ByteArrayInputStream bais = new ByteArrayInputStream(b); BsonFactory fac = new BsonFactory(); ObjectMapper mapper = new ObjectMapper(fac); if (modules != null) { for (Module mod : modules) { mapper.registerModule(mod); } } fac.setCodec(mapper); return mapper.readValue(bais, cls); } private Map<?, ?> parseBsonObject(BSONObject o) throws IOException { return parseBsonObject(o, Map.class); } @Test public void parsePrimitives() throws Exception { BSONObject o = new BasicBSONObject(); o.put("Double", 5.0); o.put("Float", 10.0f); o.put("String", "Hello World"); o.put("Null", null); o.put("Bool1", true); o.put("Bool2", false); o.put("Int32", 1234); o.put("Int64", 1234L); Map<?, ?> data = parseBsonObject(o); assertEquals(5.0, data.get("Double")); assertEquals(10.0, data.get("Float")); assertEquals("Hello World", data.get("String")); assertNull(data.get("Null")); assertEquals(true, data.get("Bool1")); assertEquals(false, data.get("Bool2")); assertEquals(1234, data.get("Int32")); assertEquals(1234L, data.get("Int64")); } /** * Tests reading a very large string. Refers issue #18 * @throws Exception if something went wrong * @author endasb */ @Test public void parseBigString() throws Exception { BSONObject o = new BasicBSONObject(); StringBuilder bigStr = new StringBuilder(); for (int i = 0; i < 80000; i++) { bigStr.append("abc"); } o.put("String", bigStr.toString()); Map<?, ?> data = parseBsonObject(o); assertEquals(240000, data.get("String").toString().length()); } /** * Tests reading a very large string using multiple threads. Refers * issue #19. Does not fail reproducibly, but with very high probability. * You may have to run unit tests several times though to really rule out * multi-threading issues. * @throws Exception if something went wrong * @author endasb */ @Test public void parseBigStringInThreads() throws Exception { final BSONObject o = new BasicBSONObject(); final AtomicInteger fails = new AtomicInteger(0); StringBuilder bigStr = new StringBuilder(); for (int i = 0; i < 80000; i++) { bigStr.append("abc"); } o.put("String", bigStr.toString()); ArrayList<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < 50; i++) { threads.add(new Thread(new Runnable() { @Override public void run() { try { Map<?, ?> data = parseBsonObject(o); data = parseBsonObject(o); assertNotNull(data); } catch (Exception e) { fail("Threading issue " + fails.incrementAndGet()); } } })); } for (Thread thread : threads) { thread.start(); } for (Thread thread : threads) { thread.join(); } assertEquals(0, fails.get()); } @Test public void parseBig() throws Exception { BSONObject o = new BasicBSONObject(); o.put("Double", 5.0); o.put("Int32", 1234); BSONEncoder enc = new BasicBSONEncoder(); byte[] b = enc.encode(o); ByteArrayInputStream bais = new ByteArrayInputStream(b); ObjectMapper mapper = new ObjectMapper(new BsonFactory()); mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); mapper.configure(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS, true); Map<?, ?> data = mapper.readValue(bais, Map.class); assertEquals(BigDecimal.class, data.get("Double").getClass()); assertEquals(BigInteger.class, data.get("Int32").getClass()); } @Test public void parseComplex() throws Exception { BSONObject o = new BasicBSONObject(); o.put("Timestamp", new BSONTimestamp(0xAABB, 0xCCDD)); o.put("Symbol", new Symbol("Test")); o.put("ObjectId", new org.bson.types.ObjectId(Integer.MAX_VALUE, -2, Integer.MIN_VALUE)); Pattern p = Pattern.compile(".*", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE | Pattern.UNICODE_CASE); o.put("Regex", p); Map<?, ?> data = parseBsonObject(o); assertEquals(new Timestamp(0xAABB, 0xCCDD), data.get("Timestamp")); assertEquals(new de.undercouch.bson4jackson.types.Symbol("Test"), data.get("Symbol")); ObjectId oid = (ObjectId) data.get("ObjectId"); assertEquals(Integer.MAX_VALUE, oid.getTime()); assertEquals(-2, oid.getMachine()); assertEquals(Integer.MIN_VALUE, oid.getInc()); Pattern p2 = (Pattern) data.get("Regex"); assertEquals(p.flags(), p2.flags()); assertEquals(p.pattern(), p2.pattern()); } @Test public void parseUndefined() throws Exception { BSONObject o = new BasicBSONObject(); o.put("Undefined", new Object()); o.put("Int32", 5); BSONEncoder enc = new BasicBSONEncoder() { @Override protected boolean putSpecial(String name, Object o) { putUndefined(name); return true; } }; byte[] b = enc.encode(o); ByteArrayInputStream bais = new ByteArrayInputStream(b); ObjectMapper mapper = new ObjectMapper(new BsonFactory()); Map<?, ?> data = mapper.readValue(bais, Map.class); assertEquals(1, data.size()); assertEquals(5, data.get("Int32")); } @Test public void parseEmbeddedDocument() throws Exception { BSONObject o1 = new BasicBSONObject(); o1.put("Int32", 5); BSONObject o2 = new BasicBSONObject(); o2.put("Int64", 10L); o1.put("Obj", o2); o1.put("String", "Hello"); Map<?, ?> data = parseBsonObject(o1); assertEquals(3, data.size()); assertEquals(5, data.get("Int32")); Map<?, ?> data2 = (Map<?, ?>) data.get("Obj"); assertEquals(1, data2.size()); assertEquals(10L, data2.get("Int64")); assertEquals("Hello", data.get("String")); } @Test public void parseEmbeddedArray() throws Exception { List<Integer> i = new ArrayList<Integer>(); i.add(5); i.add(6); BSONObject o = new BasicBSONObject(); o.put("Int32", 5); o.put("Arr", i); o.put("String", "Hello"); Map<?, ?> data = parseBsonObject(o); assertEquals(3, data.size()); assertEquals(5, data.get("Int32")); } /** * Tests reading an embedded document through * {@link BsonParser#readValueAsTree()}. Refers issue #9 * @throws Exception if something went wrong * @author audistard */ @Test public void parseEmbeddedDocumentAsTree() throws Exception { BSONObject o2 = new BasicBSONObject(); o2.put("Int64", 10L); BSONObject o3 = new BasicBSONObject(); o3.put("Int64", 11L); BSONObject o1 = new BasicBSONObject(); o1.put("Obj2", o2); o1.put("Obj3", o3); BSONEncoder enc = new BasicBSONEncoder(); byte[] b = enc.encode(o1); ByteArrayInputStream bais = new ByteArrayInputStream(b); BsonFactory fac = new BsonFactory(); ObjectMapper mapper = new ObjectMapper(fac); fac.setCodec(mapper); BsonParser dec = fac.createParser(bais); assertEquals(JsonToken.START_OBJECT, dec.nextToken()); assertEquals(JsonToken.FIELD_NAME, dec.nextToken()); assertEquals("Obj2", dec.getCurrentName()); assertEquals(JsonToken.START_OBJECT, dec.nextToken()); JsonNode obj2 = dec.readValueAsTree(); assertEquals(1, obj2.size()); assertNotNull(obj2.get("Int64")); assertEquals(10L, obj2.get("Int64").longValue()); assertEquals(JsonToken.FIELD_NAME, dec.nextToken()); assertEquals("Obj3", dec.getCurrentName()); assertEquals(JsonToken.START_OBJECT, dec.nextToken()); assertEquals(JsonToken.FIELD_NAME, dec.nextToken()); assertEquals("Int64", dec.getCurrentName()); assertEquals(JsonToken.VALUE_NUMBER_INT, dec.nextToken()); assertEquals(11L, dec.getLongValue()); assertEquals(JsonToken.END_OBJECT, dec.nextToken()); assertEquals(JsonToken.END_OBJECT, dec.nextToken()); } @Test public void parseCode() throws Exception { BSONObject scope = new BasicBSONObject(); scope.put("Int32", 5); BSONObject o = new BasicBSONObject(); o.put("Code1", new CodeWScope("alert('test');", scope)); o.put("Code2", new Code("alert('Hello');")); Map<?, ?> data = parseBsonObject(o); assertEquals(2, data.size()); JavaScript c1 = (JavaScript) data.get("Code1"); JavaScript c2 = (JavaScript) data.get("Code2"); assertEquals("alert('test');", c1.getCode()); assertEquals("alert('Hello');", c2.getCode()); Map<String, Object> c1scope = c1.getScope(); assertEquals(5, c1scope.get("Int32")); } @Test public void parseBinary() throws Exception { byte[] b = new byte[] { 1, 2, 3, 4, 5 }; BSONObject o = new BasicBSONObject(); o.put("b1", b); o.put("b2", new Binary(BsonConstants.SUBTYPE_BINARY, b)); o.put("uuid", new UUID(1L, 2L)); Map<?, ?> data = parseBsonObject(o); assertEquals(3, data.size()); assertArrayEquals(b, (byte[]) data.get("b1")); assertArrayEquals(b, (byte[]) data.get("b2")); assertEquals(new UUID(1L, 2L), data.get("uuid")); } @Test public void parseBinaryObject() throws Exception { byte[] b = new byte[] { 1, 2, 3, 4, 5 }; BSONObject o = new BasicBSONObject(); o.put("barr", b); BinaryClass data = parseBsonObject(o, BinaryClass.class); assertArrayEquals(b, data.barr); } /** * Test if {@link BsonParser#nextToken()} returns null if there * is no more input. Refers issue #10. * @throws Exception if something went wrong * @author hertzsprung * @author Michel Kraemer */ @Test public void parseBeyondEnd() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); BsonFactory bsonFactory = new BsonFactory(); BsonGenerator generator = bsonFactory.createGenerator(out); generator.writeStartObject(); generator.writeStringField("myField", "myValue"); generator.writeEndObject(); generator.close(); BsonParser parser = bsonFactory.createJsonParser(out.toByteArray()); //the following loop shall throw no exception and end after 4 iterations int i = 0; while (parser.nextToken() != null) { ++i; assertTrue(i <= 4); } assertEquals(4, i); } /** * Make sure we honor the length of the document if requested * @throws Exception if something went wrong */ @Test public void honorLength() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); BsonFactory bsonFactory = new BsonFactory(); bsonFactory.enable(BsonParser.Feature.HONOR_DOCUMENT_LENGTH); BsonGenerator generator = bsonFactory.createGenerator(out); generator.writeStartObject(); generator.writeStringField("myField", "myValue"); generator.writeEndObject(); generator.close(); out.write(new String("Hello world!\n").getBytes()); InputStream is = new ByteArrayInputStream(out.toByteArray()); ObjectMapper mapper = new ObjectMapper(bsonFactory); bsonFactory.setCodec(mapper); Map<?, ?> result = mapper.readValue(is, Map.class); assertEquals("myValue", result.get("myField")); // Now check that we can read the extra string we put at the end BufferedReader reader = new BufferedReader(new InputStreamReader(is)); assertEquals("Hello world!", reader.readLine()); } /** * Checks if the parser returns a textual representation of arbitrary * tokens. See issue #23. * @throws Exception if something went wrong */ @Test public void parseAsText() throws Exception { BSONObject o = new BasicBSONObject(); o.put("Float", 5.0f); o.put("Int32", 1234); BSONEncoder enc = new BasicBSONEncoder(); byte[] b = enc.encode(o); ByteArrayInputStream bais = new ByteArrayInputStream(b); BsonFactory fac = new BsonFactory(); BsonParser dec = fac.createParser(bais); assertEquals(JsonToken.START_OBJECT, dec.nextToken()); assertEquals(JsonToken.FIELD_NAME, dec.nextToken()); assertEquals("Float", dec.getCurrentName()); assertEquals(JsonToken.VALUE_NUMBER_FLOAT, dec.nextToken()); assertEquals(5.0f, dec.getFloatValue(), 0.00001); assertEquals("5.0", dec.getText()); assertEquals(JsonToken.FIELD_NAME, dec.nextToken()); assertEquals("Int32", dec.getCurrentName()); assertEquals(JsonToken.VALUE_NUMBER_INT, dec.nextToken()); assertEquals(1234, dec.getIntValue()); assertEquals("1234", dec.getText()); assertEquals(JsonToken.END_OBJECT, dec.nextToken()); } /** * Tests if a simple BSON file can be read successfully * @throws Exception if something went wrong */ @Test public void readBSONFile() throws Exception { InputStream is = getClass().getResourceAsStream("test.bson"); try { ObjectMapper mapper = new ObjectMapper(new BsonFactory()); MappingIterator<BSONObject> iterator = mapper.reader(BasicBSONObject.class).readValues(is); BSONObject o = null; while (iterator.hasNext()) { assertNull(o); BSONObject object = iterator.next(); assertNotNull(object); o = object; } assertEquals("Hello world", o.get("message")); assertEquals(10.0, o.get("size")); assertTrue(o.keySet().contains("_id")); assertEquals(3, o.keySet().size()); } finally { is.close(); } } /** * Tests if a root-level array can be read correctly. Fixes issue #31 * @throws Exception if something went wrong */ @Test public void parseRootArray() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); BsonFactory bsonFactory = new BsonFactory(); BsonGenerator generator = bsonFactory.createGenerator(out); generator.writeStartArray(); generator.writeString("first"); generator.writeString("second"); generator.writeString("third"); generator.writeEndArray(); generator.close(); InputStream is = new ByteArrayInputStream(out.toByteArray()); ObjectMapper mapper = new ObjectMapper(bsonFactory); bsonFactory.setCodec(mapper); String[] result = mapper.readValue(is, String[].class); assertEquals("first", result[0]); assertEquals("second", result[1]); assertEquals("third", result[2]); } /** * Tests if an empty root array can be parsed correctly * @throws Exception if something went wrong */ @Test public void parseEmptyRootArray() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); BsonFactory bsonFactory = new BsonFactory(); BsonGenerator generator = bsonFactory.createGenerator(out); generator.writeStartArray(); generator.writeEndArray(); generator.close(); InputStream is = new ByteArrayInputStream(out.toByteArray()); ObjectMapper mapper = new ObjectMapper(bsonFactory); bsonFactory.setCodec(mapper); String[] result = mapper.readValue(is, String[].class); assertEquals(0, result.length); } /** * Tests if a root object is not accidentally parsed as an array * @throws Exception if something went wrong */ @Test(expected = JsonMappingException.class) public void parseRootObjectAsArray() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); BsonFactory bsonFactory = new BsonFactory(); BsonGenerator generator = bsonFactory.createGenerator(out); generator.writeStartObject(); generator.writeStringField("myField", "myValue"); generator.writeEndObject(); generator.close(); InputStream is = new ByteArrayInputStream(out.toByteArray()); ObjectMapper mapper = new ObjectMapper(bsonFactory); bsonFactory.setCodec(mapper); mapper.readValue(is, String[].class); } /** * Creates a root array consisting of two simple objects and tries to * deserialize them * @throws Exception if something goes wrong */ @Test public void parseRootObjectArray() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); BsonFactory bsonFactory = new BsonFactory(); BsonGenerator generator = bsonFactory.createGenerator(out); generator.writeStartArray(); generator.writeStartObject(); generator.writeStringField("name", "test"); generator.writeEndObject(); generator.writeStartObject(); generator.writeStringField("name", "test2"); generator.writeEndObject(); generator.writeEndArray(); generator.close(); InputStream is = new ByteArrayInputStream(out.toByteArray()); ObjectMapper mapper = new ObjectMapper(bsonFactory); bsonFactory.setCodec(mapper); SimpleClass[] result = mapper.readValue(is, SimpleClass[].class); assertEquals("test", result[0].name); assertEquals("test2", result[1].name); } /** * Check if org.bson.types.ObjectId can be serialized and deserialized as * a byte array. See issue #38 * @throws Exception if something goes wrong */ @Test public void parseObjectId() throws Exception { class ObjectIdDeserializer extends StdDeserializer<org.bson.types.ObjectId> { private static final long serialVersionUID = 6934309887169924897L; protected ObjectIdDeserializer() { super(org.bson.types.ObjectId.class); } @Override public org.bson.types.ObjectId deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonGenerationException { return new org.bson.types.ObjectId(jp.getBinaryValue()); } } org.bson.types.ObjectId oid = new org.bson.types.ObjectId(); BSONObject o = new BasicBSONObject(); o.put("oid", oid.toByteArray()); SimpleModule mod = new SimpleModule(); mod.addDeserializer(org.bson.types.ObjectId.class, new ObjectIdDeserializer()); ObjectIdClass res = parseBsonObject(o, ObjectIdClass.class, mod); assertEquals(oid, res.oid); } }