org.mongojack.DBCursor.java Source code

Java tutorial

Introduction

Here is the source code for org.mongojack.DBCursor.java

Source

/*
 * Copyright 2011 VZ Netzwerke Ltd
 *
 * 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.mongojack;

import com.mongodb.*;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * An iterator over database results.
 * Doing a <code>find()</code> query on a collection returns a
 * <code>DBCursor</code> thus
 * <p/>
 * <blockquote><pre>
 * DBCursor cursor = collection.find( query );
 * if( cursor.hasNext() )
 *     T obj = cursor.next();
 * </pre></blockquote>
 * <p/>
 * <p><b>Warning:</b> Calling <code>toArray</code> or <code>length</code> on
 * a DBCursor will irrevocably turn it into an array.  This
 * means that, if the cursor was iterating over ten million results
 * (which it was lazily fetching from the database), suddenly there will
 * be a ten-million element array in memory.  Before converting to an array,
 * make sure that there are a reasonable number of results using
 * <code>skip()</code> and <code>limit()</code>.
 * <p>For example, to get an array of the 1000-1100th elements of a cursor, use
 * <p/>
 * <blockquote><pre>
 * List<DBObject> obj = collection.find( query ).skip( 1000 ).limit( 100 ).toArray();
 * </pre></blockquote>
 *
 * @author James Roper
 * @since 1.0
 */
public class DBCursor<T> extends DBQuery.AbstractBuilder<DBCursor<T>> implements Iterator<T>, Iterable<T> {
    private final com.mongodb.DBCursor cursor;
    private final JacksonDBCollection<T, ?> jacksonDBCollection;

    // For use in iterator mode
    private T current;
    // For use in array mode
    private final List<T> all = new ArrayList<T>();
    // Flag to indicate that the query has been executed
    private boolean executed;

    public DBCursor(JacksonDBCollection<T, ?> jacksonDBCollection, com.mongodb.DBCursor cursor) {
        super(cursor.getQuery());
        this.jacksonDBCollection = jacksonDBCollection;
        this.cursor = cursor;
        if (jacksonDBCollection.isEnabled(JacksonDBCollection.Feature.USE_STREAM_DESERIALIZATION)) {
            this.cursor.setDecoderFactory(jacksonDBCollection.getDecoderFactory());
        }
    }

    /**
     * Creates a copy of an existing database cursor.
     * The new cursor is an iterator, even if the original
     * was an array.
     *
     * @return the new cursor
     */
    public DBCursor<T> copy() {
        return new DBCursor<T>(jacksonDBCollection, cursor.copy());
    }

    /**
     * creates a copy of this cursor object that can be iterated.
     * Note:
     * - you can iterate the DBCursor itself without calling this method
     * - no actual data is getting copied.
     *
     * @return The iterator
     */
    public Iterator<T> iterator() {
        return this.copy();
    }

    // ---- query modifiers --------

    /**
     * Sorts this cursor'string elements.
     * This method must be called before getting any object from the cursor.
     *
     * @param orderBy the fields by which to sort
     * @return a cursor pointing to the first element of the sorted results
     */
    public DBCursor<T> sort(DBObject orderBy) {
        cursor.sort(orderBy);
        return this;
    }

    /**
     * adds a special operator like $maxScan or $returnKey
     * e.g. addSpecial( "$returnKey" , 1 )
     * e.g. addSpecial( "$maxScan" , 100 )
     *
     * @param name The name
     * @param o    The object
     * @return This object
     */
    public DBCursor<T> addSpecial(String name, Object o) {
        cursor.addSpecial(name, o);
        return this;
    }

    /**
     * Informs the database of indexed fields of the collection in order to improve performance.
     *
     * @param indexKeys a <code>DBObject</code> with fields and direction
     * @return same DBCursor for chaining operations
     */
    public DBCursor<T> hint(DBObject indexKeys) {
        cursor.hint(indexKeys);
        return this;
    }

    /**
     * Informs the database of an indexed field of the collection in order to improve performance.
     *
     * @param indexName the name of an index
     * @return same JacksonDBCursor<T>t for chaining operations
     */
    public DBCursor<T> hint(String indexName) {
        cursor.hint(indexName);
        return this;
    }

    /**
     * Use snapshot mode for the query. Snapshot mode assures no duplicates are
     * returned, or objects missed, which were present at both the start and end
     * of the query'string execution (if an object is new during the query, or deleted
     * during the query, it may or may not be returned, even with snapshot mode).
     * Note that short query responses (less than 1MB) are always effectively snapshotted.
     * Currently, snapshot mode may not be used with sorting or explicit hints.
     *
     * @return same JacksonDBCursor<T> for chaining operations
     */
    public DBCursor<T> snapshot() {
        cursor.snapshot();
        return this;
    }

