/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.usergrid.mongo.query; import java.util.Stack; import org.antlr.runtime.ClassicToken; import org.bson.BSONObject; import org.bson.BasicBSONObject; import org.bson.types.BasicBSONList; import org.apache.usergrid.persistence.index.query.Query; import org.apache.usergrid.persistence.index.query.Query.SortDirection; import org.apache.usergrid.persistence.index.query.tree.AndOperand; import org.apache.usergrid.persistence.index.query.tree.Equal; import org.apache.usergrid.persistence.index.query.tree.GreaterThan; import org.apache.usergrid.persistence.index.query.tree.GreaterThanEqual; import org.apache.usergrid.persistence.index.query.tree.LessThan; import org.apache.usergrid.persistence.index.query.tree.LessThanEqual; import org.apache.usergrid.persistence.index.query.tree.Operand; import org.apache.usergrid.persistence.index.query.tree.OrOperand; import static org.apache.commons.collections.MapUtils.getIntValue; /** * Parser class to parse mongo queries into usergrid EM queries * * @author tnine */ public class MongoQueryParser { /** * Convert the bson object query to a native usergrid query * * @return The query */ public static Query toNativeQuery( BSONObject query, int numberToReturn ) { return toNativeQuery( query, null, numberToReturn ); } /** * Overloaded form which takes a FieldSelector as the second query argument * * @return The query */ public static Query toNativeQuery( BSONObject query, BSONObject fieldSelector, int numberToReturn ) { // TODO overload? or add? if ( query == null ) { return null; } BasicBSONObject query_expression = null; BasicBSONObject field_selector = null; BasicBSONObject sort_order = null; Object o = query.get( "$query" ); if ( !( o instanceof BasicBSONObject ) ) { o = query.get( "query" ); } if ( o instanceof BasicBSONObject ) { query_expression = ( BasicBSONObject ) o; } o = query.get( "$orderby" ); if ( !( o instanceof BasicBSONObject ) ) { o = query.get( "orderby" ); } if ( o instanceof BasicBSONObject ) { sort_order = ( BasicBSONObject ) o; } if ( ( query_expression == null ) && ( query instanceof BasicBSONObject ) ) { query_expression = ( BasicBSONObject ) query; query_expression.removeField( "$orderby" ); query_expression.removeField( "$max" ); query_expression.removeField( "$min" ); } if ( ( query_expression == null ) && ( sort_order == null ) ) { return null; } if ( query_expression.size() == 0 && sort_order != null ) { if ( sort_order.size() == 0 ) { return null; } if ( ( sort_order.size() == 1 ) && sort_order.containsField( "_id" ) ) { return null; } } Query q = new Query(); if ( numberToReturn > 0 ) { q.setLimit( numberToReturn ); } if ( query_expression != null ) { Operand root = eval( query_expression ); q.setRootOperand( root ); } if ( fieldSelector != null ) { for ( String field : fieldSelector.keySet() ) { q.addSelect( field, field ); } } if ( sort_order != null ) { for ( String sort : sort_order.keySet() ) { if ( !"_id".equals( sort ) ) { int s = getIntValue( sort_order.toMap(), "_id", 1 ); q.addSort( sort, s >= 0 ? SortDirection.ASCENDING : SortDirection.DESCENDING ); } } } return q; } /** Evaluate an expression part */ private static Operand eval( BSONObject exp ) { Operand current = null; Object fieldValue = null; for ( String field : exp.keySet() ) { fieldValue = exp.get( field ); if ( field.startsWith( "$" ) ) { // same as OR with multiple values // same as OR with multiple values if ( "$or".equals( field ) ) { BasicBSONList values = ( BasicBSONList ) fieldValue; int size = values.size(); Stack<Operand> expressions = new Stack<Operand>(); for (Object value : values) { expressions.push(eval((BSONObject) value)); } // we need to build a tree of expressions while ( expressions.size() > 1 ) { OrOperand or = new OrOperand(); or.addChild( expressions.pop() ); or.addChild( expressions.pop() ); expressions.push( or ); } current = expressions.pop(); } else if ( "$and".equals( field ) ) { BasicBSONList values = ( BasicBSONList ) fieldValue; int size = values.size(); Stack<Operand> expressions = new Stack<Operand>(); for (Object value : values) { expressions.push(eval((BSONObject) value)); } while ( expressions.size() > 1 ) { AndOperand and = new AndOperand(); and.addChild( expressions.pop() ); and.addChild( expressions.pop() ); expressions.push( and ); } current = expressions.pop(); } } // we have a nested object else if ( fieldValue instanceof BSONObject ) { current = handleOperand( field, ( BSONObject ) fieldValue ); } else if ( !field.equals( "_id" ) ) { Equal equality = new Equal( new ClassicToken( 0, "=" ) ); equality.setProperty( field ); equality.setLiteral( exp.get( field ) ); current = equality; } } return current; } /** Handle an operand */ private static Operand handleOperand( String sourceField, BSONObject exp ) { Operand current = null; Object value = null; for ( String field : exp.keySet() ) { if ( field.startsWith( "$" ) ) { if ( "$gt".equals( field ) ) { value = exp.get( field ); GreaterThan gt = new GreaterThan(); gt.setProperty( sourceField ); gt.setLiteral( value ); current = gt; } else if ( "$gte".equals( field ) ) { value = exp.get( field ); GreaterThanEqual gte = new GreaterThanEqual(); gte.setProperty( sourceField ); gte.setLiteral( exp.get( field ) ); current = gte; // http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%3C%2C%3C%3D%2C%3E%2C%3E%3D // greater than equals // { "field" : { $gte: value } } } else if ( "$lt".equals( field ) ) { value = exp.get( field ); LessThan lt = new LessThan(); lt.setProperty( sourceField ); lt.setLiteral( value ); current = lt; } else if ( "$lte".equals( field ) ) { value = exp.get( field ); LessThanEqual lte = new LessThanEqual(); lte.setProperty( sourceField ); lte.setLiteral( value ); current = lte; } else if ( "$in".equals( field ) ) { value = exp.get( field ); BasicBSONList values = ( BasicBSONList ) value; int size = values.size(); Stack<Operand> expressions = new Stack<Operand>(); for (Object value1 : values) { Equal equal = new Equal(); equal.setProperty(sourceField); equal.setLiteral(value1); expressions.push(equal); } // we need to build a tree of expressions while ( expressions.size() > 1 ) { OrOperand or = new OrOperand(); or.addChild( expressions.pop() ); or.addChild( expressions.pop() ); expressions.push( or ); } current = expressions.pop(); } } } return current; } }