blob: d54e222d7ea31b0aa0be776852d5d9b3c8123f47 [file] [log] [blame]
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation.
* Copyright (c) 1997, 2020 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.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package com.sun.gjc.spi.jdbc40;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINEST;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.SEVERE;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.NClob;
import java.sql.ResultSet;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.sql.Struct;
import java.util.Properties;
import java.util.concurrent.Executor;
import com.sun.enterprise.util.i18n.StringManager;
import com.sun.gjc.spi.ManagedConnectionFactoryImpl;
import com.sun.gjc.spi.ManagedConnectionImpl;
import com.sun.gjc.spi.base.ConnectionHolder;
import jakarta.resource.ResourceException;
import jakarta.resource.spi.ConnectionRequestInfo;
/**
* Holds the java.sql.Connection object, which is to be passed to the
* application program.
*
* @author Jagadish Ramu
* @version 1.0, 25-Aug-2006
*/
public class ConnectionHolder40 extends ConnectionHolder {
protected final static StringManager localStrings = StringManager.getManager(ManagedConnectionFactoryImpl.class);
protected Properties defaultClientInfo;
protected boolean jdbc30Connection;
/**
* Connection wrapper given to application program
*
* @param con Connection that is wrapped
* @param mc ManagedConnection
* @param cxRequestInfo Connection Request Information
*/
public ConnectionHolder40(Connection con, ManagedConnectionImpl mc, ConnectionRequestInfo cxRequestInfo, boolean jdbc30Connection) {
super(con, mc, cxRequestInfo);
this.jdbc30Connection = jdbc30Connection;
if (!jdbc30Connection) {
init();
}
}
/**
* cache the default client info which can will set back during close()<br>
* as this connection may be re-used by connection pool of application
* server<br>
*/
protected void init() {
try {
if (isSupportClientInfo()) {
defaultClientInfo = getClientInfo();
}
} catch (Throwable e) {
_logger.log(FINEST, "jdbc.unable_to_get_client_info", e);
}
}
/**
* Constructs an object that implements the <code>Clob</code> interface. The
* object returned initially contains no data. The <code>setAsciiStream</code>,
* <code>setCharacterStream</code> and <code>setString</code> methods of the
* <code>Clob</code> interface may be used to add data to the <code>Clob</code>.
*
* @return An object that implements the <code>Clob</code> interface
* @throws java.sql.SQLException if an object that implements the
* <code>Clob</code> interface can not be constructed, this method is called on
* a closed connection or a database access error occurs.
* @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not
* support this data type
* @since 1.6
*/
@Override
public Clob createClob() throws SQLException {
checkValidity();
jdbcPreInvoke();
return connection.createClob();
}
/**
* Constructs an object that implements the <code>Blob</code> interface. The
* object returned initially contains no data. The <code>setBinaryStream</code>
* and <code>setBytes</code> methods of the <code>Blob</code> interface may be
* used to add data to the <code>Blob</code>.
*
* @return An object that implements the <code>Blob</code> interface
* @throws java.sql.SQLException if an object that implements the
* <code>Blob</code> interface can not be constructed, this method is called on
* a closed connection or a database access error occurs.
* @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not
* support this data type
* @since 1.6
*/
@Override
public Blob createBlob() throws SQLException {
checkValidity();
jdbcPreInvoke();
return connection.createBlob();
}
/**
* Constructs an object that implements the <code>NClob</code> interface. The
* object returned initially contains no data. The <code>setAsciiStream</code>,
* <code>setCharacterStream</code> and <code>setString</code> methods of the
* <code>NClob</code> interface may be used to add data to the
* <code>NClob</code>.
*
* @return An object that implements the <code>NClob</code> interface
* @throws java.sql.SQLException if an object that implements the
* <code>NClob</code> interface can not be constructed, this method is called on
* a closed connection or a database access error occurs.
* @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not
* support this data type
* @since 1.6
*/
@Override
public NClob createNClob() throws SQLException {
checkValidity();
jdbcPreInvoke();
return connection.createNClob();
}
/**
* Constructs an object that implements the <code>SQLXML</code> interface. The
* object returned initially contains no data. The
* <code>createXmlStreamWriter</code> object and <code>setString</code> method
* of the <code>SQLXML</code> interface may be used to add data to the
* <code>SQLXML</code> object.
*
* @return An object that implements the <code>SQLXML</code> interface
* @throws java.sql.SQLException if an object that implements the
* <code>SQLXML</code> interface can not be constructed, this method is called
* on a closed connection or a database access error occurs.
* @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not
* support this data type
* @since 1.6
*/
@Override
public SQLXML createSQLXML() throws SQLException {
checkValidity();
jdbcPreInvoke();
return connection.createSQLXML();
}
/**
* Returns true if the connection has not been closed and is still valid. The
* driver shall submit a query on the connection or use some other mechanism
* that positively verifies the connection is still valid when this method is
* called.
* <p/>
* The query submitted by the driver to validate the connection shall be
* executed in the context of the current transaction.
*
* @param timeout - The time in seconds to wait for the database operation used
* to validate the connection to complete. If the timeout period expires before
* the operation completes, this method returns false. A value of 0 indicates a
* timeout is not applied to the database operation.
* <p/>
* @return true if the connection is valid, false otherwise
* @throws java.sql.SQLException if the value supplied for <code>timeout</code>
* is less then 0
* @see java.sql.DatabaseMetaData#getClientInfoProperties
* @since 1.6
* <p/>
*/
@Override
public boolean isValid(int timeout) throws SQLException {
checkValidity();
return connection.isValid(timeout);
}
/**
* Sets the value of the client info property specified by name to the value
* specified by value.
* <p/>
* Applications may use the
* <code>DatabaseMetaData.getClientInfoProperties</code> method to determine the
* client info properties supported by the driver and the maximum length that
* may be specified for each property.
* <p/>
* The driver stores the value specified in a suitable location in the database.
* For example in a special register, session parameter, or system table column.
* For efficiency the driver may defer setting the value in the database until
* the next time a statement is executed or prepared. Other than storing the
* client information in the appropriate place in the database, these methods
* shall not alter the behavior of the connection in anyway. The values supplied
* to these methods are used for accounting, diagnostics and debugging purposes
* only.
* <p/>
* The driver shall generate a warning if the client info name specified is not
* recognized by the driver.
* <p/>
* If the value specified to this method is greater than the maximum length for
* the property the driver may either truncate the value and generate a warning
* or generate a <code>SQLClientInfoException</code>. If the driver generates a
* <code>SQLClientInfoException</code>, the value specified was not set on the
* connection.
* <p/>
* The following are standard client info properties. Drivers are not required
* to support these properties however if the driver supports a client info
* property that can be described by one of the standard properties, the
* standard property name should be used.
* <p/>
* <ul>
* <li>ApplicationName - The name of the application currently utilizing the
* connection</li>
* <li>ClientUser - The name of the user that the application using the
* connection is performing work for. This may not be the same as the user name
* that was used in establishing the connection.</li>
* <li>ClientHostname - The hostname of the computer the application using the
* connection is running on.</li>
* </ul>
* <p/>
*
* @param name The name of the client info property to set
* @param value The value to set the client info property to. If the value is
* null, the current value of the specified property is cleared.
* <p/>
* @throws java.sql.SQLClientInfoException if the database server returns an
* error while setting the client info value on the database server or this
* method is called on a closed connection
* <p/>
* @since 1.6
*/
@Override
public void setClientInfo(String name, String value) throws SQLClientInfoException {
try {
checkValidity();
} catch (SQLException sqe) {
SQLClientInfoException sce = new SQLClientInfoException();
sce.setStackTrace(sqe.getStackTrace());
throw sce;
}
connection.setClientInfo(name, value);
}
/**
* Sets the value of the connection's client info properties. The
* <code>Properties</code> object contains the names and values of the client
* info properties to be set. The set of client info properties contained in the
* properties list replaces the current set of client info properties on the
* connection. If a property that is currently set on the connection is not
* present in the properties list, that property is cleared. Specifying an empty
* properties list will clear all of the properties on the connection. See
* <code>setClientInfo (String, String)</code> for more information.
* <p/>
* If an error occurs in setting any of the client info properties, a
* <code>SQLClientInfoException</code> is thrown. The
* <code>SQLClientInfoException</code> contains information indicating which
* client info properties were not set. The state of the client information is
* unknown because some databases do not allow multiple client info properties
* to be set atomically. For those databases, one or more properties may have
* been set before the error occurred.
* <p/>
*
* @param properties the list of client info properties to set
* <p/>
* @throws java.sql.SQLClientInfoException if the database server returns an
* error while setting the clientInfo values on the database server or this
* method is called on a closed connection
* <p/>
* @see java.sql.Connection#setClientInfo(String,String) setClientInfo(String,
* String)
* @since 1.6
* <p/>
*/
@Override
public void setClientInfo(Properties properties) throws SQLClientInfoException {
try {
checkValidity();
} catch (SQLException sqe) {
SQLClientInfoException sce = new SQLClientInfoException();
sce.setStackTrace(sqe.getStackTrace());
throw sce;
}
connection.setClientInfo(properties);
}
/**
* Returns the value of the client info property specified by name. This method
* may return null if the specified client info property has not been set and
* does not have a default value. This method will also return null if the
* specified client info property name is not supported by the driver.
* <p/>
* Applications may use the
* <code>DatabaseMetaData.getClientInfoProperties</code> method to determine the
* client info properties supported by the driver.
* <p/>
*
* @param name The name of the client info property to retrieve
* <p/>
* @return The value of the client info property specified
* <p/>
* @throws java.sql.SQLException if the database server returns an error when
* fetching the client info value from the database or this method is called on
* a closed connection
* <p/>
* @see java.sql.DatabaseMetaData#getClientInfoProperties
* @since 1.6
* <p/>
*/
@Override
public String getClientInfo(String name) throws SQLException {
checkValidity();
return connection.getClientInfo(name);
}
/**
* Returns a list containing the name and current value of each client info
* property supported by the driver. The value of a client info property may be
* null if the property has not been set and does not have a default value.
* <p/>
*
* @return A <code>Properties</code> object that contains the name and current
* value of each of the client info properties supported by the driver.
* <p/>
* @throws java.sql.SQLException if the database server returns an error when
* fetching the client info values from the database or this method is called on
* a closed connection
* <p/>
* @since 1.6
*/
@Override
public Properties getClientInfo() throws SQLException {
checkValidity();
return connection.getClientInfo();
}
/**
* Returns true if the client info properties are supported. The application
* server calls the <code>getClientInfo</code> method and the
* <code>setClientInfo</code> method only if the driver supports the client info
* properties. The <code>DatabaseMetaData#getClientInfoProperties</code> method
* is used to determine whether the driver supports the client info properties
* or not. Note that the <code>DatabaseMetaData</code> will be cached by
* <code>ManagedConnection</code>.
* <p/>
*
* @return true if the client info properties are supported, false otherwise
*
* @throws jakarta.resource.ResourceException if the access to connection is
* failed.
*
* @throws java.sql.SQLException if the database server returns an error when
* retrieving a list of the client info properties.
*
* @see java.sql.DatabaseMetaData#getClientInfoProperties
* @since 1.6
*/
private boolean isSupportClientInfo() throws ResourceException, SQLException {
Boolean isSupportClientInfo = getManagedConnection().isClientInfoSupported();
if (isSupportClientInfo != null) {
return isSupportClientInfo;
} else {
ResultSet rs = getManagedConnection().getCachedDatabaseMetaData().getClientInfoProperties();
try {
isSupportClientInfo = rs.next();
getManagedConnection().setClientInfoSupported(isSupportClientInfo);
return isSupportClientInfo;
} finally {
try {
rs.close();
} catch (SQLException ex) {
if (_logger.isLoggable(FINEST)) {
_logger.log(FINEST, "jdbc.unable_to_get_client_info", ex);
}
return false;
}
}
}
}
/**
* Factory method for creating Array objects.
*
* @param typeName the SQL name of the type the elements of the array map to.
* The typeName is a database-specific name which may be the name of a built-in
* type, a user-defined type or a standard SQL type supported by this database.
* This is the value returned by <code>Array.getBaseTypeName</code>
* @param elements the elements that populate the returned object
* @return an Array object whose elements map to the specified SQL type
* @throws java.sql.SQLException if a database error occurs, the typeName is
* null or this method is called on a closed connection
* @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not
* support this data type
* @since 1.6
*/
@Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
checkValidity();
jdbcPreInvoke();
return connection.createArrayOf(typeName, elements);
}
/**
* Factory method for creating Struct objects.
*
* @param typeName the SQL type name of the SQL structured type that this
* <code>Struct</code> object maps to. The typeName is the name of a
* user-defined type that has been defined for this database. It is the value
* returned by <code>Struct.getSQLTypeName</code>.
* @param attributes the attributes that populate the returned object
* @return a Struct object that maps to the given SQL type and is populated with
* the given attributes
* @throws java.sql.SQLException if a database error occurs, the typeName is
* null or this method is called on a closed connection
* @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not
* support this data type
* @since 1.6
*/
@Override
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
checkValidity();
jdbcPreInvoke();
return connection.createStruct(typeName, attributes);
}
/**
* Returns an object that implements the given interface to allow access to
* non-standard methods, or standard methods not exposed by the proxy.
* <p/>
* If the receiver implements the interface then the result is the receiver or a
* proxy for the receiver. If the receiver is a wrapper and the wrapped object
* implements the interface then the result is the wrapped object or a proxy for
* the wrapped object. Otherwise return the the result of calling
* <code>unwrap</code> recursively on the wrapped object or a proxy for that
* result. If the receiver is not a wrapper and does not implement the
* interface, then an <code>SQLException</code> is thrown.
*
* @param iface A Class defining an interface that the result must implement.
* @return an object that implements the interface. May be a proxy for the
* actual implementing object.
* @throws java.sql.SQLException If no object found that implements the
* interface
* @since 1.6
*/
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
checkValidity();
if (iface.isInstance(this)) { // if iface is "java.sql.Connection"
return iface.cast(this);
}
if (iface.isInstance(connection)) {
// If iface is not "java.sql.Connection" & implemented by native Connection
@SuppressWarnings("unchecked")
Class<T>[] listIntf = new Class[] { iface };
return getProxyObject(connection, listIntf);
}
// Probably a proxy, delegating to native connection
T result = connection.unwrap(iface);
if (Connection.class.isInstance(result)) {
// rare case : returned object implements both iface & java.sql.Connection
@SuppressWarnings("unchecked")
Class<T> listIntf[] = new Class[] { iface, Connection.class };
result = getProxyObject(result, listIntf);
}
return result;
}
/**
* @param actualObject Object from jdbc vendor connection's unwrap
* @param ifaces Interfaces for which proxy is needed
* @return Proxy class implmenting the interfaces
* @throws SQLException
*/
@SuppressWarnings("unchecked")
private <T> T getProxyObject(final Object actualObject, Class<T>[] ifaces) throws SQLException {
InvocationHandler invocationHandler;
try {
invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws SQLException, IllegalAccessException, InvocationTargetException {
// When close() is called on proxy object, call close() on resource adapter's
// Connection Holder instead of physical connection.
if (method.getName().equals("close") && method.getParameterTypes().length == 0) {
if (_logger.isLoggable(FINE)) {
_logger.log(FINE, localStrings.getString("jdbc.close_called_on_proxy_object", actualObject));
}
ConnectionHolder40.this.close();
return null;
}
// default
return method.invoke(actualObject, args);
}
};
} catch (Exception e) {
throw new SQLException(e.fillInStackTrace());
}
return (T) Proxy.newProxyInstance(actualObject.getClass().getClassLoader(), ifaces, invocationHandler);
}
/**
* Returns true if this either implements the interface argument or is directly
* or indirectly a wrapper for an object that does. Returns false otherwise. If
* this implements the interface then return true, else if this is a wrapper
* then return the result of recursively calling <code>isWrapperFor</code> on
* the wrapped object. If this does not implement the interface and is not a
* wrapper, return false. This method should be implemented as a low-cost
* operation compared to <code>unwrap</code> so that callers can use this method
* to avoid expensive <code>unwrap</code> calls that may fail. If this method
* returns true then calling <code>unwrap</code> with the same argument should
* succeed.
*
* @param iface a Class defining an interface.
* @return true if this implements the interface or directly or indirectly wraps
* an object that does.
* @throws java.sql.SQLException if an error occurs while determining whether
* this is a wrapper for an object with the given interface.
* @since 1.6
*/
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
checkValidity();
if (iface.isInstance(this)) {
return true;
}
return connection.isWrapperFor(iface);
}
/**
* Closes the logical connection.<br>
* Cleans up client specific details<br>
*
* @throws SQLException In case of a database error.
*/
@Override
public void close() throws SQLException {
if (isClosed) {
_logger.log(FINE, "jdbc.duplicate_close_connection", this);
return;
}
if (!jdbc30Connection) {
try {
checkValidity();
if (isSupportClientInfo()) {
if (defaultClientInfo == null) {
setClientInfo(new Properties());
} else {
setClientInfo(defaultClientInfo);
}
}
} catch (Throwable e) {
_logger.log(FINEST, "jdbc.unable_to_set_client_info", e);
}
}
super.close();
}
@Override
public void setSchema(String schema) throws SQLException {
checkValidity();
Class<?>[] valueTypes = new Class<?>[] { String.class };
try {
getMethodExecutor().invokeMethod(connection, "setSchema", valueTypes, schema);
} catch (ResourceException ex) {
_logger.log(SEVERE, "jdbc.ex_connection_holder", ex);
throw new SQLException(ex);
}
return;
}
@Override
public String getSchema() throws SQLException {
checkValidity();
try {
return (String) getMethodExecutor().invokeMethod(connection, "getSchema", null);
} catch (ResourceException ex) {
_logger.log(SEVERE, "jdbc.ex_connection_holder", ex);
throw new SQLException(ex);
}
}
@Override
public void setNetworkTimeout(Executor executorObj, int milliseconds) throws SQLException {
checkValidity();
Class<?>[] valueTypes = new Class<?>[] { Executor.class, Integer.TYPE };
try {
getMethodExecutor().invokeMethod(connection, "setNetworkTimeout", valueTypes, executorObj, milliseconds);
} catch (ResourceException ex) {
_logger.log(SEVERE, "jdbc.ex_connection_holder", ex);
throw new SQLException(ex);
}
return;
}
@Override
public int getNetworkTimeout() throws SQLException {
checkValidity();
try {
return (Integer) getMethodExecutor().invokeMethod(connection, "getNetworkTimeout", null);
} catch (ResourceException ex) {
_logger.log(SEVERE, "jdbc.ex_connection_holder", ex);
throw new SQLException(ex);
}
}
/**
* Abort operation to mark the connection internally as a bad connection for
* removal and to close the connection. This ensures that at the end of the
* transaction, the connection is destroyed. A running thread holding a
* connection will run to completion before the connection is destroyed
*
* @param executor
* @throws SQLException
*/
@Override
public void abort(Executor executor) throws SQLException {
getManagedConnection().markForRemoval(true);
getManagedConnection().setAborted(true);
if (!getManagedConnection().isTransactionInProgress()) {
close();
}
}
}