    /**
     * Returns an object containing basic information about the
     * execution of the query that created this cursor
     * This creates a <code>DBObject</code> with the key/value pairs:
     * "cursor" : cursor type
     * "nScanned" : number of records examined by the database for this query
     * "n" : the number of records that the database returned
     * "millis" : how long it took the database to execute the query
     *
     * @return a <code>DBObject</code>
     */
    public DBObject explain() {
        return cursor.explain();
    }

    /**
     * Limits the number of elements returned.
     * Note: parameter <tt>n</tt> should be positive, although a negative value is supported for legacy reason.
     * Passing a negative value will call {@link DBCursor <T>#batchSize(int)} which is the preferred method.
     *
     * @param n the number of elements to return
     * @return a cursor to iterate the results
     */
    public DBCursor<T> limit(int n) {
        cursor.limit(n);
        return this;
    }

    /**
     * Limits the number of elements returned in one batch.
     * A cursor typically fetches a batch of result objects and store them locally.
     * <p/>
     * If <tt>batchSize</tt> is positive, it represents the size of each batch of objects retrieved.
     * It can be adjusted to optimize performance and limit data transfer.
     * <p/>
     * If <tt>batchSize</tt> is negative, it will limit of number objects returned, that fit within the max batch size limit (usually 4MB), and cursor will be closed.
     * For example if <tt>batchSize</tt> is -10, then the server will return a maximum of 10 documents and as many as can fit in 4MB, then close the cursor.
     * Note that this feature is different from limit() in that documents must fit within a maximum size, and it removes the need to send a request to close the cursor server-side.
     * <p/>
     * The batch size can be changed even after a cursor is iterated, in which case the setting will apply on the next batch retrieval.
     *
     * @param n the number of elements to return in a batch
     * @return This object
     */
    public DBCursor<T> batchSize(int n) {
        cursor.batchSize(n);
        return this;
    }

    /**
     * Discards a given number of elements at the beginning of the cursor.
     *
     * @param n the number of elements to skip
     * @return a cursor pointing to the new first element of the results
     * @throws RuntimeException if the cursor has started to be iterated through
     */
    public DBCursor<T> skip(int n) {
        cursor.skip(n);
        return this;
    }

    /**
     * gets the cursor id.
     *
     * @return the cursor id, or 0 if there is no active cursor.
     */
    public long getCursorId() {
        return cursor.getCursorId();
    }

    /**
     * kills the current cursor on the server.
     */
    public void close() {
        cursor.close();
    }

    /**
     * adds a query option - see Bytes.QUERYOPTION_* for simpleList
     *
     * @param option The option
     * @return This object
     */
    public DBCursor<T> addOption(int option) {
        cursor.addOption(option);
        return this;
    }

    /**
     * sets the query option - see Bytes.QUERYOPTION_* for simpleList
     *
     * @param options The options
     * @return This object
     */
    public DBCursor<T> setOptions(int options) {
        cursor.setOptions(options);
        return this;
    }

    /**
     * resets the query options
     *
     * @return This object
     */
    public DBCursor<T> resetOptions() {
        cursor.resetOptions();
        return this;
    }

    /**
     * gets the query options
     *
     * @return The options
     */
    public int getOptions() {
        return cursor.getOptions();
    }

    /**
     * gets the number of times, so far, that the cursor retrieved a batch from the database
     *
     * @return The number of get mores
     */
    public int numGetMores() {
        return cursor.numGetMores();
    }

    /**
     * gets a simpleList containing the number of items received in each batch
     *
     * @return The sizes of each batch
     */
    public List<Integer> getSizes() {
        return cursor.getSizes();
    }

    /**
     * Returns the number of objects through which the cursor has iterated.
     *
     * @return the number of objects seen
     */
    public int numSeen() {
        return cursor.numSeen();
    }

    // ----- iterator api -----

    /**
     * Checks if there is another object available
     *
     * @return true if there is another object available
     * @throws MongoException
     */
    public boolean hasNext() throws MongoException {
        executed();
        return cursor.hasNext();
    }

    /**
     * Returns the object the cursor is at and moves the cursor ahead by one.
     *
     * @return the next element
     * @throws MongoException
     */
    public T next() throws MongoException {
        executed();
        current = jacksonDBCollection.convertFromDbObject(cursor.next());
        return current;
    }

