Skip to content

Instantly share code, notes, and snippets.

@rstiller
Created June 7, 2012 20:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rstiller/2891498 to your computer and use it in GitHub Desktop.
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.
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());
}
}
}
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);
}
}
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());
}
}
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