All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.senzing.sql.PooledConnectionHandler Maven / Gradle / Ivy

package com.senzing.sql;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * Provides an {@link InvocationHandler} that prevents the returning the
 * backing JDBC {@link Connection} and ensures closing of the proxied
 * {@link Connection} does not actually close the backing JDBC {@link
 * Connection}, but releases it back to the {@link ConnectionPool}.
 */
class PooledConnectionHandler implements InvocationHandler {
  /**
   * Constant for converting between nanoseconds and milliseconds.
   */
  private static final long ONE_MILLION = 1000000L;

  /**
   * The backing {@link ConnectionPool}.
   */
  private ConnectionPool pool = null;

  /**
   * The backing {@link Connection}.
   */
  private final Connection backingConnection;

  /**
   * The proxied {@link Connection}.
   */
  private final Connection proxiedConnection;

  /**
   * The backing object for sub-objects that are not {@link Connection}
   * instances.
   */
  private final Object backingObject;

  /**
   * Flag indicating if we are closed.
   */
  private boolean closed = false;

  /**
   * The time this instance was created.
   */
  private long createdTimeNanos = 0L;

  /**
   * The time when the connection was closed (only matters for the parent
   * handler).
   */
  private long closedTimeNanos = -1L;

  /**
   * Constructs with the backing {@link ConnectionPool}, backing
   * {@link Connection}.  This constructor will create a {@link Proxy}
   * {@link Connection] which can be obtained via {@link
   * #getProxiedConnection()}.
   *
   * @param pool The backing {@link ConnectionPool}.
   * @param backingConnection The backing {@link Connection}.
   */
  PooledConnectionHandler(ConnectionPool  pool,
                          Connection      backingConnection)
  {
    Class[] interfaces = { Connection.class };

    this.createdTimeNanos   = System.nanoTime();
    this.pool               = pool;
    this.backingConnection  = backingConnection;
    this.backingObject      = backingConnection;
    this.closed             = false;
    this.proxiedConnection  = (Connection) Proxy.newProxyInstance(
        this.getClass().getClassLoader(), interfaces, this);
  }

  /**
   * Internal constructor for handling proxies of other JDBC objects returned
   * from the {@link Connection} that might provide a way to return the
   * backing {@link Connection}.
   *
   * @param parentHandler The {@link PooledConnectionHandler} that is the parent
   *                      for this one.
   * @param backingObject The backing JDBC {@link Object} for this handler.
   */
  private PooledConnectionHandler(PooledConnectionHandler parentHandler,
                                  Object                  backingObject)
  {
    Class[] interfaces = { Connection.class };

    this.createdTimeNanos   = System.nanoTime();
    this.pool               = parentHandler.pool;
    this.backingConnection  = parentHandler.backingConnection;
    this.proxiedConnection  = parentHandler.proxiedConnection;
    this.closed             = false;
    this.backingObject      = backingObject;
  }

  /**
   * Gets the backing {@link ConnectionPool} for this instance.
   *
   * @return The backing {@link ConnectionPool} for this instance.
   */
  ConnectionPool getPool() {
    return this.pool;
  }

  /**
   * Returns the proxied {@link Connection} that was created when this
   * instance was constructed.
   *
   * @return The proxied {@link Connection} that was created when this
   *         instance was constructed.
   */
  Connection getProxiedConnection() {
    return this.proxiedConnection;
  }

  /**
   * Checks if the handler instance has been marked closed.
   *
   * @return true if closed, otherwise false.
   */
  synchronized boolean isClosed() {
    return this.closed;
  }

  /**
   * Sets this instance as closed.
   */
  synchronized void markClosed() {
    if (this.closed) return;
    this.closed           = true;
    this.closedTimeNanos  = System.nanoTime();
  }

  /**
   * Returns the amount of time in milliseconds that the backing {@link
   * Connection} has been checked if this is the handler for that {@link
   * Connection}.  This returns null if this handler is not the
   * handler for the {@link Connection}.  If this is the handler for the
   * {@link Connection}, but it is not yet closed, it returns the time so far.
   *
   * @return The {@link Connection} lease time if known, otherwise
   *         null.
   */
  synchronized Long getLeaseTime() {
    // check if closing has been recorded
    if (this.closedTimeNanos > 0L) {
      return (this.closedTimeNanos - this.createdTimeNanos) / ONE_MILLION;
    }

    // check if this is the handler for the connection
    if (this.backingConnection == this.backingObject) {
      return (System.nanoTime() - this.createdTimeNanos) / ONE_MILLION;
    }

    // if we get here then return null
    return null;
  }

  /**
   * Overridden to invoke the method being called with special handling to
   * ensure the backing JDBC {@link Connection} is not returned, but rather
   * the proxy of it is returned.  When the proxied {@link Connection} is
   * closed then the backing {@link Connection} is returned to the backing
   * {@link ConnectionPool}.
   *
   * @param proxy The proxy object.
   * @param method The {@link Method} being invoked.
   * @param args The arguments to the method invocation.
   * @return The result from invoking the method.
   * @throws Throwable If a failure occurs.
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable
  {
    // initialize the result
    Object result = null;

    // check if closing the connection
    if (proxy == this.proxiedConnection) {
      // check if closed
      boolean open = (! this.isClosed());

      String methodName = method.getName();
      switch (methodName) {
        case "close":
          // check if already closed
          if (!open) return null;

          // release the connection back to the pool -- noop if already released
          this.pool.release(this.proxiedConnection);

          // update the state
          this.markClosed();

          break;
        case "isClosed":
          // return whether (or not) the connection is closed
          synchronized (this) {
            return (this.closed);
          }

        default:
          // check if the connection is already closed
          if (open) {
            try {
              // if not closed then delegate to the backing object
              result = method.invoke(this.backingObject, args);
            } catch (InvocationTargetException e) {
              throw e.getTargetException();
            }
          } else {
            // if closed then throw an exception
            throw new SQLException("Connection already closed.");
          }
      }
    } else {
      // just call the method on the backing object
      try {
        result = method.invoke(this.backingObject, args);
      } catch (InvocationTargetException e) {
        throw e.getTargetException();
      }
    }

    // check the result
    if (result == this.backingConnection) {
      return this.proxiedConnection;
    }

    // check if the return type is an interface
    Class returnType = method.getReturnType();
    if (returnType.isInterface()
        && "java.sql".equals(returnType.getPackageName()))
    {
      // create a sub-handler to handle the proxying the result
      InvocationHandler subHandler
          = new PooledConnectionHandler(this, result);

      // create the array of interfaces to proxy
      Class[] interfaces = { method.getReturnType() };

      // proxy the result if it is an interface
      return Proxy.newProxyInstance(
          this.getClass().getClassLoader(), interfaces, subHandler);
    }

    // if we get here then just return the result
    return result;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy