Q:

Spring data mongodb simulate ifNull behavior

I have a collection of forms:
{ "_id" : { "$oid" : "57050f22d5f79d2e6866c6f8" }, "form_name" : "Form 1", "ver" : 0, "createdAt" : { "$date" : 1459950030037 } }
{ "_id" : { "$oid" : "57050fa6d5f79d2e6866c6f9" }, "form_name" : "Form 1", "ver" : 1, "vid" : { "$oid" : "57050f22d5f79d2e6866c6f8" }, "createdAt" : { "$date" : 1459950030037 } }
{ "_id" : { "$oid" : "57050ff2d5f79d2e6866c6fa" }, "form_name" : "Form 1", "ver" : 2, "vid" : { "$oid" : "57050f22d5f79d2e6866c6f8" }, "createdAt" : { "$date" : 1459950030037 } }
{ "_id" : { "$oid" : "570511ced5f79d2e6866c6fb" }, "form_name" : "Form 2", "ver" : 0, "createdAt" : { "$date" : 1459950030037 } }

I'm getting versioned forms as follows:
db.forms.aggregate([
    {"$match":{"deletedAt":{"$exists":false}}},
    {"$sort":{"createdAt":1}},
    {"$group":{
            "_id": {"$ifNull":["$vid", "$_id"]},
            "vid":{"$push":"$$ROOT"}
        }
    }
])

and I'm getting such JSON answer:
[
    {
        "_id" : ObjectId("57050f22d5f79d2e6866c6f8"),
        "vid" : [
            {
                "_id" : ObjectId("57050f22d5f79d2e6866c6f8"),
                "form_name" : "Form 1",
                "ver" : 0,
                "createdAt" : ISODate("2016-04-06T13:29:06.079Z")
            },
            {
                "_id" : ObjectId("57050fa6d5f79d2e6866c6f9"),
                "form_name" : "Form 1",
                "ver" : 1,
                "createdAt" : ISODate("2016-04-06T13:31:18.742Z"),
                "vid" : ObjectId("57050f22d5f79d2e6866c6f8")
            },
            {
                "_id" : ObjectId("57050ff2d5f79d2e6866c6fa"),
                "form_name" : "Form 1",
                "ver" : 2,
                "createdAt" : ISODate("2016-04-06T13:32:34.986Z"),
                "vid" : ObjectId("57050f22d5f79d2e6866c6f8")
            }
        ]
    },
    {
        "_id" : ObjectId("570511ced5f79d2e6866c6fb"),
        "vid" : [
            {
                "_id" : ObjectId("570511ced5f79d2e6866c6fb"),
                "form_name" : "Form 2",
                "ver" : 0,
                "createdAt" : ISODate("2016-04-06T13:40:30.037Z")
            }
        ]
    }
]

In my Java back end I'm doing it as follows:
List<DBObject> tmpAggr = new ArrayList<>();
tmpAggr.add(new BasicDBObject("$match", new BasicDBObject("deletedAt", new BasicDBObject("$exists", false))));
tmpAggr.add(new BasicDBObject("$sort",  new BasicDBObject("createdAt", 1)));
tmpAggr.add(new BasicDBObject("$group", BasicDBObjectBuilder.start("_id", new BasicDBObject("$ifNull", new String[]{"$vid", "$_id"}))
                                                        .append("vid", new BasicDBObject("$push", "$$ROOT")).get()));
tmpAggr.add(new BasicDBObject("$sort",  new BasicDBObject("_id", 1)));
        list_with_versions = Collections.unmodifiableList(tmpAggr);  
org.getDB().getCollection("forms").aggregate(list_with_versions).results()

How to reproduce this behavior with Spring Data MongoDB framework?
The problem is that there is no ifNull construction. How to manage this issue?

A:

You could create a workaround that implements the AggregationOperation interface to take in a DBObject that represents a single operation in an aggregation pipeline with the $ifNull operator:
public class GroupAggregationOperation implements AggregationOperation {
    private DBObject operation;

    public GroupAggregationOperation (DBObject operation) {
        this.operation = operation;
    }

    @Override
    public DBObject toDBObject(AggregationOperationContext context) {
        return context.getMappedObject(operation);
    }
}

Then implement the $group operation as a DBObject in the aggregation pipeline that is the same as the one you have:
DBObject operation = (DBObject) new BasicDBObject(
    "$group", BasicDBObjectBuilder.start(
        "_id", new BasicDBObject(
            "$ifNull", new String[]{"$vid", "$_id"}
        )
    )
    .append("vid", new BasicDBObject("$push", "$$ROOT"))
    .get()              
);

which you can then use as:
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

GroupAggregationOperation groupOp = new GroupAggregationOperation(operation);
Aggregation agg = newAggregation(
    match(Criteria.where("deletedAt").exists(false)),
    sort(ASC, "deletedAt"),
    groupOp 
);
AggregationResults<Forms> results = mongoTemplate.aggregate(agg, Forms.class); 
List<Forms> forms = results.getMappedResults();

