/*******************************************************************************
* Copyright (c) 1998, 2008 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.queries;
import java.sql.*;
import java.util.*;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.expressions.*;
import org.eclipse.persistence.internal.databaseaccess.*;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
/**
* <p><b>Purpose</b>:
* Abstract class for CursoredStream and ScrolableCursor
*/
public abstract class Cursor implements Enumeration, java.io.Serializable {
/** The preparedStatement that holds the handle to the database that the results are read from. */
protected transient Statement statement;
/** The result set (cursor) that holds the handle to the database that the results are read from. */
protected transient ResultSet resultSet;
/** The session that executed the query for the stream. */
protected transient AbstractSession session;
/** The root session that executed the call for the query. Knows the database
* platform. */
protected transient AbstractSession executionSession;
/** The fields expected in the result set. */
protected transient Vector fields;
/** Cached size of the stream. */
protected int size = -1;
/** Object-level read query that initialize the stream. */
public transient CursorPolicy policy;
/** Internal collection of objects. */
protected Vector objectCollection;
/** Conforming instances found in memory when building the result. */
protected Map initiallyConformingIndex;
/** SelectionCriteria & translation row ready for incremental conforming. */
protected Expression selectionCriteriaClone;
protected AbstractRecord translationRow;
/** Current position in the objectCollection of the stream. */
protected int position;
/**
* INTERNAL:
* Default constructor.
*/
public Cursor() {
super();
}
/**
* INTERNAL:
*/
public Cursor(DatabaseCall call, CursorPolicy policy) {
this.session = policy.getQuery().getSession();
this.executionSession = session.getExecutionSession(policy.getQuery());
this.statement = call.getStatement();
this.fields = call.getFields();
this.resultSet = call.getResult();
this.policy = policy;
setObjectCollection(new Vector());
if (getQuery().getSession().isUnitOfWork() && getQuery().isObjectLevelReadQuery()) {
// Call register on the cursor itself. This will set up
// incremental conforming by setting the
// selection criteria clone and arguments, and building the
// intially conforming index (scans the UOW cache).
// The incremental registration/conforming is done
// in retrieveNext/PreviousObject -> buildAndRegisterObject
ObjectLevelReadQuery query = (ObjectLevelReadQuery)getQuery();
query.registerResultInUnitOfWork(this, (UnitOfWorkImpl)this.session, getQuery().getTranslationRow(), false);// object collection is empty, so setting irrelevant.
}
}
/**
* PUBLIC:
* Closes the stream.
* This should be performed whenever the user has finished with the stream.
*/
public void close() throws DatabaseException {
RuntimeException exception = null;
try {
if (isClosed()) {
return;
}
try {
getAccessor().closeCursor(getResultSet(), getSession());
getAccessor().closeStatement(getStatement(), getSession(), null);
} catch (RuntimeException caughtException) {
exception = caughtException;
} finally {
//release the connection (back into the pool if Three tier)
try {
//bug 4668234 -- used to only release connections on server sessions but should always release
getSession().releaseReadConnection(getQuery().getAccessor());
} catch (RuntimeException releaseException) {
if (exception == null) {
throw releaseException;
}
//else ignore
}
if (exception != null) {
throw exception;
}
}
setResultSet(null);
} catch (SQLException sqlException) {
throw DatabaseException.sqlException(sqlException, getAccessor(), getSession(), false);
}
}
/**
* Close in case not closed.
*/
protected void finalize() throws DatabaseException {
close();
}
/**
* INTERNAL:
* Return the accessor associated with the cursor.
*/
protected DatabaseAccessor getAccessor() {
// Assume we have a JDBC accessor
try {
return (DatabaseAccessor)getQuery().getAccessor();
} catch (ClassCastException e) {
throw QueryException.invalidDatabaseAccessor(getQuery().getAccessor());
}
}
/**
* INTERNAL:
* Retreive the size of the open cursor by executing a count on the same query as the cursor.
*/
protected abstract int getCursorSize() throws DatabaseException, QueryException;
/**
* INTERNAL:
* Return the fields for the stream
*/
protected Vector getFields() {
return fields;
}
/**
* INTERNAL:
* Conforming instances found in memory when building the result.
* These objects are returned first by the cursor, and a fast lookup
* is needed to make sure the same objects appearing in the cursor are
* filtered out.
*/
public Map getInitiallyConformingIndex() {
return initiallyConformingIndex;
}
/**
* INTERNAL:
* Return the internal object collection that stores the objects.
*/
public Vector getObjectCollection() {
return objectCollection;
}
/**
* INTERNAL:
* Return the number of items to be faulted in for the stream.
*/
public int getPageSize() {
return getPolicy().getPageSize();
}
/**
* INTERNAL:
* Return the cursor policy.
*/
public CursorPolicy getPolicy() {
return policy;
}
/**
* INTERNAL:
* Return the position of the stream inside the object collection
*/
public abstract int getPosition();
/**
* INTERNAL:
* Return the query associated with the stream
*/
public ReadQuery getQuery() {
return getPolicy().getQuery();
}
/**
* INTERNAL:
* Return the result set (cursor)
*/
public ResultSet getResultSet() {
return resultSet;
}
/**
* INTERNAL:
* The clone of the selection criteria is needed for in-memory conforming
* each object read from the Cursor.
*/
public Expression getSelectionCriteriaClone() {
return selectionCriteriaClone;
}
/**
* INTERNAL:
* Return the handle to the session
*/
public AbstractSession getSession() {
return session;
}
/**
* INTERNAL:
* Returns the session the underlying call was executed on. This root
* session knows the database platform.
*/
protected AbstractSession getExecutionSession() {
return executionSession;
}
/**
* INTERNAL:
* Return the Statement
*/
protected Statement getStatement() {
return statement;
}
/**
* INTERNAL:
* Gets the translation row the query was executed with, used for incremental
* conforming.
*/
protected AbstractRecord getTranslationRow() {
return translationRow;
}
/**
* PUBLIC:
* Return if the stream is closed.
*/
public boolean isClosed() {
return (getResultSet() == null);
}
/**
* INTERNAL:
* builds and registers an object from a row for cursors.
* Behavior is different from the query version in that refreshing is not
* supported.
*/
protected Object buildAndRegisterObject(AbstractRecord row) {
Object object = null;
if (getSession().isUnitOfWork() && (!getQuery().isReportQuery()) && (getQuery().shouldMaintainCache()) && (getQuery().isObjectLevelReadQuery())) {
UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)getSession();
ObjectLevelReadQuery query = (ObjectLevelReadQuery)getQuery();
if (query.shouldConformResultsInUnitOfWork() || query.getDescriptor().shouldAlwaysConformResultsInUnitOfWork()) {
object = query.conformIndividualResult(row, unitOfWork, getTranslationRow(), getSelectionCriteriaClone(), getInitiallyConformingIndex(), true);
// Notifies caller to continue until conforming instance found
if (object == null) {
return InvalidObject.instance;
}
} else {
// This will now automatically clone the result.
object = query.buildObject(row);
}
} else {
object = getQuery().buildObject(row);
}
return object;
}
/**
* INTERNAL:
* Read the next row from the result set.
*/
protected Object retrieveNextObject() throws DatabaseException {
while (true) {
if (isClosed()) {
return null;
}
AbstractRecord row = getAccessor().cursorRetrieveNextRow(getFields(), getResultSet(), getExecutionSession());
if (row == null) {
if (!(this instanceof ScrollableCursor)) {
close();
}
return null;
}
Object object = buildAndRegisterObject(row);
if (object == InvalidObject.instance) {
continue;
}
return object;
}
}
/**
* INTERNAL:
* CR#4139
* Read the previous row from the result set. It is used solely
* for scrollable cursor support.
*/
protected Object retrievePreviousObject() throws DatabaseException {
while (true) {
if (isClosed()) {
return null;
}
AbstractRecord row = getAccessor().cursorRetrievePreviousRow(getFields(), getResultSet(), getExecutionSession());
if (row == null) {
if (!(this instanceof ScrollableCursor)) {
close();
}
return null;
}
Object object = buildAndRegisterObject(row);
if (object == InvalidObject.instance) {
continue;
}
return object;
}
}
/**
* INTERNAL:
* Set the fields for the stream
*/
protected void setFields(Vector fields) {
this.fields = fields;
}
/**
* INTERNAL:
* Conforming instances found in memory when building the result.
* These objects are returned first by the cursor, and a fast lookup
* is needed to make sure the same objects appearing in the cursor are
* filtered out.
*/
public void setInitiallyConformingIndex(Map index) {
this.initiallyConformingIndex = index;
}
/**
* INTERNAL:
* Set the internal object collection
*/
public void setObjectCollection(Vector collection) {
objectCollection = collection;
}
/**
* INTERNAL:
* Set the cursor policy.
*/
public void setPolicy(CursorPolicy policy) {
this.policy = policy;
}
/**
* INTERNAL:
* Set the current position of the stream
*/
protected void setPosition(int value) {
position = value;
}
/**
* INTERNAL:
* Set the result set (cursor)
*/
protected void setResultSet(ResultSet result) {
resultSet = result;
}
/**
* INTERNAL:
* The clone of the selection criteria is needed for in-memory conforming
* each object read from the Cursor.
*/
public void setSelectionCriteriaClone(Expression expression) {
this.selectionCriteriaClone = expression;
}
/**
* INTERNAL:
* Set the session handle
*/
public void setSession(AbstractSession databaseSession) {
session = databaseSession;
}
/**
* INTERNAL:
* Sets the session the underlying call was executed on. This root
* session knows the database platform.
*/
protected void setExecutionSession(AbstractSession executionSession) {
this.executionSession = executionSession;
}
/**
* INTERNAL:
* Set the cache size
*/
public void setSize(int size) {
this.size = size;
}
/**
* INTERNAL:
* Sets the translation row this query was executed with. Used for
* incremental conforming.
*/
public void setTranslationRow(AbstractRecord row) {
this.translationRow = row;
}
/**
* PUBLIC:
* Retrieve the size of the open cursor by executing a count on the same query as the cursor.
*
* If this cursor is conforming size() can only be an estimate. cursor size
* plus number of conforming instances found in memory will be returned. The
* union (actual result) may be smaller than this.
*/
public int size() throws DatabaseException {
if (size == -1) {
size = getCursorSize();
if (getInitiallyConformingIndex() != null) {
size += getInitiallyConformingIndex().size();
}
}
return size;
}
}