Permalink
Browse files

Initial import

  • Loading branch information...
1 parent 1bc8c1e commit fd76fbadef3c8ae934386af6bb516154ba176d72 Jakub Holy committed May 12, 2012
@@ -0,0 +1,30 @@
+Example of Automatic Generic Data Mappers
+=========================================
+
+See the description at the blog post [XXXXX]().
+
+We are mapping:
+
+* XML to Java Bean via JAXB
+* Java Bean to MongoDB's DBObject (BSONObject) via Jackson Mongo Mapper
+* MongoDB's DBObject to Java Map via its own method
+* Java Map to JSON via Jersey's Pojo Mapping Feature, based on Jackson
+
+Action!
+-------
+
+To try this out, run
+```
+mvn test
+```
+
+Note about performance
+----------------------
+
+I did some testing for my current project and the results were that using JAXB for automatic XML to Java
+mapping was considerably faster than our hand-coded parsing based on XPath and that the
+mapping of Java Bean to/from Mongo DB was about 10-20 ms slower than when hand-coded (i.e. totally
+negligible compared f.ex. to the time necessary to retrieve the XML).
+
+Notice that the times printed by the test have little value as they include the setup overhead that
+normally get distributed among all (presumably many) objects handled by the system.
@@ -0,0 +1,99 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>net.jakubholy.blog</groupId>
+ <artifactId>generic-pojo-mappers</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <name>generic-pojo-mappers</name>
+ <url>https://github.com/jakubholynet/blog</url>
+
+ <properties>
+ <jersey.version>1.11</jersey.version> <!-- First to use Jackson 1.9[.2], required by mongo-jackson-mapper -->
+ <mongo-jackson-mapper.version>1.3</mongo-jackson-mapper.version> <!-- Req. Jackson 1.9.2, same as Jersey -->
+ <mongo-java-driver.version>2.7.2</mongo-java-driver.version> <!-- Compatible w/ mongo-jackson-mapper 1.3-1.4.1 -->
+ <jetty.version>8.0.0.M2</jetty.version>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <compileSource>1.6</compileSource>
+ <compileTarget>1.6</compileTarget>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${jersey.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-servlet</artifactId>
+ <version>${jersey.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-client</artifactId>
+ <version>${jersey.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-json</artifactId>
+ <version>${jersey.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.sun.jersey.jersey-test-framework</groupId>
+ <artifactId>jersey-test-framework-grizzly</artifactId>
+ <version>${jersey.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>jsr250-api</artifactId>
+ <version>1.0</version>
+ </dependency-->
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-webapp</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mongodb</groupId>
+ <artifactId>mongo-java-driver</artifactId>
+ <version>${mongo-java-driver.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>net.vz.mongodb.jackson</groupId>
+ <artifactId>mongo-jackson-mapper</artifactId>
+ <version>${mongo-jackson-mapper.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit-dep</artifactId>
+ <version>4.10</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.easytesting</groupId>
+ <artifactId>fest-assert</artifactId>
+ <version>1.4</version>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+</project>
@@ -0,0 +1,35 @@
+package net.jakubholy.blog.genericmappers.mongo;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import com.mongodb.DBCursor;
+import com.mongodb.DBObject;
+
+/**
+ * Iterate over search results returning the elements as Maps instead of
+ * the native BSON objects.
+ */
+final class DbCollectionMapIterator implements Iterator<Map<String, Object>> {
+ private final Iterator<DBObject> allDocuments;
+
+ public DbCollectionMapIterator(DBCursor allDocuments) {
+ this.allDocuments = allDocuments.iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return allDocuments.hasNext();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Map<String, Object> next() {
+ return allDocuments.next().toMap();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+}
@@ -0,0 +1,78 @@
+package net.jakubholy.blog.genericmappers.mongo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.vz.mongodb.jackson.JacksonDBCollection;
+import net.vz.mongodb.jackson.internal.MongoAnnotationIntrospector;
+import net.vz.mongodb.jackson.internal.MongoJacksonHandlerInstantiator;
+import net.vz.mongodb.jackson.internal.MongoJacksonMapperModule;
+
+import org.codehaus.jackson.Version;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.module.SimpleModule;
+
+import com.mongodb.DB;
+import com.mongodb.DBCollection;
+
+/**
+ * Helper for working with Mongo collections via POJOs,
+ * using the Jackson object mapper.
+ */
+class JacksonPojoCollectionHelper {
+
+ private static final ObjectMapper MAP_KEY_SANITIZING_MAPPER = createCustomizedObjectMapper();
+ private final DB mongoDb;
+ private final Map<String, JacksonDBCollection<?, ?>> cache = new HashMap<String, JacksonDBCollection<?,?>>();
+
+ public JacksonPojoCollectionHelper(DB mongoDb) {
+ this.mongoDb = mongoDb;
+ }
+
+ /**
+ * Create our customized object mapper that f.ex. takes care
+ * of removing invalid characters from map keys.
+ */
+ private static ObjectMapper createCustomizedObjectMapper() {
+ SimpleModule mapKeyModule = new SimpleModule("MyMapKeySanitizingSerializerModule"
+ , new Version(1, 0, 0, null));
+ mapKeyModule.addKeySerializer(String.class, new KeySanitizingSerializer());
+
+ final ObjectMapper customizedMapper = new ObjectMapper();
+ customizedMapper.registerModule(mapKeyModule);
+
+ // The following code has been copied from JacksonDBCollection and
+ // with mongo-jackson-mapper 1.4.x it can be replaced with call to
+ // MongoJacksonMapperModule.configure(mapper)
+ customizedMapper.registerModule(MongoJacksonMapperModule.INSTANCE);
+ customizedMapper.setHandlerInstantiator(new MongoJacksonHandlerInstantiator(
+ new MongoAnnotationIntrospector(customizedMapper.getDeserializationConfig())));
+ // end copy
+
+ return customizedMapper;
+ }
+
+ <T> JacksonDBCollection<T,Object> wrap(DBCollection dbCollection, Class<T> type) {
+ return JacksonDBCollection.wrap(dbCollection, type, Object.class, MAP_KEY_SANITIZING_MAPPER);
+ }
+
+ public <T> JacksonDBCollection<T, Object> getPojoCollectionFor(String collectionName, Class<T> pojoType) {
+ DBCollection rawCollection = mongoDb.getCollection(collectionName);
+
+ @SuppressWarnings("unchecked")
+ JacksonDBCollection<T, Object> pojoCollection = (JacksonDBCollection<T, Object>) cache.get(collectionName);
+ if (pojoCollection == null) {
+ pojoCollection = wrap(rawCollection, pojoType);
+ cache.put(collectionName, pojoCollection);
+ }
+
+ return pojoCollection;
+ }
+
+ public <T> JacksonDBCollection<T, Object> getPojoCollectionFor(String collectionName, T pojo) {
+ @SuppressWarnings("unchecked")
+ Class<T> pojoType = (Class<T>) pojo.getClass();
+ return getPojoCollectionFor(collectionName, pojoType);
+ }
+
+}
@@ -0,0 +1,26 @@
+package net.jakubholy.blog.genericmappers.mongo;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+/**
+ * Replace the characters that are not legal in JSON field names,
+ * namely '.' and leading '$'.
+ */
+class KeySanitizingSerializer extends JsonSerializer<String> {
+
+ @Override
+ public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
+ JsonProcessingException {
+ if (value.startsWith("$")) {
+ value = "#" + value.substring(1);
+ }
+ jgen.writeFieldName(value.replace('.', '-'));
+
+ }
+
+}
@@ -0,0 +1,87 @@
+package net.jakubholy.blog.genericmappers.mongo;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import net.vz.mongodb.jackson.JacksonDBCollection;
+
+import com.mongodb.DB;
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+import com.mongodb.Mongo;
+import com.mongodb.MongoException;
+import com.mongodb.WriteConcern;
+
+public class MongoStorage {
+
+ private Mongo mongo;
+
+ private final JacksonPojoCollectionHelper pojoDbHelper;
+
+ public MongoStorage(Mongo mongo) {
+ this.mongo = mongo;
+ this.mongo.setWriteConcern(WriteConcern.SAFE);
+ this.pojoDbHelper = new JacksonPojoCollectionHelper(getMongoDb());
+ }
+
+ /**
+ * Fetch all the documents from a collection and return them as Java maps.
+ * @param collectionName (required)
+ */
+ public Iterable<Map<String, Object>> getDocumentsAsMap(String collectionName) {
+ try {
+ final DB db = getMongoDb();
+ final DBCollection collection = db.getCollection(collectionName);
+
+ final DBCursor allDocuments = collection.find();
+
+ return new Iterable<Map<String, Object>>() {
+
+ @Override
+ public Iterator<Map<String, Object>> iterator() {
+ return new DbCollectionMapIterator(allDocuments);
+ }
+ };
+
+ } catch (MongoException me) {
+ throw new RuntimeException(me);
+ }
+ }
+
+ public <T> void updateById(String collectionName, T pojo, Object id) {
+ JacksonDBCollection<T, Object> pojos = pojoDbHelper.getPojoCollectionFor(collectionName, pojo);
+ final int updateCount = pojos.updateById(id, pojo).getN();
+ if (updateCount != 1) {
+ throw new IllegalStateException("The object to be updated doesn't " + "exist in the collection '"
+ + collectionName + "' with the id '" + id + "'. Update pojo: " + pojo);
+ }
+ }
+
+ /**
+ * Look ma, automatic POJO to Mongo's BSON object mapping without any manual transformations!
+ * @param collectionName (required) the collection to insert the document representing the pojo into
+ * @param pojo (required) the Java bean to store, optionally annotated with some Jackson annotations
+ */
+ public <T> void insert(String collectionName, T pojo) {
+ try {
+ // SAFE = Require Mongo to wait for response from the DB to verify
+ // there isn't a duplicate
+ pojoDbHelper.getPojoCollectionFor(collectionName, pojo).insert(pojo, WriteConcern.SAFE);
+ } catch (MongoException.DuplicateKey e) {
+ throw new IllegalStateException("A document with the same id exists already: " + e.getMessage());
+ }
+ }
+
+ /** For testing */
+ public void dropDatabase() {
+ getMongoDb().dropDatabase();
+ }
+
+ public <T> T findOneById(String collectionName, Object id, Class<T> type) {
+ return pojoDbHelper.getPojoCollectionFor(collectionName, type).findOneById(id);
+ }
+
+ private DB getMongoDb() {
+ return mongo.getDB("myDb");
+ }
+}
Oops, something went wrong.

0 comments on commit fd76fba

Please sign in to comment.