Friday 12 February 2016

Mongo Unit Testing - Fongo

Unit testing with any database can be a bit of a pain.  However, with Mongo it is really easy thanks to a testing framework called Fake Mongo (fongo).  The Fongo Client implements the MongoClient interface in memory so is perfect for unit testing.

https://github.com/fakemongo/fongo

Maven Dependency

    <dependency>
        <groupId>com.github.fakemongo</groupId>
        <artifactId>fongo</artifactId>
        <version>2.0.4</version>
<scope>test</scope>
    </dependency>

Example

Now just inject an instance of Fongo anywhere that a MongoClient would have been used.

    final Fongo fakeMongo = new Fongo("FakeMongo");
    final MongoClient mongoClient = fakeMongo.getMongo();

Now just interact with the mongoClient as normal.  Add documents, delete, read etc

XML to Json

Converting XML to JSON without knowing the xsd schema is a really useful trick for storing data into mongo db.  I'm commenting this because although it is easy I don't want to forget!

Maven Dependency

    <dependency>
        <groupId>org.json</groupId>
        <artifactId>json</artifactId>
        <version>20151123</version>
    </dependency>

Conversion

    String data; // Xml as a string

    final JSONObject jsonObject = XML.toJSONObject(data);
    doc.put("data", BsonDocument.parse(jsonObject.toString()));
    doc.put("rawData", data);









Mongo & Java

Doing a search with Mongo using the Java driver is very simple.  Firstly use the mongo maven dependency in your pom.

Maven Dependency


    <dependency>
        <groupId>org.mongodb</groupId>
        <artifactId>mongodb-driver</artifactId>
        <version>3.2.0</version>
    </dependency>


MongoClient

Then you need a MongoClient.  This can just be created or you can inject it with spring.

    final MongoClient mongoClient = new MongoClient(new ServerAddress(hostname, port));

or using spring....

    <bean id="mongoClient" class="com.mongodb.MongoClient">
        <constructor-arg>
            <util:list id="mongoServerAddresses" value-type="com.mongodb.ServerAddress">
                <bean class="com.mongodb.ServerAddress">
                    <constructor-arg value="localhost" />
                    <constructor-arg value="27017" />
                </bean>
                <bean class="com.mongodb.ServerAddress">
                    <constructor-arg value="localhost" />
                    <constructor-arg value="27018" />
                </bean>
            </util:list>
        </constructor-arg>
    </bean>

Writing

Create a Document object and populate it,

    // Create the document to store in mongo.
    final Document doc = new Document();
    doc.put("timestamp", now.getTime());
    doc.put("data", BsonDocument.parse(data.toString()));

Write the document to the database


    try
    {
        final MongoCollection<Document> mongoCollection = mongoClient.getDatabase(databaseName).getCollection(collection);
        mongoCollection.insertOne(doc);
    }
    catch (final Exception e)
    {
        LOG.error("Error writing to mongodb", e);
    }

Reading

    final MongoCollection mongoCollection = mongoClient.getDatabase(database).getCollection(collection);

    // Create a document to do the search with
    Bson bson = Filters.and(Filters.gte("timestamp", fromTime), Filters.lte("timestamp", toTime));
    bson = Filters.and(bson, Filters.eq("name", "fred")); 

    // Do the find request against the database.
    final FindIterable<Document> find = mongoCollection.find(bson);
    final MongoCursor<Document> cursor = find.iterator();

    // The results
    final List<Result> results = new ArrayList<>();

    // Loop through the results and put them into the match values.
    while (cursor.hasNext())
    {
        final Document doc = cursor.next();
        final Result result = new Result();
        result.setTimestamp(doc.getLong("timestamp"));
        result.setDob(doc.getLong("dob"));
        result.setSurname(doc.getString("surname"));
        results.add(result);
    }

Alternatively you can just return all the values in the document by doing


    // Loop through the results and put them into the match values.
    while (cursor.hasNext())
    {
        final Document doc = cursor.next();
        final Result result = new Result();
        result.setTimestamp(doc.getLong("timestamp"));
        for (final Entry<String, Object> entry : doc.entrySet())
        {
            result.add(entry.getKey(), entry.getValue().toString());
        }
    }





Monday 1 February 2016

JodaTime, XSD, XJC

For Java 1.7 and earlier JodaTime (http://www.joda.org/joda-time/) is a great replacement for the standard XML / XSD date time object that Java creates when using XJC.

From Java 1.8 onwards the date time support in Java is much better and is basically a JodaTime implementation (JSR310).

Specific Bindings

To use JodaTime create a bindings file which contains an XPath location to the correct element, such as,


    <jxb:bindings namespace="http://www.me.com/my/message" schemaLocation="my-message.xsd">
        <jxb:schemaBindings>
            <jxb:package name="com.me.my.message.generated" />
        </jxb:schemaBindings>
        
        <jxb:bindings node="//xs:complexType[@name='Message']/xs:sequence/xs:element[@name='PublishDateTime']">
            <xjc:javaType name="org.joda.time.DateTime" adapter="com.me.my.util.JodaDateTimeAdaptor" />    
        </jxb:bindings>
    </jxb:bindings>

Adaptor

The JodaDateTimeAdaptor will do the actual conversion and can be reused.

public class JodaDateTimeAdaptor
    extends XmlAdapter<String, DateTime>
{
    /**
     * Convert a DateTime to String.
     *
     * @param datetime DateTime to be converted.
     * @return The value of datetime formatted as per ISO8601, or the empty string if date is null.
     */
    @Override
    public String marshal(final DateTime datetime)
    {
        return datetime.toString();
    }

    /**
     * Convert a String representation of a date into a Date object.
     *
     * @param str String representation of a date/time, in the ISO8601. May be null or empty.
     * @return str converted to a Date object. If str is null or empty, null is returned.
     */
    @Override
    public DateTime unmarshal(final String str)
    {
        if (str == null || str.length() == 0)
        {
            return null;
        }
        return DateTime.parse(str);
    }
}

Global Bindings

Global bindings can also be used but these don't work when one XSD imports another and you want to use an adaptor on both.  You end up with a class name clash because the automatically generated Adaptors appear in a 

    org.w3._2001.xmlschema.Adaptor1.java
    org.w3._2001.xmlschema.Adaptor2.java
    ...

However, for a single xsd or single project this may work.  To use the global bindings instead of the specific ones use


    <!-- generateName allows the typed enums -->
    <jxb:globalBindings typesafeEnumMemberName="generateName" >
        <jxb:serializable uid="1" />
        <!-- use JODA-Time DateTime for xs:date -->
        <jxb:javaType name="org.joda.time.DateTime" xmlType="xs:date"
            parseMethod="com.me.my.util.JodaDateAdaptor.unmarshal"
            printMethod="com.me.my.util.JodaDateAdaptor.marshal"/>
        <jxb:javaType name="org.joda.time.DateTime" xmlType="xs:dateTime"
            parseMethod="com.me.my.util.JodaDateTimeAdaptor.unmarshal"
            printMethod="com.me.my.util.JodaDateTimeAdaptor.marshal"/>
    </jxb:globalBindings>