@SuppressWarnings("unchecked")
    @Override
    public Object visit(Operation<?> expr, Void context) {
        Operator op = expr.getOperator();
        if (op == Ops.EQ) {
            if (expr.getArg(0) instanceof Operation) {
                Operation<?> lhs = (Operation<?>) expr.getArg(0);
                if (lhs.getOperator() == Ops.COL_SIZE || lhs.getOperator() == Ops.ARRAY_SIZE) {
                    return asDBObject(asDBKey(lhs, 0), asDBObject("$size", asDBValue(expr, 1)));
                } else {
                    throw new UnsupportedOperationException("Illegal operation " + expr);
                }
            } else if (expr.getArg(0) instanceof Path) {
                Path<?> path = (Path<?>) expr.getArg(0);
                Constant<?> constant = (Constant<?>) expr.getArg(1);
                return asDBObject(asDBKey(expr, 0), convert(path, constant));
            }
        } else if (op == Ops.STRING_IS_EMPTY) {
            return asDBObject(asDBKey(expr, 0), "");

        } else if (op == Ops.AND) {
            BSONObject lhs = (BSONObject) handle(expr.getArg(0));
            BSONObject rhs = (BSONObject) handle(expr.getArg(1));
            if (lhs.keySet().stream().noneMatch(rhs.keySet()::contains)) {
                lhs.putAll(rhs);
                return lhs;
            } else {
                BasicDBList list = new BasicDBList();
                list.add(handle(expr.getArg(0)));
                list.add(handle(expr.getArg(1)));
                return asDBObject("$and", list);
            }

        } else if (op == Ops.NOT) {
            //Handle the not's child
            Operation<?> subOperation = (Operation<?>) expr.getArg(0);
            Operator subOp = subOperation.getOperator();
            if (subOp == Ops.IN) {
                return visit(ExpressionUtils.operation(Boolean.class, Ops.NOT_IN, subOperation.getArg(0),
                        subOperation.getArg(1)), context);
            } else {
                BasicDBObject arg = (BasicDBObject) handle(expr.getArg(0));
                return negate(arg);
            }

        } else if (op == Ops.OR) {
            BasicDBList list = new BasicDBList();
            list.add(handle(expr.getArg(0)));
            list.add(handle(expr.getArg(1)));
            return asDBObject("$or", list);

        } else if (op == Ops.NE) {
            Path<?> path = (Path<?>) expr.getArg(0);
            Constant<?> constant = (Constant<?>) expr.getArg(1);
            return asDBObject(asDBKey(expr, 0), asDBObject("$ne", convert(path, constant)));

        } else if (op == Ops.STARTS_WITH) {
            return asDBObject(asDBKey(expr, 0),
                    Pattern.compile("^" + regexValue(expr, 1)));

        } else if (op == Ops.STARTS_WITH_IC) {
            return asDBObject(asDBKey(expr, 0),
                    Pattern.compile("^" + regexValue(expr, 1), Pattern.CASE_INSENSITIVE));

        } else if (op == Ops.ENDS_WITH) {
            return asDBObject(asDBKey(expr, 0), Pattern.compile(regexValue(expr, 1) + "$"));

        } else if (op == Ops.ENDS_WITH_IC) {
            return asDBObject(asDBKey(expr, 0),
                    Pattern.compile(regexValue(expr, 1) + "$", Pattern.CASE_INSENSITIVE));

        } else if (op == Ops.EQ_IGNORE_CASE) {
            return asDBObject(asDBKey(expr, 0),
                    Pattern.compile("^" + regexValue(expr, 1) + "$", Pattern.CASE_INSENSITIVE));

        } else if (op == Ops.STRING_CONTAINS) {
            return asDBObject(asDBKey(expr, 0), Pattern.compile(".*" + regexValue(expr, 1) + ".*"));

        } else if (op == Ops.STRING_CONTAINS_IC) {
            return asDBObject(asDBKey(expr, 0),
                    Pattern.compile(".*" + regexValue(expr, 1) + ".*", Pattern.CASE_INSENSITIVE));

        } else if (op == Ops.MATCHES) {
            return asDBObject(asDBKey(expr, 0), Pattern.compile(asDBValue(expr, 1).toString()));

        } else if (op == Ops.MATCHES_IC) {
            return asDBObject(asDBKey(expr, 0), Pattern.compile(asDBValue(expr, 1).toString(), Pattern.CASE_INSENSITIVE));

        } else if (op == Ops.LIKE) {
            String regex = ExpressionUtils.likeToRegex((Expression) expr.getArg(1)).toString();
            return asDBObject(asDBKey(expr, 0), Pattern.compile(regex));

        } else if (op == Ops.LIKE_IC) {
            String regex = ExpressionUtils.likeToRegex((Expression) expr.getArg(1)).toString();
            return asDBObject(asDBKey(expr, 0), Pattern.compile(regex, Pattern.CASE_INSENSITIVE));

        } else if (op == Ops.BETWEEN) {
            BasicDBObject value = new BasicDBObject("$gte", asDBValue(expr, 1));
            value.append("$lte", asDBValue(expr, 2));
            return asDBObject(asDBKey(expr, 0), value);

        } else if (op == Ops.IN) {
            int constIndex = 0;
            int exprIndex = 1;
            if (expr.getArg(1) instanceof Constant<?>) {
                constIndex = 1;
                exprIndex = 0;
            }
            if (Collection.class.isAssignableFrom(expr.getArg(constIndex).getType())) {
                @SuppressWarnings("unchecked") //guarded by previous check
                Collection<?> values = ((Constant<? extends Collection<?>>) expr.getArg(constIndex)).getConstant();
                return asDBObject(asDBKey(expr, exprIndex), asDBObject("$in", values.toArray()));
            } else {
                Path<?> path = (Path<?>) expr.getArg(exprIndex);
                Constant<?> constant = (Constant<?>) expr.getArg(constIndex);
                return asDBObject(asDBKey(expr, exprIndex), convert(path, constant));
            }

        } else if (op == Ops.NOT_IN) {
            int constIndex = 0;
            int exprIndex = 1;
            if (expr.getArg(1) instanceof Constant<?>) {
                constIndex = 1;
                exprIndex = 0;
            }
            if (Collection.class.isAssignableFrom(expr.getArg(constIndex).getType())) {
                @SuppressWarnings("unchecked") //guarded by previous check
                Collection<?> values = ((Constant<? extends Collection<?>>) expr.getArg(constIndex)).getConstant();
                return asDBObject(asDBKey(expr, exprIndex), asDBObject("$nin", values.toArray()));
            } else {
                Path<?> path = (Path<?>) expr.getArg(exprIndex);
                Constant<?> constant = (Constant<?>) expr.getArg(constIndex);
                return asDBObject(asDBKey(expr, exprIndex), asDBObject("$ne", convert(path, constant)));
            }

        } else if (op == Ops.COL_IS_EMPTY) {
            BasicDBList list = new BasicDBList();
            list.add(asDBObject(asDBKey(expr, 0), new BasicDBList()));
            list.add(asDBObject(asDBKey(expr, 0), asDBObject("$exists", false)));
            return asDBObject("$or", list);

        } else if (op == Ops.LT) {
            return asDBObject(asDBKey(expr, 0), asDBObject("$lt", asDBValue(expr, 1)));

        } else if (op == Ops.GT) {
            return asDBObject(asDBKey(expr, 0), asDBObject("$gt", asDBValue(expr, 1)));

        } else if (op == Ops.LOE) {
            return asDBObject(asDBKey(expr, 0), asDBObject("$lte", asDBValue(expr, 1)));

        } else if (op == Ops.GOE) {
            return asDBObject(asDBKey(expr, 0), asDBObject("$gte", asDBValue(expr, 1)));

        } else if (op == Ops.IS_NULL) {
            return asDBObject(asDBKey(expr, 0), asDBObject("$exists", false));

        } else if (op == Ops.IS_NOT_NULL) {
            return asDBObject(asDBKey(expr, 0), asDBObject("$exists", true));

        } else if (op == Ops.CONTAINS_KEY) {
            Path<?> path = (Path<?>) expr.getArg(0);
            Expression<?> key = expr.getArg(1);
            return asDBObject(visit(path, context) + "." + key.toString(), asDBObject("$exists", true));

        } else if (op == MongodbOps.NEAR) {
            return asDBObject(asDBKey(expr, 0), asDBObject("$near", asDBValue(expr, 1)));

        } else if (op == MongodbOps.GEO_WITHIN_BOX) {
            return asDBObject(asDBKey(expr, 0), asDBObject(
                    "$geoWithin",
                    asDBObject("$box", Arrays.asList(asDBValue(expr, 1), asDBValue(expr, 2)))
            ));

        } else if (op == MongodbOps.NEAR_SPHERE) {
            return asDBObject(asDBKey(expr, 0), asDBObject("$nearSphere", asDBValue(expr, 1)));

        } else if (op == MongodbOps.GEO_INTERSECTS) {
            return asDBObject(asDBKey(expr, 0), asDBObject(
                    "$geoIntersects",
                    asDBObject("$geometry", asDBValue(expr, 1))
            ));
        } else if (op == MongodbOps.ALL) {
            return asDBObject(asDBKey(expr, 0), asDBObject("$all", asDBValue(expr, 1)));
        } else if (op == MongodbOps.ELEM_MATCH) {
            return asDBObject(asDBKey(expr, 0), asDBObject("$elemMatch", asDBValue(expr, 1)));
        }

        throw new UnsupportedOperationException("Illegal operation " + expr);
    }
Serializes the given Querydsl query to a DBObject query for MongoDB

@author laimw
@author sangyong choi
querydsl-mongodb/src/main/java/com/querydsl/mongodb/MongodbSerializer.java