    /**
     * Returns the element the cursor is at.
     *
     * @return the next element
     */
    public T curr() {
        // This triggers the checks to be done, we ignore the result
        cursor.curr();
        return current;
    }

    /**
     * Not implemented.
     */
    public void remove() {
        cursor.remove();
    }

    /**
     * pulls back all items into an array and returns the number of objects.
     * Note: this can be resource intensive
     *
     * @return the number of elements in the array
     * @throws MongoException Ig as error occurred
     * @see #count()
     * @see #size()
     */
    public int length() throws MongoException {
        executed();
        return cursor.length();
    }

    /**
     * Converts this cursor to an array.
     *
     * @return an array of elements
     * @throws MongoException If an error occurred
     */
    public List<T> toArray() throws MongoException {
        executed();
        return toArray(Integer.MAX_VALUE);
    }

    /**
     * Converts this cursor to an array.
     *
     * @param max the maximum number of objects to return
     * @return an array of objects
     * @throws MongoException If an error occurred
     */
    public List<T> toArray(int max) throws MongoException {
        executed();
        if (max > all.size()) {
            List<DBObject> objects = cursor.toArray(max);
            for (int i = all.size(); i < objects.size(); i++) {
                all.add(jacksonDBCollection.convertFromDbObject(objects.get(i)));
            }
        }
        return all;
    }

    /**
     * for testing only!
     * Iterates cursor and counts objects
     *
     * @return num objects
     * @see #count()
     */
    public int itcount() {
        executed();
        return cursor.itcount();
    }

    /**
     * Counts the number of objects matching the query
     * This does not take limit/skip into consideration
     *
     * @return the number of objects
     * @throws MongoException
     * @see #size()
     */
    public int count() {
        executed();
        return cursor.count();
    }

    /**
     * Counts the number of objects matching the query
     * this does take limit/skip into consideration
     *
     * @return the number of objects
     * @throws MongoException
     * @see #count()
     */
    public int size() {
        executed();
        return cursor.size();
    }

    /**
     * gets the fields to be returned
     *
     * @return The keys wanted
     */
    public DBObject getKeysWanted() {
        return cursor.getKeysWanted();
    }

    /**
     * gets the query
     *
     * @return The query
     */
    public DBObject getQuery() {
        return cursor.getQuery();
    }

    /**
     * gets the collection
     *
     * @return The collection
     */
    public JacksonDBCollection getCollection() {
        return jacksonDBCollection;
    }

    /**
     * Gets the Server Address of the server that data is pulled from.
     * Note that this information is not available if no data has been retrieved yet.
     * Availability is specific to underlying implementation and may vary.
     *
     * @return The server address
     */
    public ServerAddress getServerAddress() {
        return cursor.getServerAddress();
    }

    /**
     * Sets the read preference for this cursor.
     * See the * documentation for {@link ReadPreference}
     * for more information.
     *
     * @param preference Read Preference to use
     * @return This object
     */
    public DBCursor<T> setReadPreference(ReadPreference preference) {
        cursor.setReadPreference(preference);
        return this;
    }

    /**
     * Gets the default read preference
     *
     * @return The read preference
     */
    public ReadPreference getReadPreference() {
        return cursor.getReadPreference();
    }

    public DBCursor<T> setDecoderFactory(DBDecoderFactory fact) {
        cursor.setDecoderFactory(fact);
        return this;
    }

    public DBDecoderFactory getDecoderFactory() {
        return cursor.getDecoderFactory();
    }

    /**
     * Get the underlying MongoDB cursor.  Note, if this is an iterator cursor, calling next() on the underlying cursor
     * will cause this iterator to also progress forward, however, curr() will still return the last object that was
     * loaded by this cursor, not the underlying cursor.
     *
     * @return The underlying MongoDB cursor
     */
    public com.mongodb.DBCursor getCursor() {
        return cursor;
    }

    private void executed() {
        executed = true;
    }

    private void checkExecuted() {
        if (executed) {
            throw new MongoException("Cannot modify query after it's been executed");
        }
    }

    @Override
    protected DBCursor<T> put(String field, String op, Object value) {
        checkExecuted();
        return super.put(field, op, jacksonDBCollection.serializeField(value));
    }

    @Override
    protected DBCursor<T> putGroup(String op, Object... expressions) {
        checkExecuted();
        Object[] serialized = new Object[expressions.length];
        for (int i = 0; i < expressions.length; i++) {
            serialized[i] = jacksonDBCollection.serializeField(expressions[i]);
        }
        return super.putGroup(op, serialized);
    }
}