示例#1
0
/**
 * The root of the persistence model.
 *
 * @author hceylan
 * @since $version
 */
public class MetamodelImpl implements Metamodel {

  private static class GeneratorThreadFactory implements ThreadFactory {

    private static volatile int nextThreadNo = 1;

    /** {@inheritDoc} */
    @Override
    public Thread newThread(Runnable r) {
      final Thread thread =
          new Thread(r, "Id Generator - " + GeneratorThreadFactory.nextThreadNo++);

      thread.setPriority(Thread.MAX_PRIORITY);

      return thread;
    }
  }

  private static final BLogger LOG = BLoggerFactory.getLogger(MetamodelImpl.class);

  // TODO Consider making this configurable
  private static final long POLL_TIMEOUT = 60;

  private EntityManagerFactoryImpl emf;
  private final JdbcAdaptor jdbcAdaptor;

  private final Map<Class<?>, BasicTypeImpl<?>> basics = Maps.newHashMap();
  private final Map<Class<?>, MappedSuperclassType<?>> mappedSuperclasses = Maps.newHashMap();
  private final Map<Class<?>, EmbeddableType<?>> embeddables = Maps.newHashMap();
  private final Map<Class<?>, EntityTypeImpl<?>> entities = Maps.newHashMap();
  private final Map<String, EntityTypeImpl<?>> entitiesByName = Maps.newHashMap();
  private final Map<String, NamedQueryMetadata> namedQueries = Maps.newHashMap();

  private final CallbackManager callbackManager;

  private final Map<String, SequenceGenerator> sequenceGenerators = Maps.newHashMap();
  private final Map<String, TableGenerator> tableGenerators = Maps.newHashMap();

  private final Map<String, SequenceQueue> sequenceQueues = Maps.newHashMap();
  private final Map<String, TableIdQueue> tableIdQueues = Maps.newHashMap();

  private ThreadPoolExecutor idGeneratorExecuter;

  /**
   * @param entityManagerFactory the entity manager factory
   * @param jdbcAdaptor the JDBC Adaptor
   * @param metadata the metadata
   * @since $version
   * @author hceylan
   */
  @SuppressWarnings({"rawtypes", "unchecked"})
  public MetamodelImpl(
      EntityManagerFactoryImpl entityManagerFactory,
      JdbcAdaptor jdbcAdaptor,
      MetadataImpl metadata) {
    super();

    this.emf = entityManagerFactory;
    this.jdbcAdaptor = jdbcAdaptor;

    final List<ManagedTypeMetadata> entities = Lists.newArrayList(metadata.getEntityMappings());
    final List<ManagedTypeMetadata> sortedEntities = Lists.newArrayList();

    // sort so that the embeddables are first
    Collections.sort(
        entities,
        new Comparator<ManagedTypeMetadata>() {

          @Override
          public int compare(ManagedTypeMetadata o1, ManagedTypeMetadata o2) {
            if (o1 instanceof EmbeddableMetadata) {
              return -1;
            }

            if (o2 instanceof EmbeddableMetadata) {
              return 1;
            }

            return 0;
          }
        });

    // sort by inheritance
    try {
      while (entities.size() > 0) {
        for (final Iterator<ManagedTypeMetadata> i = entities.iterator(); i.hasNext(); ) {
          final ManagedTypeMetadata entity = i.next();
          final Class<?> c1 = this.emf.getClassloader().loadClass(entity.getClassName());

          boolean independent = true;
          for (final ManagedTypeMetadata entity2 : entities) {
            if (entity == entity2) {
              continue;
            }

            final Class<?> c2 = this.emf.getClassloader().loadClass(entity2.getClassName());

            if (c2.isAssignableFrom(c1)) {
              independent = false;
              break;
            }
          }

          if (independent) {
            i.remove();
            sortedEntities.add(entity);
          }
        }
      }
    } catch (final Exception e) {
    } // not possible at this stage

    for (final ManagedTypeMetadata type : sortedEntities) {
      try {
        final Class<?> clazz = this.emf.getClassloader().loadClass(type.getClassName());

        ManagedTypeImpl<?> parent = null;

        // locate the parent
        Class<?> currentClass = clazz.getSuperclass();
        while ((currentClass != Object.class) && (parent == null)) {
          parent = this.managedType(currentClass);
          currentClass = currentClass.getSuperclass();
        }

        if (type instanceof EntityMetadata) {
          // make sure it extends an identifiable type
          if ((parent != null) && !(parent instanceof IdentifiableTypeImpl)) {
            throw new MappingException(
                "Entities can only extend MappedSuperclasses or other Entities.",
                type.getLocator(),
                parent.getLocator());
          }

          final EntityTypeImpl entity =
              new EntityTypeImpl(this, (IdentifiableTypeImpl) parent, clazz, (EntityMetadata) type);

          this.entities.put(entity.getJavaType(), entity);
          this.entitiesByName.put(entity.getName(), entity);
          this.entitiesByName.put(entity.getJavaType().getName(), entity);
        } else if (type instanceof MappedSuperclassMetadata) {
          // make sure it extends a mapped superclass type
          if ((parent != null) && !(parent instanceof MappedSuperclassTypeImpl)) {
            throw new MappingException(
                "MappedSuperclasses can only extend other MappedSuperclasses.",
                type.getLocator(),
                parent.getLocator());
          }

          final MappedSuperclassTypeImpl mappedSuperclass =
              new MappedSuperclassTypeImpl(
                  this, (MappedSuperclassTypeImpl) parent, clazz, (MappedSuperclassMetadata) type);
          this.mappedSuperclasses.put(mappedSuperclass.getJavaType(), mappedSuperclass);
        }
        if (type instanceof EmbeddableMetadata) {
          // make sure it extends a embeddable type
          if ((parent != null) && !(parent instanceof EmbeddableTypeImpl)) {
            throw new MappingException(
                "Embeddables can only extend a other Embeddables.",
                type.getLocator(),
                parent.getLocator());
          }

          final EmbeddableTypeImpl embeddable =
              new EmbeddableTypeImpl(this, clazz, (EmbeddableMetadata) type);
          this.embeddables.put(embeddable.getJavaType(), embeddable);
        }

      } catch (final ClassNotFoundException e) {
      } // not possible at this time
    }

    this.callbackManager = new CallbackManager(metadata.getEntityListeners());

    this.addNamedQueries(metadata.getNamedQueries());
    for (final ManagedTypeMetadata entity : entities) {
      if (entity instanceof EntityMetadata) {
        this.addNamedQueries(((EntityMetadata) entity).getNamedQueries());
      }
    }
  }

  /**
   * Adds the named queries to the metamodel.
   *
   * @param namedQueries
   * @since $version
   * @author hceylan
   */
  private void addNamedQueries(List<NamedQueryMetadata> namedQueries) {
    for (final NamedQueryMetadata namedQuery : namedQueries) {
      final NamedQueryMetadata existing = this.namedQueries.put(namedQuery.getName(), namedQuery);
      if (existing != null) {
        throw new MappingException(
            "Duplicate named query with the name: " + namedQuery.getName(),
            existing.getLocator(),
            namedQuery.getLocator());
      }
    }
  }

  /**
   * Adds the sequence generator to the metamodel
   *
   * @param metadata the generator metadata
   * @since $version
   * @author hceylan
   */
  public synchronized void addSequenceGenerator(SequenceGeneratorMetadata metadata) {
    final SequenceGenerator sequenceGenerator = new SequenceGenerator(metadata);
    this.sequenceGenerators.put(sequenceGenerator.getName(), sequenceGenerator);
  }

  /**
   * Adds the sequence generator to the metamodel
   *
   * @param metadata the generator metadata
   * @since $version
   * @author hceylan
   */
  public synchronized void addTableGenerator(TableGeneratorMetadata metadata) {
    final TableGenerator tableGenerator = new TableGenerator(metadata);

    this.tableGenerators.put(tableGenerator.getName(), tableGenerator);
  }

  /**
   * Creates of returns an existing {@link BasicTypeImpl} of <code>clazz</code>
   *
   * @param clazz the class
   * @return the basic type
   * @param <T> the java type of the basic type
   * @since $version
   * @author hceylan
   */
  @SuppressWarnings("unchecked")
  public <T> BasicTypeImpl<T> createBasicType(Class<T> clazz) {
    final BasicTypeImpl<T> basicType = (BasicTypeImpl<T>) this.basics.get(clazz);
    if (basicType != null) {
      return basicType;
    }

    return this.lazyCreateBasicType(clazz);
  }

  /**
   * Drops all the tables in the database.
   *
   * @param datasource the datasource
   * @since $version
   * @author hceylan
   */
  public void dropAllTables(DataSource datasource) {
    final Set<AbstractTable> tables = Sets.newHashSet();

    for (final EntityTypeImpl<?> entity : this.entities.values()) {

      // collect the entity tables
      for (final EntityTable table : entity.getTables()) {
        // if table belongs to parent then skip
        if (table.getEntity() != entity) {
          continue;
        }

        tables.add(table);
      }

      // collect the join tables
      for (final AssociationMapping<?, ?, ?> mapping : entity.getAssociations()) {
        final JoinTable table = mapping.getTable();

        // skip not applicable tables
        if ((table == null) || (table.getEntity() != entity)) {
          continue;
        }

        tables.add(table);
      }

      // collect the join tables
      for (final PluralMapping<?, ?, ?> mapping : entity.getMappingsPlural()) {
        if (!mapping.isAssociation()) {
          final AbstractTable table = (AbstractTable) mapping.getTable();
          if (table != null) {
            tables.add(table);
          }
        }
      }
    }

    try {
      this.jdbcAdaptor.dropAllForeignKeys(datasource, tables);
      this.jdbcAdaptor.dropAllTables(datasource, tables);
      this.jdbcAdaptor.dropAllSequences(datasource, this.sequenceGenerators.values());
    } catch (final SQLException e) {
      throw new PersistenceException("Cannot drop tables", e);
    }
  }

  /** {@inheritDoc} */
  @Override
  @SuppressWarnings("unchecked")
  public <X> EmbeddableTypeImpl<X> embeddable(Class<X> clazz) {
    return (EmbeddableTypeImpl<X>) this.embeddables.get(clazz);
  }

  /** {@inheritDoc} */
  @Override
  @SuppressWarnings("unchecked")
  public <X> EntityTypeImpl<X> entity(Class<X> clazz) {
    return (EntityTypeImpl<X>) this.entities.get(clazz);
  }

  /**
   * Returns the entity by name.
   *
   * @param name the simple or fully qualified name of the entity.
   * @param <X> the type of the entity
   * @return the entity or null
   * @since $version
   * @author hceylan
   */
  @SuppressWarnings("unchecked")
  public <X> EntityTypeImpl<X> entity(String name) {
    return (EntityTypeImpl<X>) this.entitiesByName.get(name);
  }

  /**
   * Fires the callbacks.
   *
   * @param instance the instance
   * @param type the type
   * @since $version
   * @author hceylan
   */
  public void fireCallbacks(Object instance, EntityListenerType type) {
    this.callbackManager.fireCallbacks(instance, type);
  }

  /**
   * Returns the callback manager of the metamodel.
   *
   * @return the callback manager of the metamodel
   * @since $version
   * @author hceylan
   */
  public CallbackManager getCallbackManager() {
    return this.callbackManager;
  }

  /** {@inheritDoc} */
  @Override
  public Set<EmbeddableType<?>> getEmbeddables() {
    return Sets.newHashSet(this.embeddables.values());
  }

  /** {@inheritDoc} */
  @Override
  public Set<EntityType<?>> getEntities() {
    final Set<EntityType<?>> entities = Sets.newHashSet();

    for (final EntityType<?> entity : this.entities.values()) {
      entities.add(entity);
    }

    return entities;
  }

  /**
   * Returns the entity that corresponds to clazz's parant chain.
   *
   * @param clazz the class
   * @return the entity
   * @since $version
   * @author hceylan
   */
  public EntityTypeImpl<?> getEntity(Class<?> clazz) {
    EntityTypeImpl<?> entity = null;

    while ((entity == null) && (clazz != Object.class)) {
      entity = this.entity(clazz);
      if (entity != null) {
        break;
      }
      clazz = clazz.getSuperclass();
    }

    return entity;
  }

  /**
   * Returns the entity manager factory.
   *
   * @return the entity manager factory
   * @since $version
   * @author hceylan
   */
  public EntityManagerFactoryImpl getEntityManagerFactory() {
    return this.emf;
  }

  /**
   * Returns the identifiable types.
   *
   * @return the identifiable types
   * @since $version
   * @author hceylan
   */
  public Set<IdentifiableType<?>> getIdentifiables() {
    final Set<IdentifiableType<?>> identifiables = Sets.newHashSet();

    identifiables.addAll(this.mappedSuperclasses.values());
    identifiables.addAll(this.entities.values());

    return identifiables;
  }

  /**
   * Returns the JDBC Adaptor.
   *
   * @return the JDBC Adaptor
   * @since $version
   * @author hceylan
   */
  public JdbcAdaptor getJdbcAdaptor() {
    return this.jdbcAdaptor;
  }

  /** {@inheritDoc} */
  @Override
  public Set<ManagedType<?>> getManagedTypes() {
    final Set<ManagedType<?>> managedTypes = Sets.newHashSet();

    managedTypes.addAll(this.embeddables.values());
    managedTypes.addAll(this.mappedSuperclasses.values());
    managedTypes.addAll(this.entities.values());

    return managedTypes;
  }

  /**
   * Returns the set of named queries.
   *
   * @return the set of named queries
   * @since $version
   * @author hceylan
   */
  public Collection<NamedQueryMetadata> getNamedQueries() {
    return this.namedQueries.values();
  }

  /**
   * Returns the next sequence for the generator.
   *
   * @param generator the generator
   * @return the next sequence for the generator
   * @since $version
   * @author hceylan
   */
  public Long getNextSequence(String generator) {
    try {
      return this.sequenceQueues.get(generator).poll(MetamodelImpl.POLL_TIMEOUT, TimeUnit.SECONDS);
    } catch (final InterruptedException e) {
      throw new PersistenceException(
          "Unable to retrieve next sequence "
              + generator
              + " in allowed "
              + MetamodelImpl.POLL_TIMEOUT
              + " seconds");
    }
  }

  /**
   * Returns the next table value for the generator.
   *
   * @param generator the generator
   * @return the next table value for the generator
   * @since $version
   * @author hceylan
   */
  public Long getNextTableValue(String generator) {
    try {
      return this.tableIdQueues.get(generator).poll(MetamodelImpl.POLL_TIMEOUT, TimeUnit.SECONDS);
    } catch (final InterruptedException e) {
      throw new PersistenceException(
          "Unable to retrieve next sequence "
              + generator
              + " in allowed "
              + MetamodelImpl.POLL_TIMEOUT
              + " seconds");
    }
  }

  @SuppressWarnings({"unchecked", "rawtypes"})
  private synchronized <X> BasicTypeImpl<X> lazyCreateBasicType(Class<X> clazz) {
    // skip if annotated with @Entity, @MappedSuperClass or @Embeddable
    if ((clazz.getAnnotation(Entity.class) != null) //
        || (clazz.getAnnotation(MappedSuperclass.class) != null) //
        || (clazz.getAnnotation(Embeddable.class) != null)) {

      return null;
    }

    if (Serializable.class.isAssignableFrom(clazz) || clazz.isPrimitive()) {
      final BasicTypeImpl basicType = new BasicTypeImpl(this, clazz);
      this.basics.put(clazz, basicType);

      return basicType;
    }

    return null;
  }

  /** {@inheritDoc} */
  @Override
  @SuppressWarnings("unchecked")
  public <X> ManagedTypeImpl<X> managedType(Class<X> clazz) {
    ManagedTypeImpl<X> managedType = (ManagedTypeImpl<X>) this.embeddables.get(clazz);
    if (managedType != null) {
      return managedType;
    }

    managedType = (ManagedTypeImpl<X>) this.mappedSuperclasses.get(clazz);
    if (managedType != null) {
      return managedType;
    }

    return (ManagedTypeImpl<X>) this.entities.get(clazz);
  }

  /**
   * Performs the foreign key DDL operations.
   *
   * @param datasource the datasource
   * @param ddlMode the DDL Mode
   * @param entity the entity to perform DDL against
   * @throws BatooException thrown in case of an underlying exception
   * @since $version
   * @author hceylan
   */
  public void performForeignKeysDdl(
      DataSource datasource, DDLMode ddlMode, EntityTypeImpl<?> entity) {
    if ((ddlMode == DDLMode.NONE)) {
      return;
    }

    MetamodelImpl.LOG.info(
        "Performing foreign key DDL operations for entiy {0}, mode {1}", entity.getName(), ddlMode);

    for (final EntityTable table : entity.getTables()) {
      // skip parent tables
      if (table.getEntity() != entity) {
        continue;
      }

      MetamodelImpl.LOG.info(
          "Performing foreign key DDL operations for table {0}, mode {1}",
          table.getQName(), ddlMode);

      for (final ForeignKey foreignKey : table.getForeignKeys()) {
        this.jdbcAdaptor.createForeignKey(datasource, foreignKey);
      }
    }

    for (final AssociationMapping<?, ?, ?> mapping : entity.getAssociations()) {
      final JoinTable table = mapping.getTable();
      // skip not applicable join tables
      if ((table == null) || (table.getEntity() != entity)) {
        continue;
      }

      MetamodelImpl.LOG.info(
          "Performing foreign key DDL operations for join table {0}, mode {1}",
          table.getQName(), ddlMode);

      for (final ForeignKey foreignKey : table.getForeignKeys()) {
        this.jdbcAdaptor.createForeignKey(datasource, foreignKey);
      }
    }

    for (final PluralMapping<?, ?, ?> mapping : entity.getMappingsPlural()) {
      if (!mapping.isAssociation()) {
        final AbstractTable table = (AbstractTable) mapping.getTable();
        MetamodelImpl.LOG.info(
            "Performing foreign key DDL operations for join table {0}, mode {1}",
            table.getQName(), ddlMode);

        for (final ForeignKey foreignKey : table.getForeignKeys()) {
          this.jdbcAdaptor.createForeignKey(datasource, foreignKey);
        }
      }
    }
  }

  /**
   * Performs the sequence generators DDL operations.
   *
   * @param datasource the datasource
   * @param ddlMode the DDL Mode
   * @since $version
   * @author hceylan
   */
  public void performSequencesDdl(DataSource datasource, DDLMode ddlMode) {
    for (final SequenceGenerator sequenceGenerator : this.sequenceGenerators.values()) {
      MetamodelImpl.LOG.info(
          "Performing DDL operations for sequence generators for {0}, mode {1}",
          sequenceGenerator.getName(), ddlMode);

      this.jdbcAdaptor.createSequenceIfNecessary(datasource, sequenceGenerator);
    }
  }

  /**
   * Performs the table generator DDL operations.
   *
   * @param datasource the datasource
   * @param ddlMode the DDL Mode
   * @since $version
   * @author hceylan
   */
  public void performTableGeneratorsDdl(DataSource datasource, DDLMode ddlMode) {
    for (final TableGenerator tableGenerator : this.tableGenerators.values()) {
      MetamodelImpl.LOG.info(
          "Performing DDL operations for sequence generators for mode table {1}, mode {0}",
          tableGenerator.getName(), ddlMode);

      this.jdbcAdaptor.createTableGeneratorIfNecessary(datasource, tableGenerator);
    }
  }

  /**
   * Performs the table DDL operations.
   *
   * @param datasource the datasource
   * @param ddlMode the DDL Mode
   * @param entity the entity to perform DDL against
   * @throws BatooException thrown in case of an underlying exception
   * @since $version
   * @author hceylan
   */
  public void performTablesDdl(DataSource datasource, DDLMode ddlMode, EntityTypeImpl<?> entity) {
    MetamodelImpl.LOG.info(
        "Performing DDL operations for entity {0}, mode {1}", entity.getName(), ddlMode);

    // create the entity tables
    for (final EntityTable table : entity.getTables()) {
      // if table belongs to parent then skip
      if (table.getEntity() != entity) {
        continue;
      }

      MetamodelImpl.LOG.info(
          "Performing DDL operations for {0}, mode {1}", table.getQName(), ddlMode);

      this.jdbcAdaptor.createOrUpdateTable(table, datasource, ddlMode);
    }

    // create the join tables
    for (final AssociationMapping<?, ?, ?> mapping : entity.getAssociations()) {
      final JoinTable table = mapping.getTable();

      // skip not applicable tables
      if ((table == null) || (table.getEntity() != entity)) {
        continue;
      }

      this.jdbcAdaptor.createOrUpdateTable(mapping.getTable(), datasource, ddlMode);
    }

    // create the join tables
    for (final PluralMapping<?, ?, ?> mapping : entity.getMappingsPlural()) {
      if (!mapping.isAssociation()) {
        final AbstractTable table = (AbstractTable) mapping.getTable();

        this.jdbcAdaptor.createOrUpdateTable(table, datasource, ddlMode);
      }
    }
  }

  /**
   * Prefills the id generators.
   *
   * @param datasource the datasource to use
   * @since $version
   * @author hceylan
   */
  public void preFillGenerators(DataSource datasource) {
    final int nThreads = Runtime.getRuntime().availableProcessors();

    this.idGeneratorExecuter =
        new ThreadPoolExecutor(
            1,
            nThreads, //
            30,
            TimeUnit.SECONDS, //
            new LinkedBlockingQueue<Runnable>(), //
            new GeneratorThreadFactory());

    for (final SequenceGenerator generator : this.sequenceGenerators.values()) {
      this.sequenceQueues.put(
          generator.getName(),
          new SequenceQueue(
              this.jdbcAdaptor,
              datasource,
              this.idGeneratorExecuter,
              generator.getSequenceName(),
              generator.getAllocationSize()));
    }

    for (final TableGenerator generator : this.tableGenerators.values()) {
      this.tableIdQueues.put(
          generator.getName(),
          new TableIdQueue(this.jdbcAdaptor, datasource, this.idGeneratorExecuter, generator));
    }
  }

  /**
   * Stops the id generators.
   *
   * @since $version
   * @author hceylan
   */
  public void stopIdGenerators() {
    this.idGeneratorExecuter.shutdownNow();
    try {
      this.idGeneratorExecuter.awaitTermination(5, TimeUnit.SECONDS);
    } catch (final Exception e) {
    }
  }

  /**
   * Returns the type corresponding to the <code>clazz</code>.
   *
   * @param clazz the class of the type
   * @param <X> the expected type of the type
   * @return the type
   * @since $version
   * @author hceylan
   */
  @SuppressWarnings("unchecked")
  public <X> TypeImpl<X> type(Class<X> clazz) {
    final BasicTypeImpl<?> basic = this.basics.get(clazz);
    if (basic != null) {
      return (TypeImpl<X>) basic;
    }

    return this.managedType(clazz);
  }

  /**
   * Updates the callback availability.
   *
   * @param availability the callback availability
   * @return the callback availability
   * @since $version
   * @author hceylan
   */
  public CallbackAvailability updateAvailability(CallbackAvailability availability) {
    return availability.updateAvailability(this.callbackManager);
  }
}
/**
 * @author hceylan
 * @since $version
 */
public class PreparedStatementProxy implements PreparedStatement {

  /**
   * The configuration type indicating how sql printing should be.
   *
   * @author hceylan
   * @since $version
   */
  public static enum SqlLoggingType {
    /** SQLs are not printed. */
    NONE, //

    /** SQLs are printed to the standard error. */
    STDERR, //

    /** SQLs are printed to the standard output. */
    STDOUT
  }

  private static final BLogger LOG = BLoggerFactory.getLogger("org.batoo.jpa.SQL");

  private static AtomicLong no = new AtomicLong(0);

  private long statementNo = -1;
  private long executionNo = -1;
  private final String sql;
  private final long slowSqlThreshold;
  private final PreparedStatement statement;

  private Object[] parameters;
  private ParameterMetaData parameterMetaData;

  private boolean debug;
  private final PrintStream sqlStream;

  /**
   * @param sql the SQL
   * @param statement the delegate statement
   * @param slowSqlThreshold the time to decide if SQL is deemed as slow
   * @param sqlLoggingType the type of the sql logging
   * @since $version
   * @author hceylan
   */
  public PreparedStatementProxy(
      String sql,
      PreparedStatement statement,
      long slowSqlThreshold,
      SqlLoggingType sqlLoggingType) {
    super();

    this.sql = sql;
    this.statement = statement;
    this.slowSqlThreshold = slowSqlThreshold;

    switch (sqlLoggingType) {
      case STDERR:
        this.sqlStream = System.err;
        break;
      case STDOUT:
        this.sqlStream = System.out;
        break;
      default:
        this.sqlStream = null;
    }

    this.debug = PreparedStatementProxy.LOG.isDebugEnabled();
  }

  /** {@inheritDoc} */
  @Override
  public void addBatch() throws SQLException {
    this.statement.addBatch();
  }

  /** {@inheritDoc} */
  @Override
  public void addBatch(String sql) throws SQLException {
    this.statement.addBatch();
  }

  /** {@inheritDoc} */
  @Override
  public void cancel() throws SQLException {
    this.statement.cancel();
  }

  /** {@inheritDoc} */
  @Override
  public void clearBatch() throws SQLException {
    this.statement.clearBatch();
  }

  /** {@inheritDoc} */
  @Override
  public void clearParameters() throws SQLException {
    this.statement.clearParameters();
  }

  /** {@inheritDoc} */
  @Override
  public void clearWarnings() throws SQLException {
    this.statement.clearWarnings();
  }

  /** {@inheritDoc} */
  @Override
  public void close() throws SQLException {
    this.statement.close();
  }

  /** {@inheritDoc} */
  @Override
  public void closeOnCompletion() throws SQLException {
    this.statement.closeOnCompletion();
  }

  /** {@inheritDoc} */
  @Override
  public boolean execute() throws SQLException {
    return this.statement.execute();
  }

  /** {@inheritDoc} */
  @Override
  public boolean execute(String sql) throws SQLException {
    return this.statement.execute();
  }

