You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@usergrid.apache.org by sn...@apache.org on 2014/01/28 23:21:51 UTC

[41/96] [abbrv] [partial] Change package namespace to org.apache.usergrid

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2c2acbe4/stack/core/src/main/java/org/apache/usergrid/persistence/Query.java
----------------------------------------------------------------------
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/Query.java b/stack/core/src/main/java/org/apache/usergrid/persistence/Query.java
new file mode 100644
index 0000000..65c1240
--- /dev/null
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/Query.java
@@ -0,0 +1,1309 @@
+/*******************************************************************************
+ * Copyright 2012 Apigee Corporation
+ *
+ * Licensed 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.persistence;
+
+
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.UUID;
+
+import org.antlr.runtime.ANTLRStringStream;
+import org.antlr.runtime.ClassicToken;
+import org.antlr.runtime.CommonTokenStream;
+import org.antlr.runtime.RecognitionException;
+import org.antlr.runtime.Token;
+import org.antlr.runtime.TokenRewriteStream;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.commons.lang.StringUtils;
+import org.apache.usergrid.persistence.Results.Level;
+import org.apache.usergrid.persistence.exceptions.QueryParseException;
+import org.apache.usergrid.persistence.query.tree.AndOperand;
+import org.apache.usergrid.persistence.query.tree.ContainsOperand;
+import org.apache.usergrid.persistence.query.tree.Equal;
+import org.apache.usergrid.persistence.query.tree.EqualityOperand;
+import org.apache.usergrid.persistence.query.tree.GreaterThan;
+import org.apache.usergrid.persistence.query.tree.GreaterThanEqual;
+import org.apache.usergrid.persistence.query.tree.LessThan;
+import org.apache.usergrid.persistence.query.tree.LessThanEqual;
+import org.apache.usergrid.persistence.query.tree.Operand;
+import org.apache.usergrid.persistence.query.tree.QueryFilterLexer;
+import org.apache.usergrid.persistence.query.tree.QueryFilterParser;
+import org.apache.usergrid.utils.JsonUtils;
+
+import static org.apache.commons.codec.binary.Base64.decodeBase64;
+import static org.apache.commons.lang.StringUtils.isBlank;
+import static org.apache.commons.lang.StringUtils.split;
+import static org.apache.usergrid.persistence.Schema.PROPERTY_UUID;
+import static org.apache.usergrid.utils.ClassUtils.cast;
+import static org.apache.usergrid.utils.ConversionUtils.uuid;
+import static org.apache.usergrid.utils.ListUtils.first;
+import static org.apache.usergrid.utils.ListUtils.firstBoolean;
+import static org.apache.usergrid.utils.ListUtils.firstInteger;
+import static org.apache.usergrid.utils.ListUtils.firstLong;
+import static org.apache.usergrid.utils.ListUtils.firstUuid;
+import static org.apache.usergrid.utils.ListUtils.isEmpty;
+import static org.apache.usergrid.utils.MapUtils.toMapList;
+
+
+public class Query {
+
+    private static final Logger logger = LoggerFactory.getLogger( Query.class );
+
+    public static final int DEFAULT_LIMIT = 10;
+
+    public static final int MAX_LIMIT = 1000;
+
+    private String type;
+    private List<SortPredicate> sortPredicates = new ArrayList<SortPredicate>();
+    private Operand rootOperand;
+    private UUID startResult;
+    private String cursor;
+    private int limit = 0;
+
+    private Map<String, String> selectAssignments = new LinkedHashMap<String, String>();
+    private boolean mergeSelectResults = false;
+    private Level level = Level.ALL_PROPERTIES;
+    private String connection;
+    private List<String> permissions;
+    private boolean reversed;
+    private boolean reversedSet = false;
+    private Long startTime;
+    private Long finishTime;
+    private boolean pad;
+    private CounterResolution resolution = CounterResolution.ALL;
+    private List<Identifier> identifiers;
+    private List<CounterFilterPredicate> counterFilters;
+    private String collection;
+    private String ql;
+
+
+    public Query() {
+    }
+
+
+    public Query( Query q ) {
+        if ( q != null ) {
+            type = q.type;
+            sortPredicates = q.sortPredicates != null ? new ArrayList<SortPredicate>( q.sortPredicates ) : null;
+            startResult = q.startResult;
+            cursor = q.cursor;
+            limit = q.limit;
+            selectAssignments =
+                    q.selectAssignments != null ? new LinkedHashMap<String, String>( q.selectAssignments ) : null;
+            mergeSelectResults = q.mergeSelectResults;
+            level = q.level;
+            connection = q.connection;
+            permissions = q.permissions != null ? new ArrayList<String>( q.permissions ) : null;
+            reversed = q.reversed;
+            reversedSet = q.reversedSet;
+            startTime = q.startTime;
+            finishTime = q.finishTime;
+            resolution = q.resolution;
+            pad = q.pad;
+            rootOperand = q.rootOperand;
+            identifiers = q.identifiers != null ? new ArrayList<Identifier>( q.identifiers ) : null;
+            counterFilters =
+                    q.counterFilters != null ? new ArrayList<CounterFilterPredicate>( q.counterFilters ) : null;
+            collection = q.collection;
+        }
+    }
+
+
+    public static Query fromQL( String ql ) throws QueryParseException {
+        if ( ql == null ) {
+            return null;
+        }
+        String originalQl = ql;
+        ql = ql.trim();
+
+        String qlt = ql.toLowerCase();
+        if ( !qlt.startsWith( "select" ) && !qlt.startsWith( "insert" ) && !qlt.startsWith( "update" ) && !qlt
+                .startsWith( "delete" ) ) {
+            if ( qlt.startsWith( "order by" ) ) {
+                ql = "select * " + ql;
+            }
+            else {
+                ql = "select * where " + ql;
+            }
+        }
+
+        ANTLRStringStream in = new ANTLRStringStream( qlt.trim() );
+        QueryFilterLexer lexer = new QueryFilterLexer( in );
+        CommonTokenStream tokens = new CommonTokenStream( lexer );
+        QueryFilterParser parser = new QueryFilterParser( tokens );
+
+        try {
+            Query q = parser.ql().query;
+            q.setQl( originalQl );
+            return q;
+        }
+        catch ( RecognitionException e ) {
+            logger.error( "Unable to parse \"{}\"", ql, e );
+
+            int index = e.index;
+            int lineNumber = e.line;
+            Token token = e.token;
+
+            String message = String.format(
+                    "The query cannot be parsed. The token '%s' at column %d on line %d cannot be " + "parsed",
+                    token.getText(), index, lineNumber );
+
+            throw new QueryParseException( message, e );
+        }
+    }
+
+
+    private static Query newQueryIfNull( Query query ) {
+        if ( query == null ) {
+            query = new Query();
+        }
+        return query;
+    }
+
+
+    public static Query fromJsonString( String json ) throws QueryParseException {
+        Object o = JsonUtils.parse( json );
+        if ( o instanceof Map ) {
+            @SuppressWarnings({ "unchecked", "rawtypes" }) Map<String, List<String>> params =
+                    cast( toMapList( ( Map ) o ) );
+            return fromQueryParams( params );
+        }
+        return null;
+    }
+
+
+    public static Query fromQueryParams( Map<String, List<String>> params ) throws QueryParseException {
+        Query q = null;
+        CounterResolution resolution = null;
+        List<Identifier> identifiers = null;
+        List<CounterFilterPredicate> counterFilters = null;
+
+        String ql = QueryUtils.queryStrFrom( params );
+        String type = first( params.get( "type" ) );
+        Boolean reversed = firstBoolean( params.get( "reversed" ) );
+        String connection = first( params.get( "connection" ) );
+        UUID start = firstUuid( params.get( "start" ) );
+        String cursor = first( params.get( "cursor" ) );
+        Integer limit = firstInteger( params.get( "limit" ) );
+        List<String> permissions = params.get( "permission" );
+        Long startTime = firstLong( params.get( "start_time" ) );
+        Long finishTime = firstLong( params.get( "end_time" ) );
+
+        List<String> l = params.get( "resolution" );
+        if ( !isEmpty( l ) ) {
+            resolution = CounterResolution.fromString( l.get( 0 ) );
+        }
+
+        l = params.get( "counter" );
+
+        if ( !isEmpty( l ) ) {
+            counterFilters = CounterFilterPredicate.fromList( l );
+        }
+
+        Boolean pad = firstBoolean( params.get( "pad" ) );
+
+        for ( Entry<String, List<String>> param : params.entrySet() ) {
+            Identifier identifier = Identifier.from( param.getKey() );
+            if ( ( param.getValue() == null ) || ( param.getValue().size() == 0 ) || identifier.isUUID() ) {
+                if ( identifier != null ) {
+                    if ( identifiers == null ) {
+                        identifiers = new ArrayList<Identifier>();
+                    }
+                    identifiers.add( identifier );
+                }
+            }
+        }
+
+        if ( ql != null ) {
+            q = Query.fromQL( decode( ql ) );
+        }
+
+        l = params.get( "filter" );
+
+        if ( !isEmpty( l ) ) {
+            q = newQueryIfNull( q );
+            for ( String s : l ) {
+                q.addFilter( decode( s ) );
+            }
+        }
+
+        l = params.get( "sort" );
+        if ( !isEmpty( l ) ) {
+            q = newQueryIfNull( q );
+            for ( String s : l ) {
+                q.addSort( decode( s ) );
+            }
+        }
+
+        if ( type != null ) {
+            q = newQueryIfNull( q );
+            q.setEntityType( type );
+        }
+
+        if ( connection != null ) {
+            q = newQueryIfNull( q );
+            q.setConnectionType( connection );
+        }
+
+        if ( permissions != null ) {
+            q = newQueryIfNull( q );
+            q.setPermissions( permissions );
+        }
+
+        if ( start != null ) {
+            q = newQueryIfNull( q );
+            q.setStartResult( start );
+        }
+
+        if ( cursor != null ) {
+            q = newQueryIfNull( q );
+            q.setCursor( cursor );
+        }
+
+        if ( limit != null ) {
+            q = newQueryIfNull( q );
+            q.setLimit( limit );
+        }
+
+        if ( startTime != null ) {
+            q = newQueryIfNull( q );
+            q.setStartTime( startTime );
+        }
+
+        if ( finishTime != null ) {
+            q = newQueryIfNull( q );
+            q.setFinishTime( finishTime );
+        }
+
+        if ( resolution != null ) {
+            q = newQueryIfNull( q );
+            q.setResolution( resolution );
+        }
+
+        if ( counterFilters != null ) {
+            q = newQueryIfNull( q );
+            q.setCounterFilters( counterFilters );
+        }
+
+        if ( pad != null ) {
+            q = newQueryIfNull( q );
+            q.setPad( pad );
+        }
+
+        if ( identifiers != null ) {
+            q = newQueryIfNull( q );
+            q.setIdentifiers( identifiers );
+        }
+
+        if ( reversed != null ) {
+            q = newQueryIfNull( q );
+            q.setReversed( reversed );
+        }
+
+        return q;
+    }
+
+
+    public static Query searchForProperty( String propertyName, Object propertyValue ) {
+        Query q = new Query();
+        q.addEqualityFilter( propertyName, propertyValue );
+        return q;
+    }
+
+
+    public static Query findForProperty( String propertyName, Object propertyValue ) {
+        Query q = new Query();
+        q.addEqualityFilter( propertyName, propertyValue );
+        q.setLimit( 1 );
+        return q;
+    }
+
+
+    public static Query fromUUID( UUID uuid ) {
+        Query q = new Query();
+        q.addIdentifier( Identifier.fromUUID( uuid ) );
+        return q;
+    }
+
+
+    public static Query fromIdentifier( Object id ) {
+        Query q = new Query();
+        q.addIdentifier( Identifier.from( id ) );
+        return q;
+    }
+
+
+    public boolean hasQueryPredicates() {
+        return rootOperand != null;
+    }
+
+
+    public boolean containsNameOrEmailIdentifiersOnly() {
+        if ( hasQueryPredicates() ) {
+            return false;
+        }
+        if ( ( identifiers == null ) || identifiers.isEmpty() ) {
+            return false;
+        }
+        for ( Identifier identifier : identifiers ) {
+            if ( !identifier.isEmail() && !identifier.isName() ) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    @JsonIgnore
+    public String getSingleNameOrEmailIdentifier() {
+        if ( !containsSingleNameOrEmailIdentifier() ) {
+            return null;
+        }
+        return ( identifiers.get( 0 ).toString() );
+    }
+
+
+    public boolean containsSingleNameOrEmailIdentifier() {
+        return containsNameOrEmailIdentifiersOnly() && ( identifiers.size() == 1 );
+    }
+
+
+    @JsonIgnore
+    public Identifier getSingleIdentifier() {
+        return identifiers != null && identifiers.size() == 1 ? identifiers.get( 0 ) : null;
+    }
+
+
+    public boolean containsSingleUuidIdentifier() {
+        return containsUuidIdentifiersOnly() && ( identifiers.size() == 1 );
+    }
+
+
+    boolean containsUuidIdentifiersOnly() {
+        if ( hasQueryPredicates() ) {
+            return false;
+        }
+        if ( ( identifiers == null ) || identifiers.isEmpty() ) {
+            return false;
+        }
+
+        for ( Identifier identifier : identifiers ) {
+            if ( !identifier.isUUID() ) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    public Query addSort( SortPredicate sort ) {
+        if ( sort == null ) {
+            return this;
+        }
+
+        for ( SortPredicate s : sortPredicates ) {
+            if ( s.getPropertyName().equals( sort.getPropertyName() ) ) {
+                throw new QueryParseException(
+                        String.format( "Attempted to set sort order for %s more than once", s.getPropertyName() ) );
+            }
+        }
+        sortPredicates.add( sort );
+        return this;
+    }
+
+
+    @JsonIgnore
+    public UUID getSingleUuidIdentifier() {
+        if ( !containsSingleUuidIdentifier() ) {
+            return null;
+        }
+        return ( identifiers.get( 0 ).getUUID() );
+    }
+
+
+    @JsonIgnore
+    boolean isIdsOnly() {
+        if ( ( selectAssignments.size() == 1 ) && selectAssignments.containsKey( PROPERTY_UUID ) ) {
+            level = Level.IDS;
+            return true;
+        }
+        return false;
+    }
+
+
+    private void setIdsOnly( boolean idsOnly ) {
+        if ( idsOnly ) {
+            selectAssignments = new LinkedHashMap<String, String>();
+            selectAssignments.put( PROPERTY_UUID, PROPERTY_UUID );
+            level = Level.IDS;
+        }
+        else if ( isIdsOnly() ) {
+            selectAssignments = new LinkedHashMap<String, String>();
+            level = Level.ALL_PROPERTIES;
+        }
+    }
+
+
+    public Level getResultsLevel() {
+        isIdsOnly();
+        return level;
+    }
+
+
+    public void setResultsLevel( Level level ) {
+        setIdsOnly( level == Level.IDS );
+        this.level = level;
+    }
+
+
+    public Query withResultsLevel( Level level ) {
+        setIdsOnly( level == Level.IDS );
+        this.level = level;
+        return this;
+    }
+
+
+    public Query withReversed( boolean reversed ) {
+        setReversed( reversed );
+        return this;
+    }
+
+
+    public String getEntityType() {
+        return type;
+    }
+
+
+    public void setEntityType( String type ) {
+        this.type = type;
+    }
+
+
+    public String getConnectionType() {
+        return connection;
+    }
+
+
+    public void setConnectionType( String connection ) {
+        this.connection = connection;
+    }
+
+
+    public List<String> getPermissions() {
+        return permissions;
+    }
+
+
+    public void setPermissions( List<String> permissions ) {
+        this.permissions = permissions;
+    }
+
+
+    public Query addSelect( String select ) {
+
+        return addSelect( select, null );
+    }
+
+
+    public Query addSelect( String select, String output ) {
+        // be paranoid with the null checks because
+        // the query parser sometimes flakes out
+        if ( select == null ) {
+            return this;
+        }
+        select = select.trim();
+
+        if ( select.equals( "*" ) ) {
+            return this;
+        }
+
+        mergeSelectResults = StringUtils.isNotEmpty( output );
+
+        if ( output == null ) {
+            output = "";
+        }
+
+        selectAssignments.put( select, output );
+
+        return this;
+    }
+
+
+    public boolean hasSelectSubjects() {
+        return !selectAssignments.isEmpty();
+    }
+
+
+    @JsonIgnore
+    public Set<String> getSelectSubjects() {
+        return selectAssignments.keySet();
+    }
+
+
+    public Map<String, String> getSelectAssignments() {
+        return selectAssignments;
+    }
+
+
+    boolean isMergeSelectResults() {
+        return mergeSelectResults;
+    }
+
+
+    public Query addSort( String propertyName ) {
+        if ( isBlank( propertyName ) ) {
+            return this;
+        }
+        propertyName = propertyName.trim();
+        if ( propertyName.indexOf( ',' ) >= 0 ) {
+            String[] propertyNames = split( propertyName, ',' );
+            for ( String s : propertyNames ) {
+                addSort( s );
+            }
+            return this;
+        }
+
+        SortDirection direction = SortDirection.ASCENDING;
+        if ( propertyName.indexOf( ' ' ) >= 0 ) {
+            String[] parts = split( propertyName, ' ' );
+            if ( parts.length > 1 ) {
+                propertyName = parts[0];
+                direction = SortDirection.find( parts[1] );
+            }
+        }
+        else if ( propertyName.startsWith( "-" ) ) {
+            propertyName = propertyName.substring( 1 );
+            direction = SortDirection.DESCENDING;
+        }
+        else if ( propertyName.startsWith( "+" ) ) {
+            propertyName = propertyName.substring( 1 );
+            direction = SortDirection.ASCENDING;
+        }
+
+        return addSort( propertyName, direction );
+    }
+
+
+    public Query addSort( String propertyName, SortDirection direction ) {
+        if ( isBlank( propertyName ) ) {
+            return this;
+        }
+        propertyName = propertyName.trim();
+        for ( SortPredicate s : sortPredicates ) {
+            if ( s.getPropertyName().equals( propertyName ) ) {
+                logger.error(
+                        "Attempted to set sort order for " + s.getPropertyName() + " more than once, discarding..." );
+                return this;
+            }
+        }
+        sortPredicates.add( new SortPredicate( propertyName, direction ) );
+        return this;
+    }
+
+
+    @JsonIgnore
+    public boolean isSortSet() {
+        return !sortPredicates.isEmpty();
+    }
+
+
+    public List<SortPredicate> getSortPredicates() {
+        return sortPredicates;
+    }
+
+
+    public Query addFilter( String filter ) {
+        ANTLRStringStream in = new ANTLRStringStream( filter );
+        QueryFilterLexer lexer = new QueryFilterLexer( in );
+        TokenRewriteStream tokens = new TokenRewriteStream( lexer );
+        QueryFilterParser parser = new QueryFilterParser( tokens );
+
+        Operand root;
+
+        try {
+            root = parser.ql().query.getRootOperand();
+        }
+        catch ( RecognitionException e ) {
+            // todo: should we create a specific Exception for this? checked?
+            throw new RuntimeException( "Unknown operation: " + filter, e );
+        }
+
+        if ( root != null ) {
+            addClause( root );
+        }
+
+        return this;
+    }
+
+
+    /** Add a less than filter to this query. && with existing clauses */
+    public Query addLessThanFilter( String propName, Object value ) {
+        LessThan equality = new LessThan( null );
+
+        addClause( equality, propName, value );
+
+        return this;
+    }
+
+
+    /** Add a less than equal filter to this query. && with existing clauses */
+    public Query addLessThanEqualFilter( String propName, Object value ) {
+        LessThanEqual equality = new LessThanEqual( null );
+
+        addClause( equality, propName, value );
+
+        return this;
+    }
+
+
+    /** Add a equal filter to this query. && with existing clauses */
+    public Query addEqualityFilter( String propName, Object value ) {
+        Equal equality = new Equal( new ClassicToken( 0, "=" ) );
+
+        addClause( equality, propName, value );
+
+        return this;
+    }
+
+
+    /** Add a greater than equal filter to this query. && with existing clauses */
+    public Query addGreaterThanEqualFilter( String propName, Object value ) {
+        GreaterThanEqual equality = new GreaterThanEqual( null );
+
+        addClause( equality, propName, value );
+
+        return this;
+    }
+
+
+    /** Add a less than filter to this query. && with existing clauses */
+    public Query addGreaterThanFilter( String propName, Object value ) {
+        GreaterThan equality = new GreaterThan( null );
+
+        addClause( equality, propName, value );
+
+        return this;
+    }
+
+
+    public Query addContainsFilter( String propName, String keyword ) {
+        ContainsOperand equality = new ContainsOperand( new ClassicToken( 0, "contains" ) );
+
+        equality.setProperty( propName );
+        equality.setLiteral( keyword );
+
+        addClause( equality );
+
+        return this;
+    }
+
+
+    private void addClause( EqualityOperand equals, String propertyName, Object value ) {
+        equals.setProperty( propertyName );
+        equals.setLiteral( value );
+        addClause( equals );
+    }
+
+
+    private void addClause( Operand equals ) {
+
+        if ( rootOperand == null ) {
+            rootOperand = equals;
+            return;
+        }
+
+        AndOperand and = new AndOperand();
+        and.addChild( rootOperand );
+        and.addChild( equals );
+
+        // redirect the root to new && clause
+        rootOperand = and;
+    }
+
+
+    @JsonIgnore
+    public Operand getRootOperand() {
+        if ( rootOperand == null ) { // attempt deserialization
+            if ( ql != null ) {
+                try {
+                    Query q = Query.fromQL( ql );
+                    rootOperand = q.rootOperand;
+                }
+                catch ( QueryParseException e ) {
+                    logger.error( "error parsing sql for rootOperand", e ); // shouldn't happen
+                }
+            }
+        }
+        return rootOperand;
+    }
+
+
+    public void setRootOperand( Operand root ) {
+        this.rootOperand = root;
+    }
+
+
+    void setStartResult( UUID startResult ) {
+        this.startResult = startResult;
+    }
+
+
+    public Query withStartResult( UUID startResult ) {
+        this.startResult = startResult;
+        return this;
+    }
+
+
+    public UUID getStartResult() {
+        if ( ( startResult == null ) && ( cursor != null ) ) {
+            byte[] cursorBytes = decodeBase64( cursor );
+            if ( ( cursorBytes != null ) && ( cursorBytes.length == 16 ) ) {
+                startResult = uuid( cursorBytes );
+            }
+        }
+        return startResult;
+    }
+
+
+    public String getCursor() {
+        return cursor;
+    }
+
+
+    public void setCursor( String cursor ) {
+        this.cursor = cursor;
+    }
+
+
+    public Query withCursor( String cursor ) {
+        setCursor( cursor );
+        return this;
+    }
+
+
+    public int getLimit() {
+        return getLimit( DEFAULT_LIMIT );
+    }
+
+
+    public int getLimit( int defaultLimit ) {
+        if ( limit <= 0 ) {
+            if ( defaultLimit > 0 ) {
+                return defaultLimit;
+            }
+            else {
+                return DEFAULT_LIMIT;
+            }
+        }
+        return limit;
+    }
+
+
+    public void setLimit( int limit ) {
+
+        //      TODO tnine.  After users have had time to change their query limits,
+        // this needs to be uncommented and enforced.
+        //        if(limit > MAX_LIMIT){
+        //          throw new IllegalArgumentException(String.format("Query limit must be <= to %d", MAX_LIMIT));
+        //        }
+
+        if ( limit > MAX_LIMIT ) {
+            limit = MAX_LIMIT;
+        }
+
+        this.limit = limit;
+    }
+
+
+    public Query withLimit( int limit ) {
+        setLimit( limit );
+        return this;
+    }
+
+
+    public boolean isReversed() {
+        return reversed;
+    }
+
+
+    public void setReversed( boolean reversed ) {
+        reversedSet = true;
+        this.reversed = reversed;
+    }
+
+
+    public boolean isReversedSet() {
+        return reversedSet;
+    }
+
+
+    public Long getStartTime() {
+        return startTime;
+    }
+
+
+    public void setStartTime( Long startTime ) {
+        this.startTime = startTime;
+    }
+
+
+    public Long getFinishTime() {
+        return finishTime;
+    }
+
+
+    public void setFinishTime( Long finishTime ) {
+        this.finishTime = finishTime;
+    }
+
+
+    public boolean isPad() {
+        return pad;
+    }
+
+
+    public void setPad( boolean pad ) {
+        this.pad = pad;
+    }
+
+
+    public void setResolution( CounterResolution resolution ) {
+        this.resolution = resolution;
+    }
+
+
+    public CounterResolution getResolution() {
+        return resolution;
+    }
+
+
+    public void addIdentifier( Identifier identifier ) {
+        if ( identifiers == null ) {
+            identifiers = new ArrayList<Identifier>();
+        }
+        identifiers.add( identifier );
+    }
+
+
+    void setIdentifiers( List<Identifier> identifiers ) {
+        this.identifiers = identifiers;
+    }
+
+
+    public List<CounterFilterPredicate> getCounterFilters() {
+        return counterFilters;
+    }
+
+
+    public void addCounterFilter( String counter ) {
+        CounterFilterPredicate p = CounterFilterPredicate.fromString( counter );
+        if ( p == null ) {
+            return;
+        }
+        if ( counterFilters == null ) {
+            counterFilters = new ArrayList<CounterFilterPredicate>();
+        }
+        counterFilters.add( p );
+    }
+
+
+    void setCounterFilters( List<CounterFilterPredicate> counterFilters ) {
+        this.counterFilters = counterFilters;
+    }
+
+
+    @Override
+    public String toString() {
+        if ( ql != null ) {
+            return ql;
+        }
+        StringBuilder s = new StringBuilder( "select " );
+        if ( selectAssignments.isEmpty() ) {
+            s.append( "*" );
+        }
+        else {
+            if ( mergeSelectResults ) {
+                s.append( "{ " );
+                boolean first = true;
+                for ( Map.Entry<String, String> select : selectAssignments.entrySet() ) {
+                    if ( !first ) {
+                        s.append( ", " );
+                    }
+                    s.append( select.getValue() ).append( " : " ).append( select.getKey() );
+                    first = false;
+                }
+                s.append( " }" );
+            }
+            else {
+                boolean first = true;
+                for ( String select : selectAssignments.keySet() ) {
+                    if ( !first ) {
+                        s.append( ", " );
+                    }
+                    s.append( select );
+                    first = false;
+                }
+            }
+        }
+        s.append( " from " );
+        s.append( type );
+        if ( !sortPredicates.isEmpty() ) {
+            boolean first = true;
+            s.append( " order by " );
+            for ( SortPredicate sp : sortPredicates ) {
+                if ( !first ) {
+                    s.append( ", " );
+                }
+                s.append( sp );
+                first = false;
+            }
+        }
+        //      if (!filterPredicates.isEmpty()) {
+        //        s.append(" where ");
+        //        boolean first = true;
+        //        for (FilterPredicate f : filterPredicates) {
+        //          if (!first) {
+        //            s.append(" and ");
+        //          }
+        //          s.append(f.toString());
+        //          first = false;
+        //        }
+        //      }
+        return s.toString();
+    }
+
+
+    public static enum SortDirection {
+        ASCENDING, DESCENDING;
+
+
+        public static SortDirection find( String s ) {
+            if ( s == null ) {
+                return ASCENDING;
+            }
+            s = s.toLowerCase();
+            if ( s.startsWith( "asc" ) ) {
+                return ASCENDING;
+            }
+            if ( s.startsWith( "des" ) ) {
+                return DESCENDING;
+            }
+            if ( s.equals( "+" ) ) {
+                return ASCENDING;
+            }
+            if ( s.equals( "-" ) ) {
+                return DESCENDING;
+            }
+            return ASCENDING;
+        }
+    }
+
+
+    public static final class SortPredicate implements Serializable {
+        private static final long serialVersionUID = 1L;
+        private final String propertyName;
+        private final Query.SortDirection direction;
+
+
+        public SortPredicate( String propertyName, Query.SortDirection direction ) {
+            if ( propertyName == null ) {
+                throw new NullPointerException( "Property name was null" );
+            }
+
+            if ( direction == null ) {
+                direction = SortDirection.ASCENDING;
+            }
+
+            this.propertyName = propertyName.trim();
+            this.direction = direction;
+        }
+
+
+        public SortPredicate( String propertyName, String direction ) {
+            this( propertyName, SortDirection.find( direction ) );
+        }
+
+
+        public String getPropertyName() {
+            return propertyName;
+        }
+
+
+        public Query.SortDirection getDirection() {
+            return direction;
+        }
+
+
+        @Override
+        public boolean equals( Object o ) {
+            if ( this == o ) {
+                return true;
+            }
+            if ( ( o == null ) || ( super.getClass() != o.getClass() ) ) {
+                return false;
+            }
+
+            SortPredicate that = ( SortPredicate ) o;
+
+            if ( direction != that.direction ) {
+                return false;
+            }
+
+            return ( propertyName.equals( that.propertyName ) );
+        }
+
+
+        @Override
+        public int hashCode() {
+            int result = propertyName.hashCode();
+            result = ( 31 * result ) + direction.hashCode();
+            return result;
+        }
+
+
+        @Override
+        public String toString() {
+            return propertyName + ( ( direction == Query.SortDirection.DESCENDING ) ? " DESC" : "" );
+        }
+    }
+
+
+    public static final class CounterFilterPredicate implements Serializable {
+
+        private static final long serialVersionUID = 1L;
+        private final String name;
+        private final Identifier user;
+        private final Identifier group;
+        private final String queue;
+        private final String category;
+
+
+        public CounterFilterPredicate( String name, Identifier user, Identifier group, String queue, String category ) {
+            this.name = name;
+            this.user = user;
+            this.group = group;
+            this.queue = queue;
+            this.category = category;
+        }
+
+
+        public Identifier getUser() {
+            return user;
+        }
+
+
+        public Identifier getGroup() {
+            return group;
+        }
+
+
+        public String getQueue() {
+            return queue;
+        }
+
+
+        public String getCategory() {
+            return category;
+        }
+
+
+        public String getName() {
+            return name;
+        }
+
+
+        public static CounterFilterPredicate fromString( String s ) {
+            Identifier user = null;
+            Identifier group = null;
+            String category = null;
+            String name = null;
+            String[] l = split( s, ':' );
+
+            if ( l.length > 0 ) {
+                if ( !"*".equals( l[0] ) ) {
+                    name = l[0];
+                }
+            }
+
+            if ( l.length > 1 ) {
+                if ( !"*".equals( l[1] ) ) {
+                    user = Identifier.from( l[1] );
+                }
+            }
+
+            if ( l.length > 2 ) {
+                if ( !"*".equals( l[2] ) ) {
+                    group = Identifier.from( l[3] );
+                }
+            }
+
+            if ( l.length > 3 ) {
+                if ( !"*".equals( l[3] ) ) {
+                    category = l[3];
+                }
+            }
+
+            if ( ( user == null ) && ( group == null ) && ( category == null ) && ( name == null ) ) {
+                return null;
+            }
+
+            return new CounterFilterPredicate( name, user, group, null, category );
+        }
+
+
+        public static List<CounterFilterPredicate> fromList( List<String> l ) {
+            if ( ( l == null ) || ( l.size() == 0 ) ) {
+                return null;
+            }
+            List<CounterFilterPredicate> counterFilters = new ArrayList<CounterFilterPredicate>();
+            for ( String s : l ) {
+                CounterFilterPredicate filter = CounterFilterPredicate.fromString( s );
+                if ( filter != null ) {
+                    counterFilters.add( filter );
+                }
+            }
+            if ( counterFilters.size() == 0 ) {
+                return null;
+            }
+            return counterFilters;
+        }
+    }
+
+
+    public List<Object> getSelectionResults( Results rs ) {
+
+        List<Entity> entities = rs.getEntities();
+        if ( entities == null ) {
+            return null;
+        }
+
+        if ( !hasSelectSubjects() ) {
+            return cast( entities );
+        }
+
+        List<Object> results = new ArrayList<Object>();
+
+        for ( Entity entity : entities ) {
+            if ( isMergeSelectResults() ) {
+                boolean include = false;
+                Map<String, Object> result = new LinkedHashMap<String, Object>();
+                Map<String, String> selects = getSelectAssignments();
+                for ( Map.Entry<String, String> select : selects.entrySet() ) {
+                    Object obj = JsonUtils.select( entity, select.getValue(), false );
+                    if ( obj != null ) {
+                        include = true;
+                    }
+                    result.put( select.getKey(), obj );
+                }
+                if ( include ) {
+                    results.add( result );
+                }
+            }
+            else {
+                boolean include = false;
+                List<Object> result = new ArrayList<Object>();
+                Set<String> selects = getSelectSubjects();
+                for ( String select : selects ) {
+                    Object obj = JsonUtils.select( entity, select );
+                    if ( obj != null ) {
+                        include = true;
+                    }
+                    result.add( obj );
+                }
+                if ( include ) {
+                    results.add( result );
+                }
+            }
+        }
+
+        if ( results.size() == 0 ) {
+            return null;
+        }
+
+        return results;
+    }
+
+
+    public Object getSelectionResult( Results rs ) {
+        List<Object> r = getSelectionResults( rs );
+        if ( ( r != null ) && ( r.size() > 0 ) ) {
+            return r.get( 0 );
+        }
+        return null;
+    }
+
+
+    private static String decode( String input ) {
+        try {
+            return URLDecoder.decode( input, "UTF-8" );
+        }
+        catch ( UnsupportedEncodingException e ) {
+            // shouldn't happen, but just in case
+            throw new RuntimeException( e );
+        }
+    }
+
+
+    // note: very likely to be null
+    public String getCollection() {
+        return collection;
+    }
+
+
+    public void setCollection( String collection ) {
+        this.collection = collection;
+    }
+
+
+    // may be null
+    public String getQl() {
+        return ql;
+    }
+
+
+    public void setQl( String ql ) {
+        this.ql = ql;
+    }
+
+
+    public List<Identifier> getIdentifiers() {
+        return identifiers;
+    }
+
+
+    public String getConnection() {
+        return connection;
+    }
+
+
+    public String getType() {
+        return type;
+    }
+
+
+    public Level getLevel() {
+        return level;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2c2acbe4/stack/core/src/main/java/org/apache/usergrid/persistence/QueryUtils.java
----------------------------------------------------------------------
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/QueryUtils.java b/stack/core/src/main/java/org/apache/usergrid/persistence/QueryUtils.java
new file mode 100644
index 0000000..cfc7aac
--- /dev/null
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/QueryUtils.java
@@ -0,0 +1,34 @@
+package org.apache.usergrid.persistence;
+
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.usergrid.utils.ListUtils;
+
+
+/**
+ * Utilities to deal with query extraction and generation
+ *
+ * @author zznate
+ */
+public class QueryUtils {
+
+    public static final String PARAM_QL = "ql";
+    public static final String PARAM_Q = "q";
+    public static final String PARAM_QUERY = "query";
+
+
+    public static String queryStrFrom( Map<String, List<String>> params ) {
+        if ( params.containsKey( PARAM_QL ) ) {
+            return ListUtils.first( params.get( PARAM_QL ) );
+        }
+        else if ( params.containsKey( PARAM_Q ) ) {
+            return ListUtils.first( params.get( PARAM_Q ) );
+        }
+        else if ( params.containsKey( PARAM_QUERY ) ) {
+            return ListUtils.first( params.get( PARAM_QUERY ) );
+        }
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2c2acbe4/stack/core/src/main/java/org/apache/usergrid/persistence/RelationManager.java
----------------------------------------------------------------------
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/RelationManager.java b/stack/core/src/main/java/org/apache/usergrid/persistence/RelationManager.java
new file mode 100644
index 0000000..70b68d5
--- /dev/null
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/RelationManager.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright 2012 Apigee Corporation
+ *
+ * Licensed 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.persistence;
+
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import org.apache.usergrid.persistence.Results.Level;
+
+
+public interface RelationManager {
+
+    public Set<String> getCollectionIndexes( String collectionName ) throws Exception;
+
+    public Map<String, Map<UUID, Set<String>>> getOwners() throws Exception;
+
+
+    /**
+     * Returns true if the entity ref if a member of the owner ref for the current relation manager
+     *
+     * @param collectionName The name of the collection
+     */
+    public boolean isCollectionMember( String collectionName, EntityRef entity ) throws Exception;
+
+    /** Returns true if the target entity is currently connected to the owner ref of this relation manager */
+    public boolean isConnectionMember( String connectionName, EntityRef entity ) throws Exception;
+
+    public Set<String> getCollections() throws Exception;
+
+    public Results getCollection( String collectionName, UUID startResult, int count, Results.Level resultsLevel,
+                                  boolean reversed ) throws Exception;
+
+    public Results getCollection( String collectionName, Query query, Results.Level resultsLevel ) throws Exception;
+
+    public Entity addToCollection( String collectionName, EntityRef itemRef ) throws Exception;
+
+    public Entity addToCollections( List<EntityRef> owners, String collectionName ) throws Exception;
+
+    public Entity createItemInCollection( String collectionName, String itemType, Map<String, Object> properties )
+            throws Exception;
+
+    public void removeFromCollection( String collectionName, EntityRef itemRef ) throws Exception;
+
+    public void copyRelationships( String srcRelationName, EntityRef dstEntityRef, String dstRelationName )
+            throws Exception;
+
+    public Results searchCollection( String collectionName, Query query ) throws Exception;
+
+    public ConnectionRef createConnection( ConnectionRef connection ) throws Exception;
+
+    public ConnectionRef createConnection( String connectionType, EntityRef connectedEntityRef ) throws Exception;
+
+    public ConnectionRef createConnection( String pairedConnectionType, EntityRef pairedEntity, String connectionType,
+                                           EntityRef connectedEntityRef ) throws Exception;
+
+    public ConnectionRef createConnection( ConnectedEntityRef... connections ) throws Exception;
+
+    public ConnectionRef connectionRef( String connectionType, EntityRef connectedEntityRef ) throws Exception;
+
+    public ConnectionRef connectionRef( String pairedConnectionType, EntityRef pairedEntity, String connectionType,
+                                        EntityRef connectedEntityRef ) throws Exception;
+
+    public ConnectionRef connectionRef( ConnectedEntityRef... connections );
+
+    public void deleteConnection( ConnectionRef connectionRef ) throws Exception;
+
+    public Set<String> getConnectionTypes( UUID connectedEntityId ) throws Exception;
+
+    public Set<String> getConnectionTypes() throws Exception;
+
+    public Set<String> getConnectionTypes( boolean filterConnection ) throws Exception;
+
+    /**
+     * Get all entities connected to this entity.  Also get all
+     *
+     * @param connectionType The type/name of the connection
+     * @param connectedEntityType The type of
+     */
+    public Results getConnectedEntities( String connectionType, String connectedEntityType, Results.Level resultsLevel )
+            throws Exception;
+
+    public Results getConnectingEntities( String connectionType, String connectedEntityType,
+                                          Results.Level resultsLevel ) throws Exception;
+
+    // public Results searchConnectedEntitiesForProperty(String connectionType,
+    // String connectedEntityType, String propertyName,
+    // Object searchStartValue, Object searchFinishValue,
+    // UUID startResult, int count, boolean reversed, Level resultsLevel)
+    // throws Exception;
+
+    public Results getConnectingEntities(String connectionType, String entityType, Level level, int count) throws Exception;
+
+	public Results searchConnectedEntities( Query query ) throws Exception;
+
+
+    public Set<String> getConnectionIndexes( String connectionType ) throws Exception;
+}

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2c2acbe4/stack/core/src/main/java/org/apache/usergrid/persistence/Results.java
----------------------------------------------------------------------
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/Results.java b/stack/core/src/main/java/org/apache/usergrid/persistence/Results.java
new file mode 100644
index 0000000..585666a
--- /dev/null
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/Results.java
@@ -0,0 +1,1294 @@
+/*******************************************************************************
+ * Copyright 2012 Apigee Corporation
+ *
+ * Licensed 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.persistence;
+
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.apache.usergrid.persistence.cassandra.QueryProcessor;
+import org.apache.usergrid.persistence.query.ir.SearchVisitor;
+import org.apache.usergrid.utils.MapUtils;
+import org.apache.usergrid.utils.StringUtils;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;
+
+import static org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString;
+import static org.apache.usergrid.persistence.SimpleEntityRef.ref;
+import static org.apache.usergrid.utils.ClassUtils.cast;
+import static org.apache.usergrid.utils.ConversionUtils.bytes;
+
+
+@XmlRootElement
+public class Results implements Iterable<Entity> {
+
+
+    public enum Level {
+        IDS, REFS, CORE_PROPERTIES, ALL_PROPERTIES, LINKED_PROPERTIES
+    }
+
+
+    Level level = Level.IDS;
+    UUID id;
+    List<UUID> ids;
+    Set<UUID> idSet;
+
+    EntityRef ref;
+    List<EntityRef> refs;
+    Map<UUID, EntityRef> refsMap;
+    Map<String, List<EntityRef>> refsByType;
+
+    Entity entity;
+    List<Entity> entities;
+    Map<UUID, Entity> entitiesMap;
+    Map<String, List<Entity>> entitiesByType;
+
+    List<ConnectionRef> connections;
+    boolean forwardConnections = true;
+
+    List<AggregateCounterSet> counters;
+
+    Set<String> types;
+
+    Map<UUID, Map<String, Object>> metadata;
+    boolean metadataMerged = true;
+
+    UUID nextResult;
+    String cursor;
+
+    Query query;
+    Object data;
+    String dataName;
+
+    private QueryProcessor queryProcessor;
+    private SearchVisitor searchVisitor;
+
+
+    public Results() {
+    }
+
+
+    public Results( Results r ) {
+        if ( r != null ) {
+            level = r.level;
+
+            id = r.id;
+            ids = r.ids;
+            idSet = r.idSet;
+
+            ref = r.ref;
+            refs = r.refs;
+            refsMap = r.refsMap;
+            refsByType = r.refsByType;
+
+            entity = r.entity;
+            entities = r.entities;
+            entitiesMap = r.entitiesMap;
+            entitiesByType = r.entitiesByType;
+
+            connections = r.connections;
+            forwardConnections = r.forwardConnections;
+
+            counters = r.counters;
+
+            types = r.types;
+
+            metadata = r.metadata;
+            metadataMerged = r.metadataMerged;
+
+            nextResult = r.nextResult;
+            cursor = r.cursor;
+
+            query = r.query;
+            data = r.data;
+            dataName = r.dataName;
+        }
+    }
+
+
+    public void init() {
+        level = Level.IDS;
+
+        id = null;
+        ids = null;
+        idSet = null;
+
+        ref = null;
+        refs = null;
+        refsMap = null;
+        refsByType = null;
+
+        entity = null;
+        entities = null;
+        entitiesMap = null;
+        entitiesByType = null;
+
+        connections = null;
+        forwardConnections = true;
+
+        counters = null;
+
+        types = null;
+
+        // metadata = null;
+        metadataMerged = false;
+
+        query = null;
+        data = null;
+        dataName = null;
+    }
+
+
+    public static Results fromIdList( List<UUID> l ) {
+        Results r = new Results();
+        r.setIds( l );
+        return r;
+    }
+
+
+    public static Results fromIdList( List<UUID> l, String type ) {
+        if ( type == null ) {
+            return fromIdList( l );
+        }
+        List<EntityRef> refs = new ArrayList<EntityRef>();
+        for ( UUID u : l ) {
+            refs.add( ref( type, u ) );
+        }
+        Results r = new Results();
+        r.setRefs( refs );
+        return r;
+    }
+
+
+    public static Results fromId( UUID id ) {
+        Results r = new Results();
+        if ( id != null ) {
+            List<UUID> l = new ArrayList<UUID>();
+            l.add( id );
+            r.setIds( l );
+        }
+        return r;
+    }
+
+
+    public static Results fromRefList( List<EntityRef> l ) {
+        Results r = new Results();
+        r.setRefs( l );
+        return r;
+    }
+
+
+    public static Results fromEntities( List<? extends Entity> l ) {
+        Results r = new Results();
+        r.setEntities( l );
+        return r;
+    }
+
+
+    public static Results fromEntity( Entity e ) {
+        Results r = new Results();
+        r.setEntity( e );
+        return r;
+    }
+
+
+    public static Results fromRef( EntityRef ref ) {
+        if ( ref instanceof Entity ) {
+            return fromEntity( ( Entity ) ref );
+        }
+        Results r = new Results();
+        r.setRef( ref );
+        return r;
+    }
+
+
+    public static Results fromData( Object obj ) {
+        Results r = new Results();
+        r.setData( obj );
+        return r;
+    }
+
+
+    public static Results fromCounters( AggregateCounterSet counters ) {
+        Results r = new Results();
+        List<AggregateCounterSet> l = new ArrayList<AggregateCounterSet>();
+        l.add( counters );
+        r.setCounters( l );
+        return r;
+    }
+
+
+    public static Results fromCounters( List<AggregateCounterSet> counters ) {
+        Results r = new Results();
+        r.setCounters( counters );
+        return r;
+    }
+
+
+    @SuppressWarnings("unchecked")
+    public static Results fromConnections( List<? extends ConnectionRef> connections ) {
+        Results r = new Results();
+        r.setConnections( ( List<ConnectionRef> ) connections, true );
+        return r;
+    }
+
+
+    @SuppressWarnings("unchecked")
+    public static Results fromConnections( List<? extends ConnectionRef> connections, boolean forward ) {
+        Results r = new Results();
+        r.setConnections( ( List<ConnectionRef> ) connections, forward );
+        return r;
+    }
+
+
+    public Level getLevel() {
+        return level;
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public Query getQuery() {
+        return query;
+    }
+
+
+    public void setQuery( Query query ) {
+        this.query = query;
+    }
+
+
+    public Results withQuery( Query query ) {
+        this.query = query;
+        return this;
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public UUID getId() {
+        if ( id != null ) {
+            return id;
+        }
+        if ( entity != null ) {
+            id = entity.getUuid();
+            return id;
+        }
+        if ( ( ids != null ) && ( ids.size() > 0 ) ) {
+            id = ids.get( 0 );
+            return id;
+        }
+        if ( ( entities != null ) && ( entities.size() > 0 ) ) {
+            entity = entities.get( 0 );
+            id = entity.getUuid();
+            return id;
+        }
+        if ( ( refs != null ) && ( refs.size() > 0 ) ) {
+            EntityRef ref = refs.get( 0 );
+            id = ref.getUuid();
+        }
+        return id;
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public List<UUID> getIds() {
+        if ( ids != null ) {
+            return ids;
+        }
+        /*
+         * if (connectionTypeAndEntityTypeToEntityIdMap != null) { ids = new
+         * ArrayList<UUID>(); Set<UUID> entitySet = new LinkedHashSet<UUID>();
+         * for (String ctype : connectionTypeAndEntityTypeToEntityIdMap
+         * .keySet()) { Map<String, List<UUID>> m =
+         * connectionTypeAndEntityTypeToEntityIdMap .get(ctype); for (String
+         * etype : m.keySet()) { List<UUID> l = m.get(etype); for (UUID id : l)
+         * { if (!entitySet.contains(id)) { ids.add(id); } } } } return ids; }
+         */
+        if ( connections != null ) {
+            ids = new ArrayList<UUID>();
+            for ( ConnectionRef connection : connections ) {
+                if ( forwardConnections ) {
+                    ConnectedEntityRef c = connection.getConnectedEntity();
+                    if ( c != null ) {
+                        ids.add( c.getUuid() );
+                    }
+                }
+                else {
+                    EntityRef c = connection.getConnectingEntity();
+                    if ( c != null ) {
+                        ids.add( c.getUuid() );
+                    }
+                }
+            }
+            return ids;
+        }
+        if ( ( entities != null )
+        /* || (connectionTypeAndEntityTypeToEntityMap != null) */ ) {
+            // getEntities();
+            ids = new ArrayList<UUID>();
+            for ( Entity entity : entities ) {
+                ids.add( entity.getUuid() );
+            }
+            return ids;
+        }
+        if ( refs != null ) {
+            ids = new ArrayList<UUID>();
+            for ( EntityRef ref : refs ) {
+                ids.add( ref.getUuid() );
+            }
+            return ids;
+        }
+        if ( id != null ) {
+            ids = new ArrayList<UUID>();
+            ids.add( id );
+            return ids;
+        }
+        if ( entity != null ) {
+            ids = new ArrayList<UUID>();
+            ids.add( entity.getUuid() );
+            return ids;
+        }
+        return new ArrayList<UUID>();
+    }
+
+
+    public void setIds( List<UUID> resultsIds ) {
+        init();
+        ids = resultsIds;
+        level = Level.IDS;
+    }
+
+
+    public Results withIds( List<UUID> resultsIds ) {
+        setIds( resultsIds );
+        return this;
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public Set<UUID> getIdSet() {
+        if ( idSet != null ) {
+            return idSet;
+        }
+        getIds();
+        if ( ids != null ) {
+            idSet = new LinkedHashSet<UUID>();
+            idSet.addAll( ids );
+            return idSet;
+        }
+        return new LinkedHashSet<UUID>();
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    @SuppressWarnings("unchecked")
+    public List<EntityRef> getRefs() {
+        if ( refs != null ) {
+            return refs;
+        }
+        List<?> l = getEntities();
+        if ( ( l != null ) && ( l.size() > 0 ) ) {
+            return ( List<EntityRef> ) l;
+        }
+        if ( connections != null ) {
+            refs = new ArrayList<EntityRef>();
+            for ( ConnectionRef connection : connections ) {
+                if ( forwardConnections ) {
+                    ConnectedEntityRef c = connection.getConnectedEntity();
+                    if ( c != null ) {
+                        refs.add( c );
+                    }
+                }
+                else {
+                    EntityRef c = connection.getConnectingEntity();
+                    if ( c != null ) {
+                        refs.add( c );
+                    }
+                }
+            }
+            return refs;
+        }
+        if ( ref != null ) {
+            refs = new ArrayList<EntityRef>();
+            refs.add( ref );
+            return refs;
+        }
+        return new ArrayList<EntityRef>();
+    }
+
+
+    public void setRefs( List<EntityRef> resultsRefs ) {
+        init();
+        refs = resultsRefs;
+        level = Level.REFS;
+    }
+
+
+    public Results withRefs( List<EntityRef> resultsRefs ) {
+        setRefs( resultsRefs );
+        return this;
+    }
+
+
+    public void setRef( EntityRef ref ) {
+        init();
+        this.ref = ref;
+        level = Level.REFS;
+    }
+
+
+    public Results withRef( EntityRef ref ) {
+        setRef( ref );
+        return this;
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public EntityRef getRef() {
+        if ( ref != null ) {
+            return ref;
+        }
+        ref = getEntity();
+        if ( ref != null ) {
+            return ref;
+        }
+        UUID u = getId();
+        if ( u != null ) {
+            return ref( u );
+        }
+        return null;
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public Map<UUID, EntityRef> getRefsMap() {
+        if ( refsMap != null ) {
+            return refsMap;
+        }
+        getEntitiesMap();
+        if ( entitiesMap != null ) {
+            refsMap = cast( entitiesMap );
+            return refsMap;
+        }
+        getRefs();
+        if ( refs != null ) {
+            refsMap = new LinkedHashMap<UUID, EntityRef>();
+            for ( EntityRef ref : refs ) {
+                refsMap.put( ref.getUuid(), ref );
+            }
+        }
+        return refsMap;
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public Entity getEntity() {
+        mergeEntitiesWithMetadata();
+        if ( entity != null ) {
+            return entity;
+        }
+        if ( ( entities != null ) && ( entities.size() > 0 ) ) {
+            entity = entities.get( 0 );
+            return entity;
+        }
+        return null;
+    }
+
+
+    public void setEntity( Entity resultEntity ) {
+        init();
+        entity = resultEntity;
+        level = Level.CORE_PROPERTIES;
+    }
+
+
+    public Results withEntity( Entity resultEntity ) {
+        setEntity( resultEntity );
+        return this;
+    }
+
+
+    public Iterator<UUID> idIterator() {
+        List<UUID> l = getIds();
+        if ( l != null ) {
+            return l.iterator();
+        }
+        return ( new ArrayList<UUID>( 0 ) ).iterator();
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public List<Entity> getEntities() {
+        mergeEntitiesWithMetadata();
+        if ( entities != null ) {
+            return entities;
+        }
+        /*
+         * if (connectionTypeAndEntityTypeToEntityMap != null) { entities = new
+         * ArrayList<Entity>(); Map<UUID, Entity> eMap = new LinkedHashMap<UUID,
+         * Entity>(); for (String ctype :
+         * connectionTypeAndEntityTypeToEntityMap.keySet()) { Map<String,
+         * List<Entity>> m = connectionTypeAndEntityTypeToEntityMap .get(ctype);
+         * for (String etype : m.keySet()) { List<Entity> l = m.get(etype); for
+         * (Entity e : l) { if (!eMap.containsKey(e.getUuid())) { entities.add(e);
+         * eMap.put(e.getUuid(), e); } } } } return entities; }
+         */
+        if ( entity != null ) {
+            entities = new ArrayList<Entity>();
+            entities.add( entity );
+            return entities;
+        }
+        return new ArrayList<Entity>();
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public Map<UUID, Entity> getEntitiesMap() {
+        if ( entitiesMap != null ) {
+            return entitiesMap;
+        }
+        if ( entities != null ) {
+            entitiesMap = new LinkedHashMap<UUID, Entity>();
+            for ( Entity entity : entities ) {
+                entitiesMap.put( entity.getUuid(), entity );
+            }
+        }
+        return entitiesMap;
+    }
+
+
+    public List<EntityRef> getEntityRefsByType( String type ) {
+        if ( entitiesByType != null ) {
+            return refsByType.get( type );
+        }
+        List<EntityRef> l = cast( getEntitiesByType( type ) );
+        if ( l != null ) {
+            return l;
+        }
+        getRefs();
+        if ( refs == null ) {
+            return null;
+        }
+        refsByType = new LinkedHashMap<String, List<EntityRef>>();
+        for ( Entity entity : entities ) {
+            l = refsByType.get( entity.getType() );
+            if ( l == null ) {
+                l = new ArrayList<EntityRef>();
+                refsByType.put( entity.getType(), l );
+            }
+            l.add( entity );
+        }
+        return l;
+    }
+
+
+    public List<Entity> getEntitiesByType( String type ) {
+        if ( entitiesByType != null ) {
+            return entitiesByType.get( type );
+        }
+        getEntities();
+        if ( entities == null ) {
+            return null;
+        }
+        List<Entity> l = null;
+        entitiesByType = new LinkedHashMap<String, List<Entity>>();
+        for ( Entity entity : entities ) {
+            l = entitiesByType.get( entity.getType() );
+            if ( l == null ) {
+                l = new ArrayList<Entity>();
+                entitiesByType.put( entity.getType(), l );
+            }
+            l.add( entity );
+        }
+        return l;
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public Set<String> getTypes() {
+        if ( types != null ) {
+            return types;
+        }
+        getEntityRefsByType( "entity" );
+        if ( entitiesByType != null ) {
+            types = entitiesByType.keySet();
+        }
+        else if ( refsByType != null ) {
+            types = refsByType.keySet();
+        }
+        return types;
+    }
+
+
+    public void merge( Results results ) {
+        getEntitiesMap();
+        results.getEntitiesMap();
+        if ( entitiesMap != null || results.entitiesMap != null ) {
+
+            level = Level.ALL_PROPERTIES;
+
+            // do nothing, nothing to union
+            if ( entitiesMap != null && results.entitiesMap == null ) {
+                return;
+                // other side has the results, assign and return
+            }
+            else if ( entitiesMap == null && results.entitiesMap != null ) {
+                entities = results.entities;
+                return;
+            }
+
+            entitiesMap.putAll( results.entitiesMap );
+            entities = new ArrayList<Entity>( entitiesMap.values() );
+
+            return;
+        }
+
+        getRefsMap();
+        results.getRefsMap();
+        if ( ( refsMap != null ) || ( results.refsMap != null ) ) {
+
+            level = Level.REFS;
+
+            // do nothing, nothing to union
+            if ( refsMap != null && results.refsMap == null ) {
+                return;
+                // other side has the results, assign and return
+            }
+            else if ( refsMap == null && results.refsMap != null ) {
+                refs = results.refs;
+                return;
+            }
+
+            refsMap.putAll( results.refsMap );
+            refs = new ArrayList<EntityRef>( refsMap.values() );
+
+            return;
+        }
+
+        getIdSet();
+        results.getIdSet();
+        if ( ( idSet != null ) && ( results.idSet != null ) ) {
+
+            level = Level.IDS;
+
+            // do nothing, nothing to union
+            if ( idSet != null && results.idSet == null ) {
+                return;
+                // other side has the results, assign and return
+            }
+            else if ( idSet == null && results.idSet != null ) {
+                ids = results.ids;
+                return;
+            }
+
+            idSet.addAll( results.idSet );
+            ids = new ArrayList<UUID>( idSet );
+
+            return;
+        }
+    }
+
+
+    /** Remove the passed in results from the current results */
+    public void subtract( Results results ) {
+        getEntitiesMap();
+        results.getEntitiesMap();
+
+        if ( ( entitiesMap != null ) && ( results.entitiesMap != null ) ) {
+            Map<UUID, Entity> newMap = new LinkedHashMap<UUID, Entity>();
+            for ( Map.Entry<UUID, Entity> e : entitiesMap.entrySet() ) {
+                if ( !results.entitiesMap.containsKey( e.getKey() ) ) {
+                    newMap.put( e.getKey(), e.getValue() );
+                }
+            }
+            entitiesMap = newMap;
+            entities = new ArrayList<Entity>( entitiesMap.values() );
+            level = Level.ALL_PROPERTIES;
+            return;
+        }
+
+        getRefsMap();
+        results.getRefsMap();
+        if ( ( refsMap != null ) && ( results.refsMap != null ) ) {
+            Map<UUID, EntityRef> newMap = new LinkedHashMap<UUID, EntityRef>();
+            for ( Map.Entry<UUID, EntityRef> e : refsMap.entrySet() ) {
+                if ( !results.refsMap.containsKey( e.getKey() ) ) {
+                    newMap.put( e.getKey(), e.getValue() );
+                }
+            }
+            refsMap = newMap;
+            refs = new ArrayList<EntityRef>( refsMap.values() );
+            level = Level.REFS;
+            return;
+        }
+
+        getIdSet();
+        results.getIdSet();
+        if ( ( idSet != null ) && ( results.idSet != null ) ) {
+            Set<UUID> newSet = new LinkedHashSet<UUID>();
+            for ( UUID uuid : idSet ) {
+                if ( !results.idSet.contains( uuid ) ) {
+                    newSet.add( uuid );
+                }
+            }
+            idSet = newSet;
+            ids = new ArrayList<UUID>( idSet );
+            level = Level.IDS;
+            return;
+        }
+    }
+
+
+    /** Perform an intersection of the 2 results */
+    public void and( Results results ) {
+        getEntitiesMap();
+        results.getEntitiesMap();
+
+        if ( ( entitiesMap != null ) && ( results.entitiesMap != null ) ) {
+            Map<UUID, Entity> newMap = new LinkedHashMap<UUID, Entity>();
+            for ( Map.Entry<UUID, Entity> e : entitiesMap.entrySet() ) {
+                if ( results.entitiesMap.containsKey( e.getKey() ) ) {
+                    newMap.put( e.getKey(), e.getValue() );
+                }
+            }
+            entitiesMap = newMap;
+            entities = new ArrayList<Entity>( entitiesMap.values() );
+            level = Level.ALL_PROPERTIES;
+            return;
+        }
+
+        getRefsMap();
+        results.getRefsMap();
+        if ( ( refsMap != null ) && ( results.refsMap != null ) ) {
+            Map<UUID, EntityRef> newMap = new LinkedHashMap<UUID, EntityRef>();
+            for ( Map.Entry<UUID, EntityRef> e : refsMap.entrySet() ) {
+                if ( results.refsMap.containsKey( e.getKey() ) ) {
+                    newMap.put( e.getKey(), e.getValue() );
+                }
+            }
+            refsMap = newMap;
+            refs = new ArrayList<EntityRef>( refsMap.values() );
+            level = Level.REFS;
+            ids = null;
+            return;
+        }
+
+        getIdSet();
+        results.getIdSet();
+        if ( ( idSet != null ) && ( results.idSet != null ) ) {
+            Set<UUID> newSet = new LinkedHashSet<UUID>();
+            for ( UUID uuid : idSet ) {
+                if ( results.idSet.contains( uuid ) ) {
+                    newSet.add( uuid );
+                }
+            }
+            idSet = newSet;
+            ids = new ArrayList<UUID>( idSet );
+            level = Level.IDS;
+            return;
+        }
+
+        // should be empty
+        init();
+    }
+
+
+    public void replace( Entity entity ) {
+        entitiesMap = null;
+        if ( ( this.entity != null ) && ( this.entity.getUuid().equals( entity.getUuid() ) ) ) {
+            this.entity = entity;
+        }
+        if ( entities != null ) {
+            ListIterator<Entity> i = entities.listIterator();
+            while ( i.hasNext() ) {
+                Entity e = i.next();
+                if ( e.getUuid().equals( entity.getUuid() ) ) {
+                    i.set( entity );
+                }
+            }
+        }
+    }
+
+
+    public Results startingFrom( UUID entityId ) {
+        if ( entities != null ) {
+            for ( int i = 0; i < entities.size(); i++ ) {
+                Entity entity = entities.get( i );
+                if ( entityId.equals( entity.getUuid() ) ) {
+                    if ( i == 0 ) {
+                        return this;
+                    }
+                    return Results.fromEntities( entities.subList( i, entities.size() ) );
+                }
+            }
+        }
+        if ( refs != null ) {
+            for ( int i = 0; i < refs.size(); i++ ) {
+                EntityRef entityRef = refs.get( i );
+                if ( entityId.equals( entityRef.getUuid() ) ) {
+                    if ( i == 0 ) {
+                        return this;
+                    }
+                    return Results.fromRefList( refs.subList( i, refs.size() ) );
+                }
+            }
+        }
+        if ( ids != null ) {
+            for ( int i = 0; i < ids.size(); i++ ) {
+                UUID uuid = ids.get( i );
+                if ( entityId.equals( uuid ) ) {
+                    if ( i == 0 ) {
+                        return this;
+                    }
+                    return Results.fromIdList( ids.subList( i, ids.size() ) );
+                }
+            }
+        }
+        return this;
+    }
+
+
+    @SuppressWarnings("unchecked")
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public <E extends Entity> List<E> getList() {
+        List<Entity> l = getEntities();
+        return ( List<E> ) l;
+    }
+
+
+    public <E extends Entity> Iterator<E> iterator( Class<E> cls ) {
+        List<E> l = getList();
+        if ( l != null ) {
+            return l.iterator();
+        }
+        return ( new ArrayList<E>( 0 ) ).iterator();
+    }
+
+
+    @Override
+    public Iterator<Entity> iterator() {
+        List<Entity> l = getEntities();
+        if ( l != null ) {
+            return l.iterator();
+        }
+        return ( new ArrayList<Entity>( 0 ) ).iterator();
+    }
+
+
+    public Results findForProperty( String propertyName, Object propertyValue ) {
+        return findForProperty( propertyName, propertyValue, 1 );
+    }
+
+
+    public Results findForProperty( String propertyName, Object propertyValue, int count ) {
+        if ( propertyValue == null ) {
+            return new Results();
+        }
+        List<Entity> l = getEntities();
+        if ( l == null ) {
+            return new Results();
+        }
+        List<Entity> found = new ArrayList<Entity>();
+        for ( Entity e : l ) {
+            if ( propertyValue.equals( e.getProperty( propertyName ) ) ) {
+                found.add( e );
+                if ( ( count > 0 ) && ( found.size() == count ) ) {
+                    break;
+                }
+            }
+        }
+        return Results.fromEntities( found );
+    }
+
+
+    @SuppressWarnings("unchecked")
+    public void setEntities( List<? extends Entity> resultsEntities ) {
+        init();
+        entities = ( List<Entity> ) resultsEntities;
+        level = Level.CORE_PROPERTIES;
+    }
+
+
+    public Results withEntities( List<? extends Entity> resultsEntities ) {
+        setEntities( resultsEntities );
+        return this;
+    }
+
+
+    public boolean hasConnections() {
+        return connections != null;
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public List<ConnectionRef> getConnections() {
+        return connections;
+    }
+
+
+    private void setConnections( List<ConnectionRef> connections, boolean forwardConnections ) {
+        init();
+        this.connections = connections;
+        this.forwardConnections = forwardConnections;
+        level = Level.REFS;
+        for ( ConnectionRef connection : connections ) {
+            if ( forwardConnections ) {
+                this.setMetadata( connection.getConnectedEntity().getUuid(), "connection",
+                        connection.getConnectionType() );
+            }
+            else {
+                this.setMetadata( connection.getConnectingEntity().getUuid(), "connection",
+                        connection.getConnectionType() );
+            }
+        }
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public Object getObject() {
+        if ( data != null ) {
+            return data;
+        }
+        if ( entities != null ) {
+            return entities;
+        }
+        if ( ids != null ) {
+            return ids;
+        }
+        if ( entity != null ) {
+            return entity;
+        }
+        if ( id != null ) {
+            return id;
+        }
+        if ( counters != null ) {
+            return counters;
+        }
+        return null;
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public String getObjectName() {
+        if ( dataName != null ) {
+            return dataName;
+        }
+        if ( entities != null ) {
+            return "entities";
+        }
+        if ( ids != null ) {
+            return "ids";
+        }
+        if ( entity != null ) {
+            return "entity";
+        }
+        if ( id != null ) {
+            return "id";
+        }
+        return null;
+    }
+
+
+    public void setDataName( String dataName ) {
+        this.dataName = dataName;
+    }
+
+
+    public Results withDataName( String dataName ) {
+        this.dataName = dataName;
+        return this;
+    }
+
+
+    public boolean hasData() {
+        return data != null;
+    }
+
+
+    public void setData( Object data ) {
+        this.data = data;
+    }
+
+
+    public Results withData( Object data ) {
+        this.data = data;
+        return this;
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public Object getData() {
+        return data;
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public List<AggregateCounterSet> getCounters() {
+        return counters;
+    }
+
+
+    public void setCounters( List<AggregateCounterSet> counters ) {
+        this.counters = counters;
+    }
+
+
+    public Results withCounters( List<AggregateCounterSet> counters ) {
+        this.counters = counters;
+        return this;
+    }
+
+
+    public int size() {
+        if ( entities != null ) {
+            return entities.size();
+        }
+        if ( refs != null ) {
+            return refs.size();
+        }
+        if ( ids != null ) {
+            return ids.size();
+        }
+        if ( entity != null ) {
+            return 1;
+        }
+        if ( ref != null ) {
+            return 1;
+        }
+        if ( id != null ) {
+            return 1;
+        }
+        return 0;
+    }
+
+
+    public boolean isEmpty() {
+        return size() == 0;
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public UUID getNextResult() {
+        return nextResult;
+    }
+
+
+    public Results excludeCursorMetadataAttribute() {
+        if ( metadata != null ) {
+            for ( Entry<UUID, Map<String, Object>> entry : metadata.entrySet() ) {
+                Map<String, Object> map = entry.getValue();
+                if ( map != null ) {
+                    map.remove( Schema.PROPERTY_CURSOR );
+                }
+            }
+        }
+        return new Results( this );
+    }
+
+
+    public Results trim( int count ) {
+        if ( count == 0 ) {
+            return this;
+        }
+
+        int size = size();
+        if ( size <= count ) {
+            return this;
+        }
+
+        List<UUID> ids = getIds();
+        UUID nextResult = null;
+        String cursor = null;
+        if ( ids.size() > count ) {
+            nextResult = ids.get( count );
+            ids = ids.subList( 0, count );
+            if ( metadata != null ) {
+                cursor = StringUtils.toString( MapUtils.getMapMap( metadata, nextResult, "cursor" ) );
+            }
+            if ( cursor == null ) {
+                cursor = encodeBase64URLSafeString( bytes( nextResult ) );
+            }
+        }
+
+        Results r = new Results( this );
+        if ( r.entities != null ) {
+            r.entities = r.entities.subList( 0, count );
+        }
+        if ( r.refs != null ) {
+            r.refs = r.refs.subList( 0, count );
+        }
+        if ( r.ids != null ) {
+            r.ids = r.ids.subList( 0, count );
+        }
+        r.setNextResult( nextResult );
+        r.setCursor( cursor );
+
+        return r;
+    }
+
+
+    public boolean hasMoreResults() {
+        return nextResult != null;
+    }
+
+
+    public void setNextResult( UUID nextResult ) {
+        this.nextResult = nextResult;
+    }
+
+
+    public Results withNextResult( UUID nextResult ) {
+        this.nextResult = nextResult;
+        return this;
+    }
+
+
+    @JsonSerialize(include = Inclusion.NON_NULL)
+    public String getCursor() {
+        return cursor;
+    }
+
+
+    public boolean hasCursor() {
+        return cursor != null && cursor.length() > 0;
+    }
+
+
+    public void setCursor( String cursor ) {
+        this.cursor = cursor;
+    }
+
+
+    public Results withCursor( String cursor ) {
+        this.cursor = cursor;
+        return this;
+    }
+
+
+    public void setMetadata( UUID id, String name, Object value ) {
+        if ( metadata == null ) {
+            metadata = new LinkedHashMap<UUID, Map<String, Object>>();
+        }
+        Map<String, Object> entityMetadata = metadata.get( id );
+        if ( entityMetadata == null ) {
+            entityMetadata = new LinkedHashMap<String, Object>();
+            metadata.put( id, entityMetadata );
+        }
+        entityMetadata.put( name, value );
+        metadataMerged = false;
+        // updateIndex(id, name, value);
+    }
+
+
+    public Results withMetadata( UUID id, String name, Object value ) {
+        setMetadata( id, name, value );
+        return this;
+    }
+
+
+    public void setMetadata( UUID id, Map<String, Object> data ) {
+        if ( metadata == null ) {
+            metadata = new LinkedHashMap<UUID, Map<String, Object>>();
+        }
+        Map<String, Object> entityMetadata = metadata.get( id );
+        if ( entityMetadata == null ) {
+            entityMetadata = new LinkedHashMap<String, Object>();
+            metadata.put( id, entityMetadata );
+        }
+        entityMetadata.putAll( data );
+        metadataMerged = false;
+        /*
+         * for (Entry<String, Object> m : data.entrySet()) { updateIndex(id,
+         * m.getKey(), m.getValue()); }
+         */
+    }
+
+
+    public Results withMetadata( UUID id, Map<String, Object> data ) {
+        setMetadata( id, data );
+        return this;
+    }
+
+
+    public void setMetadata( Map<UUID, Map<String, Object>> metadata ) {
+        this.metadata = metadata;
+    }
+
+
+    public Results withMetadata( Map<UUID, Map<String, Object>> metadata ) {
+        this.metadata = metadata;
+        return this;
+    }
+
+
+    public void mergeEntitiesWithMetadata() {
+        if ( metadataMerged ) {
+            return;
+        }
+        if ( metadata == null ) {
+            return;
+        }
+        metadataMerged = true;
+        getEntities();
+        if ( entities != null ) {
+            for ( Entity entity : entities ) {
+                entity.clearMetadata();
+                Map<String, Object> entityMetadata = metadata.get( entity.getUuid() );
+                if ( entityMetadata != null ) {
+                    entity.mergeMetadata( entityMetadata );
+                }
+            }
+        }
+    }
+
+
+    protected QueryProcessor getQueryProcessor() {
+        return queryProcessor;
+    }
+
+
+    public void setQueryProcessor( QueryProcessor queryProcessor ) {
+        this.queryProcessor = queryProcessor;
+    }
+
+
+    public void setSearchVisitor( SearchVisitor searchVisitor ) {
+        this.searchVisitor = searchVisitor;
+    }
+
+
+    /** uses cursor to get next batch of Results (returns null if no cursor) */
+    public Results getNextPageResults() throws Exception {
+        if ( !hasCursor() ) {
+            return null;
+        }
+
+        Query q = new Query( query );
+        q.setCursor( getCursor() );
+        queryProcessor.setQuery( q );
+
+        return queryProcessor.getResults( searchVisitor );
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/2c2acbe4/stack/core/src/main/java/org/apache/usergrid/persistence/RoleRef.java
----------------------------------------------------------------------
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/RoleRef.java b/stack/core/src/main/java/org/apache/usergrid/persistence/RoleRef.java
new file mode 100644
index 0000000..f194a2b
--- /dev/null
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/RoleRef.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright 2012 Apigee Corporation
+ *
+ * Licensed 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.persistence;
+
+
+import java.util.UUID;
+
+
+public interface RoleRef extends EntityRef {
+
+    public UUID getGroupId();
+
+    public EntityRef getGroupRef();
+
+    public String getRoleName();
+
+    public String getApplicationRoleName();
+}