blob: 18b4948fba5331000a482ddaf7ad42c562a5347c [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.eis;
import jakarta.resource.ResourceException;
import jakarta.resource.cci.Connection;
import jakarta.resource.cci.ConnectionMetaData;
import jakarta.resource.cci.Interaction;
import jakarta.resource.cci.InteractionSpec;
import jakarta.resource.cci.Record;
import jakarta.resource.cci.RecordFactory;
import org.eclipse.persistence.eis.interactions.EISInteraction;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.internal.databaseaccess.DatasourceAccessor;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.queries.Call;
import org.eclipse.persistence.sessions.SessionProfiler;
/**
* <p><code>EISAccessor</code> is an implementation of the <code>Accessor</code>
* interface. It is responsible for:
* <ul>
* <li>Connecting via connection factory
* <li>Local transactions
* <li>Interaction execution
* <li>Record translation
* </ul>
*
* @see EISInteraction
* @see EISLogin
*
* @author James
* @since OracleAS TopLink 10<i>g</i> (10.0.3)
*/
public class EISAccessor extends DatasourceAccessor {
protected Connection cciConnection;
protected RecordFactory recordFactory;
/**
* Default Constructor.
*/
public EISAccessor() {
super();
}
/**
* Begin a local transaction.
*/
@Override
protected void basicBeginTransaction(AbstractSession session) throws EISException {
try {
if (getEISPlatform().supportsLocalTransactions()) {
getCCIConnection().getLocalTransaction().begin();
}
} catch (ResourceException exception) {
throw EISException.resourceException(exception, this, session);
}
}
/**
* Close the connection.
*/
@Override
protected void closeDatasourceConnection() {
try {
getCCIConnection().close();
} catch (ResourceException exception) {
throw EISException.resourceException(exception, this, null);
}
}
/**
* Commit the local transaction.
*/
@Override
protected void basicCommitTransaction(AbstractSession session) throws EISException {
try {
if (getEISPlatform().supportsLocalTransactions()) {
getCCIConnection().getLocalTransaction().commit();
}
} catch (ResourceException exception) {
throw EISException.resourceException(exception, this, session);
}
}
/**
* If logging is turned on and the CCI implementation supports meta data then display connection info.
*/
@Override
protected void buildConnectLog(AbstractSession session) {
try {
// Log connection information.
if (session.shouldLog(SessionLog.CONFIG, SessionLog.CONNECTION)) {// Avoid printing if no logging required.
ConnectionMetaData metaData = getCCIConnection().getMetaData();
Object[] args = { metaData.getUserName(), metaData.getEISProductName(), metaData.getEISProductVersion(), Helper.cr(), "\t" };
session.log(SessionLog.CONFIG, SessionLog.CONNECTION, "connected_user_database", args, this);
}
} catch (ResourceException exception) {
// Some databases do not support metadata, ignore exception.
session.warning("JDBC_driver_does_not_support_meta_data", SessionLog.CONNECTION);
}
}
/**
* Avoid super to have logging occur after possible manual auto-commit.
*/
@Override
public Object executeCall(Call call, AbstractRecord translationRow, AbstractSession session) throws DatabaseException {
return basicExecuteCall(call, translationRow, session);
}
/**
* Execute the interaction.
* The execution can differ slightly depending on the type of interaction.
* The call may be parameterized where the arguments are in the translation row.
* The row will be empty if there are no parameters.
* @return depending of the type either the row count, row or vector of rows.
*/
@Override
public Object basicExecuteCall(Call call, AbstractRecord translationRow, AbstractSession session) throws DatabaseException {
// If the login is null, then this accessor has never been connected.
if (getLogin() == null) {
throw DatabaseException.databaseAccessorNotConnected();
}
Interaction interaction = null;
Object result = null;
EISInteraction eisCall = null;
try {
eisCall = (EISInteraction)call;
} catch (ClassCastException e) {
throw QueryException.invalidDatabaseCall(call);
}
// Record and check if auto-commit is required.
// Some platforms may require this (AQ).
boolean autoCommit = (!isInTransaction()) && getEISPlatform().requiresAutoCommit();
if (autoCommit) {
beginTransaction(session);
}
try {
if (session.shouldLog(SessionLog.FINE, SessionLog.SQL)) {// pre-check to improve performance
session.log(SessionLog.FINE, SessionLog.SQL, call.getLogString(this), null, this, false);
}
incrementCallCount(session);
session.startOperationProfile(SessionProfiler.SqlPrepare, eisCall.getQuery(), SessionProfiler.ALL);
Record input = null;
Record output = null;
try {
interaction = getCCIConnection().createInteraction();
input = getEISPlatform().createInputRecord(eisCall, this);
output = getEISPlatform().createOutputRecord(eisCall, translationRow, this);
} finally {
session.endOperationProfile(SessionProfiler.SqlPrepare, eisCall.getQuery(), SessionProfiler.ALL);
}
session.startOperationProfile(SessionProfiler.StatementExecute, eisCall.getQuery(), SessionProfiler.ALL);
try {
boolean success = true;
InteractionSpec interactionSpec = getEISPlatform().buildInteractionSpec(eisCall);
if (output == null) {
output = interaction.execute(interactionSpec, input);
} else {
success = interaction.execute(interactionSpec, input, output);
}
session.log(SessionLog.FINEST, SessionLog.QUERY, "adapter_result", output);
if (eisCall.isNothingReturned()) {
if (success) {
result = 1;
} else {
result = 0;
}
// Fire the output parameter row to allow app to handle return value.
if (output != null) {
AbstractRecord outputRow = getEISPlatform().buildRow(output, eisCall, this);
if (outputRow != null) {
eisCall.getQuery().setProperty("output", outputRow);
if (session.hasEventManager()) {
session.getEventManager().outputParametersDetected(outputRow, eisCall);
}
}
}
} else if (eisCall.isOneRowReturned()) {
result = getEISPlatform().buildRow(output, eisCall, this);
} else {
result = getEISPlatform().buildRows(output, eisCall, this);
}
session.log(SessionLog.FINEST, SessionLog.QUERY, "data_access_result", output);
} finally {
session.endOperationProfile(SessionProfiler.StatementExecute, eisCall.getQuery(), SessionProfiler.ALL);
}
} catch (ResourceException exception) {
// Ensure each resource is released, but still ensure that the real exception is thrown.
if (interaction != null) {
try {
interaction.close();
} catch (Exception closeException) {
// Ignore error to avoid masking real exception.
}
}
try {
decrementCallCount();
} catch (Exception closeException) {
// Ignore error to avoid masking real exception.
}
try {
if (autoCommit) {
commitTransaction(session);
}
} catch (Exception closeException) {
// Ignore error to avoid masking real exception.
}
throw EISException.resourceException(exception, call, this, session);
} catch (RuntimeException exception) {
try {// Ensure that the statement is closed, but still ensure that the real exception is thrown.
try {
if (interaction != null) {
interaction.close();
}
} finally {
if (autoCommit) {
commitTransaction(session);
}
}
} catch (Exception closeException) {
}
throw exception;
}
boolean transactionCommitted = false;
boolean countDecremented = false;
// This is in separate try block to ensure that the real exception is not masked by the close exception.
try {
interaction.close();
if (autoCommit) {
commitTransaction(session);
}
transactionCommitted = true;
decrementCallCount();
countDecremented = true;
} catch (ResourceException exception) {
try {
if (!transactionCommitted) {
if (autoCommit) {
commitTransaction(session);
}
}
} catch (Exception ignore) {
// Ignore error to avoid masking real exception.
}
try {
if (!countDecremented) {
decrementCallCount();
}
} catch (Exception ignore) {
// Ignore error to avoid masking real exception.
}
throw EISException.resourceException(exception, this, session);
}
return result;
}
/**
* Return the CCI connection to the EIS resource adapter.
*/
public Connection getCCIConnection() {
return (Connection)getDatasourceConnection();
}
/**
* Return and cast the platform.
*/
public EISPlatform getEISPlatform() {
return (EISPlatform)getDatasourcePlatform();
}
/**
* Return the RecordFactory.
* The record factory is acquired from the ConnectionManager,
* and used to create record to pass to interactions.
*/
public RecordFactory getRecordFactory() {
return recordFactory;
}
/**
* Set the RecordFactory.
* The record factory is acquired from the ConnectionManager,
* and used to create record to pass to interactions.
*/
public void setRecordFactory(RecordFactory recordFactory) {
this.recordFactory = recordFactory;
}
/**
* Rollback the local transaction on the datasource.
*/
@Override
public void basicRollbackTransaction(AbstractSession session) throws DatabaseException {
try {
if (getEISPlatform().supportsLocalTransactions()) {
getCCIConnection().getLocalTransaction().rollback();
}
} catch (ResourceException exception) {
throw EISException.resourceException(exception, this, session);
}
}
/**
* Return if the connection to the "data source" is connected.
*/
@Override
protected boolean isDatasourceConnected() {
return isConnected;
}
}