class ConnectionInvocationHandler implements InvocationHandler {

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    checkNotNull(method);

    // Extract the sql text when creating a CallableStatement or a PreparedStatement and then pass
    // it to the StatementInvocationHandler which will annotate the trace span with this sql text.
    Optional<String> sql;
    if ((method.getName().equals("prepareCall") || method.getName().equals("prepareStatement"))
        && method.getParameterTypes().length > 0
        && method.getParameterTypes()[0] == String.class) {
      sql = Optional.fromNullable((String) args[0]);
    } else {
      sql = Optional.absent();
    }

    Object o;
    try {
      o = method.invoke(conn, args);
    } catch (InvocationTargetException e) {
      // Rethrow the exception from the underlying method.
      throw e.getCause();
    }

    if (o instanceof Statement) {
      Statement stmt = (Statement) o;
      o =
          Proxies.newProxyInstance(
              stmt, new StatementInvocationHandler(stmt, traceOptions, traceService, sql));
    }

    return o;
  }

    ConnectionInvocationHandler(
      Connection conn, TraceOptions traceOptions, TraceService traceService);

}