  /** {@inheritDoc} */
  @Override
  public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
    return this.statement.execute(sql, autoGeneratedKeys);
  }

  /** {@inheritDoc} */
  @Override
  public boolean execute(String sql, int[] columnIndexes) throws SQLException {
    return this.statement.execute(sql, columnIndexes);
  }

  /** {@inheritDoc} */
  @Override
  public boolean execute(String sql, String[] columnNames) throws SQLException {
    return this.statement.execute(sql, columnNames);
  }

  /** {@inheritDoc} */
  @Override
  public int[] executeBatch() throws SQLException {
    return this.statement.executeBatch();
  }

  /** {@inheritDoc} */
  @Override
  public ResultSet executeQuery() throws SQLException {
    if ((this.sqlStream == null) && !this.debug) {
      return this.statement.executeQuery();
    }

    if (this.statementNo == -1) {
      this.statementNo = PreparedStatementProxy.no.incrementAndGet();
    }

    this.executionNo++;

    PreparedStatementProxy.LOG.debug(
        "{0}:{1} executeQuery(){2}",
        this.statementNo,
        this.executionNo,
        PreparedStatementProxy.LOG.lazyBoxed(this.sql, this.parameters));

    final long start = System.currentTimeMillis();
    try {
      return this.statement.executeQuery();
    } finally {
      final long time = System.currentTimeMillis() - start;

      if (time > this.slowSqlThreshold) {
        PreparedStatementProxy.LOG.warn(
            new OperationTookLongTimeWarning(),
            "{0}:{1} {2} msecs, executeQuery()",
            this.statementNo,
            this.executionNo,
            time);
      } else {
        PreparedStatementProxy.LOG.trace(
            "{0}:{1} {2} msecs, executeQuery()", this.statementNo, this.executionNo, time);
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  public ResultSet executeQuery(String sql) throws SQLException {
    this.throwNotImplemented();
    return null;
  }

  /** {@inheritDoc} */
  @Override
  public int executeUpdate() throws SQLException {
    if ((this.sqlStream == null) && !this.debug) {
      return this.statement.executeUpdate();
    }

    if (this.statementNo == -1) {
      this.statementNo = PreparedStatementProxy.no.incrementAndGet();
    }

    this.executionNo++;

    PreparedStatementProxy.LOG.debug(
        "{0}:{1} executeUpdate(){2}",
        this.statementNo,
        this.executionNo,
        PreparedStatementProxy.LOG.lazyBoxed(this.sql, this.parameters));
    if (this.sqlStream != null) {
      this.sqlStream.println(
          MessageFormat.format(
              "{0}:{1} executeUpdate(){2}",
              this.statementNo,
              this.executionNo,
              PreparedStatementProxy.LOG.lazyBoxed(this.sql, this.parameters)));
    }

    final long start = System.currentTimeMillis();
    try {
      return this.statement.executeUpdate();
    } finally {
      final long time = System.currentTimeMillis() - start;
      if (time > this.slowSqlThreshold) {
        if (this.sqlStream != null) {
          this.sqlStream.println(
              MessageFormat.format(
                  "{0}:{1} {2} msecs, executeUpdate()", this.statementNo, this.executionNo, time));

          new OperationTookLongTimeWarning().printStackTrace(this.sqlStream);
        }

        PreparedStatementProxy.LOG.warn(
            new OperationTookLongTimeWarning(),
            "{0}:{1} {2} msecs, executeUpdate()",
            this.statementNo,
            this.executionNo,
            time);
      } else {
        if (this.sqlStream != null) {
          this.sqlStream.println(
              MessageFormat.format(
                  "{0}:{1} {2} msecs, executeUpdate()", this.statementNo, this.executionNo, time));
        }

        PreparedStatementProxy.LOG.debug(
            "{0}:{1} {2} msecs, executeUpdate()", this.statementNo, this.executionNo, time);
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  public int executeUpdate(String sql) throws SQLException {
    this.throwNotImplemented();
    return 0;
  }

  /** {@inheritDoc} */
  @Override
  public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
    this.throwNotImplemented();
    return 0;
  }

  /** {@inheritDoc} */
  @Override
  public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
    this.throwNotImplemented();
    return 0;
  }

  /** {@inheritDoc} */
  @Override
  public int executeUpdate(String sql, String[] columnNames) throws SQLException {
    this.throwNotImplemented();
    return 0;
  }

  /** {@inheritDoc} */
  @Override
  public Connection getConnection() throws SQLException {
    this.throwNotImplemented();
    return null;
  }

  /** {@inheritDoc} */
  @Override
  public int getFetchDirection() throws SQLException {
    this.throwNotImplemented();
    return 0;
  }

  /** {@inheritDoc} */
  @Override
  public int getFetchSize() throws SQLException {
    this.throwNotImplemented();
    return 0;
  }

  /** {@inheritDoc} */
  @Override
  public ResultSet getGeneratedKeys() throws SQLException {
    this.throwNotImplemented();
    return null;
  }

  /** {@inheritDoc} */
  @Override
  public int getMaxFieldSize() throws SQLException {
    this.throwNotImplemented();
    return 0;
  }

  /** {@inheritDoc} */
  @Override
  public int getMaxRows() throws SQLException {
    this.throwNotImplemented();
    return 0;
  }

  /** {@inheritDoc} */
  @Override
  public ResultSetMetaData getMetaData() throws SQLException {
    this.throwNotImplemented();
    return null;
  }

  /** {@inheritDoc} */
  @Override
  public boolean getMoreResults() throws SQLException {
    this.throwNotImplemented();
    return false;
  }

  /** {@inheritDoc} */
  @Override
  public boolean getMoreResults(int current) throws SQLException {
    this.throwNotImplemented();
    return false;
  }

  /** {@inheritDoc} */
  @Override
  public ParameterMetaData getParameterMetaData() throws SQLException {
    if (this.parameterMetaData != null) {
      return this.parameterMetaData;
    }

    this.parameterMetaData = this.statement.getParameterMetaData();

    if (this.parameters == null) {
      this.parameters = new Object[this.parameterMetaData.getParameterCount()];
    }

    return this.parameterMetaData;
  }

  /** {@inheritDoc} */
  @Override
  public int getQueryTimeout() throws SQLException {
    this.throwNotImplemented();
    return 0;
  }

  /** {@inheritDoc} */
  @Override
  public ResultSet getResultSet() throws SQLException {
    this.throwNotImplemented();
    return null;
  }

  /** {@inheritDoc} */
  @Override
  public int getResultSetConcurrency() throws SQLException {
    this.throwNotImplemented();
    return 0;
  }

  /** {@inheritDoc} */
  @Override
  public int getResultSetHoldability() throws SQLException {
    this.throwNotImplemented();
    return 0;
  }

  /** {@inheritDoc} */
  @Override
  public int getResultSetType() throws SQLException {
    this.throwNotImplemented();
    return 0;
  }

  /** {@inheritDoc} */
  @Override
  public int getUpdateCount() throws SQLException {
    this.throwNotImplemented();
    return 0;
  }

  /** {@inheritDoc} */
  @Override
  public SQLWarning getWarnings() throws SQLException {
    this.throwNotImplemented();
    return null;
  }

  /** {@inheritDoc} */
  @Override
  public boolean isClosed() throws SQLException {
    this.throwNotImplemented();
    return false;
  }

  /** {@inheritDoc} */
  @Override
  public boolean isCloseOnCompletion() throws SQLException {
    this.throwNotImplemented();
    return false;
  }

  /** {@inheritDoc} */
  @Override
  public boolean isPoolable() throws SQLException {
    this.throwNotImplemented();
    return false;
  }

  /** {@inheritDoc} */
  @Override
  public boolean isWrapperFor(Class<?> iface) throws SQLException {
    this.throwNotImplemented();
    return false;
  }

  /**
   * Resets the prepared statement and returns itself
   *
   * @return self
   * @since $version
   * @author hceylan
   */
  public PreparedStatement reset() {
    this.debug = PreparedStatementProxy.LOG.isDebugEnabled();

    return this;
  }

  /** {@inheritDoc} */
  @Override
  public void setArray(int parameterIndex, Array x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setBlob(int parameterIndex, Blob x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setBlob(int parameterIndex, InputStream inputStream, long length)
      throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setBoolean(int parameterIndex, boolean x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setByte(int parameterIndex, byte x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setBytes(int parameterIndex, byte[] x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setCharacterStream(int parameterIndex, Reader reader, int length)
      throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setCharacterStream(int parameterIndex, Reader reader, long length)
      throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setClob(int parameterIndex, Clob x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setClob(int parameterIndex, Reader reader) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setCursorName(String name) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setDate(int parameterIndex, Date x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setDouble(int parameterIndex, double x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setEscapeProcessing(boolean enable) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setFetchDirection(int direction) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setFetchSize(int rows) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setFloat(int parameterIndex, float x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setInt(int parameterIndex, int x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setLong(int parameterIndex, long x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setMaxFieldSize(int max) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setMaxRows(int max) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setNCharacterStream(int parameterIndex, Reader value, long length)
      throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setNClob(int parameterIndex, NClob value) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setNClob(int parameterIndex, Reader reader) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setNString(int parameterIndex, String value) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setNull(int parameterIndex, int sqlType) throws SQLException {
    if ((this.debug || (this.sqlStream != null)) && (this.parameters != null)) {
      this.parameters[parameterIndex - 1] = null;
    }

    this.statement.setNull(parameterIndex, sqlType);
  }

  /** {@inheritDoc} */
  @Override
  public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setObject(int parameterIndex, Object x) throws SQLException {
    if ((this.debug || (this.sqlStream != null)) && (this.parameters != null)) {
      this.parameters[parameterIndex - 1] = x;
    }

    this.statement.setObject(parameterIndex, x);
  }

  /** {@inheritDoc} */
  @Override
  public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength)
      throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setPoolable(boolean poolable) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setQueryTimeout(int seconds) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setRef(int parameterIndex, Ref x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setRowId(int parameterIndex, RowId x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setShort(int parameterIndex, short x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setString(int parameterIndex, String x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setTime(int parameterIndex, Time x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
    this.throwNotImplemented();
  }

  /** {@inheritDoc} */
  @Override
  public void setURL(int parameterIndex, URL x) throws SQLException {
    this.throwNotImplemented();
  }

  private void throwNotImplemented() {
    throw new NotImplementedException();
  }

  /** {@inheritDoc} */
  @Override
  public <T> T unwrap(Class<T> iface) throws SQLException {
    this.throwNotImplemented();
    return null;
  }
}