Package org.jpox.store.rdbms.query

Source Code of org.jpox.store.rdbms.query.ScrollableQueryResult

/**********************************************************************
Copyright (c) 2005 Erik Bengtson and others. All rights reserved.
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.

Contributors:
2005 Andy Jefferson - added support for bidirectional iterator
2008 Andy Jefferson - added resultCacheType. Removed optimistic restriction
    ...
**********************************************************************/
package org.jpox.store.rdbms.query;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;

import javax.jdo.JDODataStoreException;

import org.jpox.FetchPlan;
import org.jpox.exceptions.JPOXDataStoreException;
import org.jpox.exceptions.JPOXException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.store.mapped.expression.QueryExpression;
import org.jpox.store.query.Query;
import org.jpox.store.query.QueryResult;
import org.jpox.store.query.ResultObjectFactory;
import org.jpox.store.rdbms.SQLWarnings;
import org.jpox.util.JPOXLogger;
import org.jpox.util.SoftValueMap;
import org.jpox.util.StringUtils;
import org.jpox.util.WeakValueMap;

/**
* Lazy collection results from a Query with the ResultSet scrollable.
* Supports the following query extensions :-
* <ul>
* <li><b>org.jpox.query.resultSizeMethod</b> The method used to find the size of the result set.</li>
* <li><b>org.jpox.query.loadResultsAtCommit</b> Whether to load all results when the connection is closing.
* Has no effect if caching is not used.</li>
* <li><b>org.jpox.query.resultCacheType</b> Type of caching of result objects.
* Supports hard, weak, soft, none</li>
* </ul>
* If there is no transaction present, or if the FetchPlan is in "greedy" mode, and where caching is being used
* will load all results at startup. Otherwise results are only loaded when accessed.
*
* @version $Revision: 1.10 $
*/
public final class ScrollableQueryResult extends AbstractRDBMSQueryResult
    implements QueryResult, java.io.Serializable
{
    /** Size of the ResultSet. */
    private int size = -1;

    /** Map of ResultSet object values, keyed by the list index ("0", "1", etc). */
    private Map resultsObjsByIndex = null;

    /** Whether to load any unread results at commit (when connection is closed). */
    private boolean loadResultsAtCommit = true; // Default to load

    /** Method for getting the size of the results. */
    private String resultSizeMethod = "last"; // Default to moving to last row

    /**
     * Constructor of the result from a Query.
     * @param qs The Query Statement
     * @param query The Query
     * @param rof The factory to retrieve results from
     * @param rs The ResultSet from the Query Statement
     * @param candidates the Candidates collection. Pass this argument only when distinct = false
     */
    public ScrollableQueryResult(QueryExpression qs,
                                  Query query,
                                  ResultObjectFactory rof,
                                  ResultSet rs,
                                  Collection candidates)
    {
        super(qs, query, rof, rs);

        if (candidates != null)
        {
            //TODO support this feature
            throw new JPOXException("Unsupported Feature: Candidate Collection is only allowed using ForwardQueryResults").setFatal();
        }

        // Process any supported extensions
        String ext = (String)query.getExtension("org.jpox.query.resultSizeMethod");
        if (ext != null)
        {
            resultSizeMethod = ext;
        }
        ext = (String)query.getExtension("org.jpox.query.loadResultsAtCommit");
        if (ext != null)
        {
            loadResultsAtCommit = new Boolean(ext).booleanValue();
        }

        ext = (String)query.getExtension("org.jpox.query.resultCacheType");
        if (ext != null)
        {
            if (ext.equalsIgnoreCase("soft"))
            {
                resultsObjsByIndex = new SoftValueMap();
            }
            else if (ext.equalsIgnoreCase("weak"))
            {
                resultsObjsByIndex = new WeakValueMap();
            }
            else if (ext.equalsIgnoreCase("hard"))
            {
                resultsObjsByIndex = new HashMap();
            }
            else if (ext.equalsIgnoreCase("none"))
            {
                resultsObjsByIndex = null;
            }
            else
            {
                resultsObjsByIndex = new WeakValueMap();
            }
        }
        else
        {
            resultsObjsByIndex = new WeakValueMap();
        }

        if (resultsObjsByIndex != null)
        {
            // Caching results so load up any result objects needed right now
            int fetchSize = query.getFetchPlan().getFetchSize();
            if (!query.getObjectManager().getTransaction().isActive() || fetchSize == FetchPlan.FETCH_SIZE_GREEDY)
            {
                // No transaction or in "greedy" mode so load all results now
                boolean hasMoreResults = true;
                int index = 0;
                while (hasMoreResults)
                {
                    try
                    {
                        boolean rowExists = rs.absolute(index+1);
                        if (!rowExists)
                        {
                            hasMoreResults = false;
                            size = index; // We know the size now so store for later use
                        }
                        else
                        {
                            getObjectForIndex(index);
                            index++;
                        }
                    }
                    catch (SQLException sqle)
                    {

                    }
                }
            }
            else if (fetchSize > 0)
            {
                // Load up the first "fetchSize" objects now
                for (int i=0;i<fetchSize;i++)
                {
                    getObjectForIndex(i);
                }
            }
        }
    }

    /**
     * Accessor for the result object at an index.
     * If the object has already been processed will return that object,
     * otherwise will retrieve the object using the factory.
     * @param index The list index position
     * @return The result object
     */
    protected Object getObjectForIndex(int index)
    {
        if (resultsObjsByIndex != null)
        {
            // Caching objects, so check the cache for this index
            Object obj = resultsObjsByIndex.get("" + index);
            if (obj != null)
            {
                // Already retrieved so return it
                return obj;
            }
        }

        try
        {
            // ResultSet is numbered 1, 2, ... N
            // List is indexed 0, 1, 2, ... N-1
            rs.absolute(index+1);
            Object obj = rof.getObject(query.getObjectManager(), rs);
            SQLWarnings.log(rs);

            if (resultsObjsByIndex != null)
            {
                // Put it in our cache, keyed by the list index
                resultsObjsByIndex.put("" + index, obj);
            }

            return obj;
        }
        catch (SQLException sqe)
        {
            if (query.getObjectManager().getOMFContext().getApi().equalsIgnoreCase("JDO"))
            {
                throw new JDODataStoreException(LOCALISER.msg("052601", sqe));
            }
            else
            {
                throw new JPOXDataStoreException(LOCALISER.msg("052601", sqe));
            }
        }
    }

    /**
     * Method to close the results, making the results unusable thereafter.
     */
    public synchronized void close()
    {
        if (resultsObjsByIndex != null)
        {
            resultsObjsByIndex.clear();
        }

        super.close();
    }

    /**
     * Inform the query result that the connection is being closed so perform
     * any operations now, or rest in peace.
     */
    protected void closingConnection()
    {
        // Make sure all rows are loaded.
        if (loadResultsAtCommit && isOpen())
        {
            // Query connection closing message
            JPOXLogger.QUERY.info(LOCALISER.msg("052606", query.toString()));

            for (int i=0;i<size();i++)
            {
                getObjectForIndex(i);
            }
        }
    }

    // ------------------------- Implementation of List methods ----------------------

    /**
     * Accessor for an iterator for the results.
     * @return The iterator
     */
    public Iterator iterator()
    {
        return new QueryResultIterator();
    }

    /**
     * Accessor for an iterator for the results.
     * @return The iterator
     */
    public ListIterator listIterator()
    {
        return new QueryResultIterator();
    }

    /**
     * An Iterator results of a pm.query.execute().iterator()
     */
    private class QueryResultIterator implements ListIterator
    {
        private int iterRowNum = 0; // The index of the next object

        /**
         * Constructor
         */
        public QueryResultIterator()
        {
        }

        public void add(Object obj)
        {
            throw new UnsupportedOperationException(LOCALISER.msg("052603"));
        }

        public boolean hasNext()
        {
            synchronized (ScrollableQueryResult.this)
            {
                if (!isOpen())
                {
                    // Spec 14.6.7 Calling hasNext() on closed Query will return false
                    return false;
                }

                // When we aren't at size()-1 we have at least one more element
                return (iterRowNum <= (size() - 1));
            }
        }

        public boolean hasPrevious()
        {
            synchronized (ScrollableQueryResult.this)
            {
                if (!isOpen())
                {
                    // Spec 14.6.7 Calling hasPrevious() on closed Query will return false
                    return false;
                }

                // A List has indices starting at 0 so when we have > 0 we have a previous
                return (iterRowNum > 0);
            }
        }

        public Object next()
        {
            synchronized (ScrollableQueryResult.this)
            {
                if (!isOpen())
                {
                    // Spec 14.6.7 Calling next() on closed Query will throw NoSuchElementException
                    throw new NoSuchElementException(LOCALISER.msg("052600"));
                }

                if (!hasNext())
                {
                    throw new NoSuchElementException("No next element");
                }
                Object obj = getObjectForIndex(iterRowNum);
                iterRowNum++;

                return obj;
            }
        }

        public int nextIndex()
        {
            if (hasNext())
            {
                return iterRowNum;
            }
            return size();
        }

        public Object previous()
        {
            synchronized (ScrollableQueryResult.this)
            {
                if (!isOpen())
                {
                    // Spec 14.6.7 Calling previous() on closed Query will throw NoSuchElementException
                    throw new NoSuchElementException(LOCALISER.msg("052600"));
                }

                if (!hasPrevious())
                {
                    throw new NoSuchElementException("No previous element");
                }

                iterRowNum--;
                return getObjectForIndex(iterRowNum);
            }
        }

        public int previousIndex()
        {
            if (iterRowNum == 0)
            {
                return -1;
            }
            return iterRowNum-1;
        }

        public void remove()
        {
            throw new UnsupportedOperationException(LOCALISER.msg("052603"));
        }

        public void set(Object obj)
        {
            throw new UnsupportedOperationException(LOCALISER.msg("052603"));
        }
    }

    /**
     * Equality operator for QueryResults.
     * Overrides the AbstractList implementation since that uses
     * size() and iterator() and that would cause problems when closed.
     * @param o The object to compare against
     * @return Whether they are equal
     */
    public boolean equals(Object o)
    {
        if (o == null || !(o instanceof ScrollableQueryResult))
        {
            return false;
        }

        ScrollableQueryResult other = (ScrollableQueryResult)o;
        if (qs != null)
        {
            return other.qs == qs;
        }
        else if (query != null)
        {
            return other.query == query;
        }
        return StringUtils.toJVMIDString(other).equals(StringUtils.toJVMIDString(this));
    }

    /**
     * Method to retrieve a particular element from the list.
     * @param index The index of the element
     * @return The element at index
     */
    public synchronized Object get(int index)
    {
        assertIsOpen();
        return getObjectForIndex(index);
    }

    /**
     * Method to return the size of the result set.
     * @return The size of the result set.
     */
    public synchronized int size()
    {
        assertIsOpen();
        if (size < 0)
        {
            if (resultSizeMethod.equalsIgnoreCase("LAST"))
            {
                try
                {
                    boolean hasLast = rs.last();
                    if (!hasLast)
                    {
                        // No results in ResultSet
                        size = 0;
                    }
                    else
                    {
                        // ResultSet has results so return the row number as the list size
                        size = rs.getRow();
                    }
                }
                catch (SQLException sqle)
                {
                    throw new JDODataStoreException(LOCALISER.msg("052601", sqle));
                }
            }
            else
            {
                // TODO Support other ways of determining the size of the result set (e.g SELECT COUNT(*) ...)
                throw new JPOXUserException("JPOX doesnt currently support any method \"" + resultSizeMethod + "\" " +
                    "for determining the size of the query results");
            }

        }
        return size;
    }
}
TOP

Related Classes of org.jpox.store.rdbms.query.ScrollableQueryResult

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.