Created
June 7, 2012 20:59
-
-
Save rstiller/2891498 to your computer and use it in GitHub Desktop.
Simple Util Classes for Jackson. DBCursorReader makes a MongoDB DBCursor accessable as Reader e.g. for processing MongoDB Data with Jackson. MongoGenerator stores JSON objects in MongoDB Collections.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.rstiller.mongo; | |
import java.io.IOException; | |
import java.io.Reader; | |
import java.lang.reflect.Array; | |
import java.util.Date; | |
import java.util.Map; | |
import java.util.Set; | |
import org.bson.types.Binary; | |
import org.codehaus.jackson.Base64Variant; | |
import org.codehaus.jackson.Base64Variants; | |
import com.mongodb.Bytes; | |
import com.mongodb.DBCursor; | |
import com.mongodb.DBObject; | |
public class DBCursorReader extends Reader { | |
protected DBCursor cursor; | |
protected StringBuilder builder = new StringBuilder(); | |
protected int builderOffset = 0; | |
protected Base64Variant b64variant = Base64Variants.getDefaultVariant(); | |
public DBCursorReader(DBCursor cursor) { | |
this.cursor = cursor; | |
} | |
public DBCursorReader(DBCursor cursor, Base64Variant base64Variant) { | |
this.cursor = cursor; | |
b64variant = base64Variant; | |
} | |
@Override | |
public void close() throws IOException { | |
} | |
@Override | |
public int read(char[] buffer, int offset, int length) throws IOException { | |
int remaining = length, tmp, copied = 0; | |
while (remaining > 0) { | |
if (builderOffset >= builder.length()) { | |
fillBuffer(); | |
builderOffset = 0; | |
} | |
if (builder.length() == 0) { | |
break; | |
} | |
tmp = Math.min(length, builder.length() - builderOffset); | |
builder.getChars(builderOffset, builderOffset + tmp, buffer, offset); | |
builderOffset += tmp; | |
copied += tmp; | |
remaining -= tmp; | |
} | |
if (copied == 0) { | |
copied = -1; | |
} | |
return copied; | |
} | |
protected void fillBuffer() { | |
DBObject next; | |
builder.delete(0, builder.length()); | |
while (cursor.hasNext()) { | |
if ((next = cursor.next()) != null) { | |
stringify(next); | |
} | |
} | |
} | |
protected void stringify(DBObject obj) { | |
Object field; | |
Set<String> keys = obj.keySet(); | |
boolean filled = false; | |
builder.append('{'); | |
for (String key : keys) { | |
field = obj.get(key); | |
if (field != null) { | |
builder.append('"'); | |
builder.append(key); | |
builder.append("\":"); | |
stringify(field); | |
builder.append(','); | |
filled = true; | |
} | |
} | |
if (filled) { | |
builder.delete(builder.length() - 1, builder.length()); | |
} | |
builder.append('}'); | |
} | |
@SuppressWarnings("unchecked") | |
protected void stringify(Object obj) { | |
boolean filled = false; | |
CharSequence seq; | |
char c; | |
obj = Bytes.applyEncodingHooks(obj); | |
if (obj instanceof CharSequence) { | |
seq = (CharSequence) obj; | |
builder.append('"'); | |
for (int i = 0; i < seq.length(); i++) { | |
c = seq.charAt(i); | |
if (c == '\\') { | |
builder.append("\\\\"); | |
} else if (c == '"') { | |
builder.append("\\\""); | |
} else if (c == '\n') { | |
builder.append("\\n"); | |
} else if (c == '\r') { | |
builder.append("\\r"); | |
} else if (c == '\t') { | |
builder.append("\\t"); | |
} else if (c == '\b') { | |
builder.append("\\b"); | |
} else if (c < 32) { | |
continue; | |
} else { | |
builder.append(c); | |
} | |
} | |
builder.append('"'); | |
} else if (obj instanceof Boolean || obj instanceof Number) { | |
builder.append(obj); | |
} else if (obj instanceof Iterable) { | |
builder.append('['); | |
for (Object value : (Iterable<Object>) obj) { | |
stringify(value); | |
builder.append(','); | |
filled = true; | |
} | |
if (filled) { | |
builder.delete(builder.length() - 1, builder.length()); | |
} | |
builder.append(']'); | |
} else if (obj.getClass().isArray()) { | |
builder.append('['); | |
for (int i = 0; i < Array.getLength(obj); i++) { | |
stringify(Array.get(obj, i)); | |
builder.append(','); | |
filled = true; | |
} | |
if (filled) { | |
builder.delete(builder.length() - 1, builder.length()); | |
} | |
builder.append(']'); | |
} else if (obj instanceof DBObject) { | |
stringify((DBObject) obj); | |
} else if (obj instanceof Map) { | |
builder.append('{'); | |
for (Map.Entry<String, Object> entry : ((Map<String, Object>) obj).entrySet()) { | |
builder.append(entry.getKey()); | |
builder.append(':'); | |
stringify(entry.getValue()); | |
builder.append(','); | |
filled = true; | |
} | |
if (filled) { | |
builder.delete(builder.length() - 1, builder.length()); | |
} | |
builder.append('}'); | |
} else if (obj instanceof byte[]) { | |
builder.append(b64variant.encode((byte[]) obj)); | |
} else if (obj instanceof Binary) { | |
builder.append(b64variant.encode(((Binary) obj).getData())); | |
} else if (obj instanceof Date) { | |
builder.append(((Date) obj).getTime()); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.rstiller.mongo; | |
import java.io.IOException; | |
import java.math.BigDecimal; | |
import java.math.BigInteger; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.Stack; | |
import org.codehaus.jackson.Base64Variant; | |
import org.codehaus.jackson.JsonGenerationException; | |
import org.codehaus.jackson.ObjectCodec; | |
import org.codehaus.jackson.impl.JsonGeneratorBase; | |
import com.mongodb.BasicDBObject; | |
import com.mongodb.DBCollection; | |
import com.mongodb.WriteResult; | |
public class MongoGenerator extends JsonGeneratorBase { | |
protected class BuilderContext { | |
protected String name; | |
protected BasicDBObject obj = new BasicDBObject(); | |
protected BuilderContext parent; | |
public BuilderContext() { | |
} | |
public BuilderContext(BuilderContext parent) { | |
this.parent = parent; | |
} | |
public BuilderContext endArray() { | |
return parent; | |
} | |
public BuilderContext startArray() { | |
ArrayBuilderContext ctx = new ArrayBuilderContext(this); | |
if (name != null) { | |
obj.append(name, ctx.objs); | |
} | |
return ctx; | |
} | |
public BuilderContext endObject() { | |
return parent; | |
} | |
public BuilderContext startObject() { | |
BuilderContext ctx = new BuilderContext(this); | |
if (name != null) { | |
obj.append(name, ctx.obj); | |
} | |
return ctx; | |
} | |
public void addValue(Object value) { | |
if (name != null) { | |
obj.append(name, value); | |
} | |
} | |
@Override | |
public String toString() { | |
return getClass().getSimpleName() + " - " + (obj != null ? obj.toString() : null); | |
} | |
} | |
protected class ArrayBuilderContext extends BuilderContext { | |
protected List<Object> objs = new ArrayList<Object>(); | |
public ArrayBuilderContext(BuilderContext parent) { | |
super(parent); | |
} | |
@Override | |
public BuilderContext startArray() { | |
ArrayBuilderContext ctx = new ArrayBuilderContext(this); | |
objs.add(ctx.objs); | |
return ctx; | |
} | |
@Override | |
public BuilderContext startObject() { | |
BuilderContext ctx = new BuilderContext(this); | |
objs.add(ctx.obj); | |
return ctx; | |
} | |
@Override | |
public void addValue(Object value) { | |
objs.add(value); | |
} | |
} | |
protected DBCollection collection; | |
protected WriteResult writeResult; | |
protected BuilderContext rootCtx; | |
protected BuilderContext currentCtx; | |
protected Stack<Object> lastIds = new Stack<Object>(); | |
public MongoGenerator(DBCollection collection, ObjectCodec codec) { | |
this(collection, codec, Feature.collectDefaults()); | |
} | |
public MongoGenerator(DBCollection collection, ObjectCodec codec, int features) { | |
super(features, codec); | |
this.collection = collection; | |
try { | |
flush(); | |
} catch (IOException exception) { | |
} | |
} | |
public DBCollection getCollection() { | |
return collection; | |
} | |
public WriteResult getWriteResult() { | |
return writeResult; | |
} | |
public Stack<Object> getLastIds() { | |
return lastIds; | |
} | |
@Override | |
public void flush() throws IOException { | |
if (rootCtx != null) { | |
writeResult = collection.save(rootCtx.obj); | |
lastIds.push(rootCtx.obj.get("_id")); | |
} | |
rootCtx = null; | |
currentCtx = null; | |
} | |
@Override | |
protected void _releaseBuffers() { | |
} | |
@Override | |
public void writeEndArray() throws IOException, JsonGenerationException { | |
if (currentCtx != null) { | |
currentCtx = currentCtx.endArray(); | |
} | |
} | |
@Override | |
public void writeStartArray() throws IOException, JsonGenerationException { | |
if (currentCtx != null) { | |
currentCtx = currentCtx.startArray(); | |
} else { | |
throw new RuntimeException("can't write array as root object"); | |
} | |
} | |
@Override | |
public void writeEndObject() throws IOException, JsonGenerationException { | |
if (currentCtx != null) { | |
currentCtx = currentCtx.endObject(); | |
} | |
} | |
@Override | |
public void writeStartObject() throws IOException, JsonGenerationException { | |
if (currentCtx != null) { | |
currentCtx = currentCtx.startObject(); | |
} else { | |
rootCtx = new BuilderContext(); | |
currentCtx = rootCtx; | |
} | |
} | |
@Override | |
protected void _verifyValueWrite(String typeMsg) throws IOException, JsonGenerationException { | |
} | |
@Override | |
public void writeFieldName(String name) throws IOException, JsonGenerationException { | |
currentCtx.name = name; | |
} | |
@Override | |
public void writeString(String text) throws IOException, JsonGenerationException { | |
currentCtx.addValue(text); | |
} | |
@Override | |
public void writeString(char[] text, int offset, int len) throws IOException, JsonGenerationException { | |
currentCtx.addValue(new String(text, offset, len)); | |
} | |
@Override | |
public void writeRawUTF8String(byte[] text, int offset, int length) throws IOException, JsonGenerationException { | |
currentCtx.addValue(new String(text, offset, length, "utf-8")); | |
} | |
@Override | |
public void writeUTF8String(byte[] text, int offset, int length) throws IOException, JsonGenerationException { | |
currentCtx.addValue(new String(text, offset, length, "utf-8")); | |
} | |
@Override | |
public void writeRaw(String text) throws IOException, JsonGenerationException { | |
currentCtx.addValue(text); | |
} | |
@Override | |
public void writeRaw(String text, int offset, int len) throws IOException, JsonGenerationException { | |
currentCtx.addValue(text.substring(offset, offset + len)); | |
} | |
@Override | |
public void writeRaw(char[] text, int offset, int len) throws IOException, JsonGenerationException { | |
currentCtx.addValue(new String(text, offset, len)); | |
} | |
@Override | |
public void writeRaw(char c) throws IOException, JsonGenerationException { | |
currentCtx.addValue(c); | |
} | |
@Override | |
public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) throws IOException, JsonGenerationException { | |
currentCtx.addValue(Arrays.copyOfRange(data, offset, offset + len)); | |
} | |
@Override | |
public void writeNumber(int v) throws IOException, JsonGenerationException { | |
currentCtx.addValue(v); | |
} | |
@Override | |
public void writeNumber(long v) throws IOException, JsonGenerationException { | |
currentCtx.addValue(v); | |
} | |
@Override | |
public void writeNumber(BigInteger v) throws IOException, JsonGenerationException { | |
currentCtx.addValue(v); | |
} | |
@Override | |
public void writeNumber(double d) throws IOException, JsonGenerationException { | |
currentCtx.addValue(d); | |
} | |
@Override | |
public void writeNumber(float f) throws IOException, JsonGenerationException { | |
currentCtx.addValue(f); | |
} | |
@Override | |
public void writeNumber(BigDecimal dec) throws IOException, JsonGenerationException { | |
currentCtx.addValue(dec); | |
} | |
@Override | |
public void writeNumber(String encodedValue) throws IOException, JsonGenerationException, UnsupportedOperationException { | |
currentCtx.addValue(encodedValue); | |
} | |
@Override | |
public void writeBoolean(boolean state) throws IOException, JsonGenerationException { | |
currentCtx.addValue(state); | |
} | |
@Override | |
public void writeNull() throws IOException, JsonGenerationException { | |
currentCtx.addValue(null); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.rstiller.mongo; | |
import static org.junit.Assert.assertEquals; | |
import static org.junit.Assert.assertNotNull; | |
import static org.junit.Assert.assertTrue; | |
import static org.junit.Assert.fail; | |
import static org.mockito.Matchers.any; | |
import static org.mockito.Matchers.eq; | |
import static org.mockito.Mockito.verify; | |
import static org.mockito.Mockito.when; | |
import java.util.ArrayList; | |
import java.util.List; | |
import org.codehaus.jackson.map.JsonMappingException; | |
import org.codehaus.jackson.map.ObjectMapper; | |
import org.junit.Before; | |
import org.junit.Test; | |
import org.junit.runner.RunWith; | |
import org.mockito.Answers; | |
import org.mockito.ArgumentCaptor; | |
import org.mockito.Captor; | |
import org.mockito.Mock; | |
import org.mockito.runners.MockitoJUnitRunner; | |
import com.mongodb.BasicDBObject; | |
import com.mongodb.DBCollection; | |
import com.mongodb.DBCursor; | |
import com.mongodb.DBObject; | |
import com.mongodb.WriteConcern; | |
import com.mongodb.WriteResult; | |
@RunWith(MockitoJUnitRunner.class) | |
public class MongoTest { | |
@Mock(answer = Answers.RETURNS_MOCKS) | |
DBCollection collection; | |
@Mock | |
WriteResult writeResult; | |
@Captor | |
ArgumentCaptor<DBObject> objectCaptor; | |
@Mock | |
WriteConcern writeConcern; | |
@Mock | |
DBCursor cursor; | |
DBObject value; | |
ObjectMapper mapper; | |
MongoGenerator generator; | |
@Before | |
public void init() { | |
value = new BasicDBObject(); | |
when(cursor.next()).thenReturn(value); | |
when(cursor.hasNext()).thenReturn(true, false); | |
when(collection.getWriteConcern()).thenReturn(writeConcern); | |
when(collection.insert(any(DBObject.class))).thenReturn(writeResult); | |
mapper = new ObjectMapper(); | |
generator = new MongoGenerator(collection, mapper); | |
} | |
@SuppressWarnings("unchecked") | |
@Test | |
public void nestedObject() throws Exception { | |
TestBean bean1, bean2; | |
WriteResult lastResult; | |
bean2 = new TestBean(); | |
bean2.setData(new byte[] { 4, 5, 6 }); | |
bean2.setName("2"); | |
bean1 = new TestBean(); | |
bean1.setData(new byte[] { 1, 2, 3 }); | |
bean1.setName("1"); | |
bean1.setNext(bean2); | |
bean1.getBeans().add(new TestBean("3")); | |
bean1.getBeans().add(new TestBean("4")); | |
bean1.getBeans().add(new TestBean("5")); | |
mapper.writeValue(generator, bean1); | |
lastResult = generator.getWriteResult(); | |
verify(collection).insert(objectCaptor.capture(), eq(writeConcern)); | |
assertEquals("1", objectCaptor.getValue().get("name")); | |
assertEquals(1, ((byte[]) objectCaptor.getValue().get("data"))[0]); | |
assertEquals(2, ((byte[]) objectCaptor.getValue().get("data"))[1]); | |
assertEquals(3, ((byte[]) objectCaptor.getValue().get("data"))[2]); | |
assertEquals("2", ((DBObject) objectCaptor.getValue().get("next")).get("name")); | |
assertEquals(4, ((byte[]) ((DBObject) objectCaptor.getValue().get("next")).get("data"))[0]); | |
assertEquals(5, ((byte[]) ((DBObject) objectCaptor.getValue().get("next")).get("data"))[1]); | |
assertEquals(6, ((byte[]) ((DBObject) objectCaptor.getValue().get("next")).get("data"))[2]); | |
assertEquals("3", ((DBObject) ((List<Object>) objectCaptor.getValue().get("beans")).get(0)).get("name")); | |
assertEquals("4", ((DBObject) ((List<Object>) objectCaptor.getValue().get("beans")).get(1)).get("name")); | |
assertEquals("5", ((DBObject) ((List<Object>) objectCaptor.getValue().get("beans")).get(2)).get("name")); | |
assertNotNull(lastResult); | |
} | |
@Test | |
public void array() throws Exception { | |
List<TestBean> array = new ArrayList<TestBean>(); | |
array.add(new TestBean("1")); | |
array.add(new TestBean("2")); | |
array.add(new TestBean("3")); | |
try { | |
mapper.writeValue(generator, array); | |
fail(); | |
} catch (JsonMappingException exception) { | |
assertTrue(exception.getCause() instanceof RuntimeException); | |
} catch (Exception exception) { | |
fail(); | |
} | |
} | |
@Test | |
public void simpleRead() throws Exception { | |
TestBean bean; | |
value.put("name", "1"); | |
value.put("count", "123"); | |
bean = mapper.readValue(new DBCursorReader(cursor), TestBean.class); | |
assertEquals("1", bean.getName()); | |
assertEquals(123, bean.getCount()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.rstiller.mongo; | |
import java.util.List; | |
public class TestBean { | |
protected String name; | |
protected int count; | |
protected byte[] data; | |
protected TestBean next; | |
protected List<TestBean> beans; | |
public TestBean() { | |
} | |
public TestBean(String name) { | |
this.name = name; | |
} | |
public int getCount() { | |
return count; | |
} | |
public void setCount(int count) { | |
this.count = count; | |
} | |
public TestBean getNext() { | |
return next; | |
} | |
public void setNext(TestBean next) { | |
this.next = next; | |
} | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
public byte[] getData() { | |
return data; | |
} | |
public void setData(byte[] data) { | |
this.data = data; | |
} | |
public List<TestBean> getBeans() { | |
return beans; | |
} | |
public void setBeans(List<TestBean> beans) { | |
this.beans = beans; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment