001package com.avaje.ebean.config.dbplatform;
002
003import org.h2.api.Trigger;
004import org.slf4j.Logger;
005import org.slf4j.LoggerFactory;
006
007import java.sql.Connection;
008import java.sql.PreparedStatement;
009import java.sql.ResultSet;
010import java.sql.SQLException;
011import java.sql.Timestamp;
012import java.util.ArrayList;
013import java.util.Arrays;
014import java.util.List;
015
016/**
017 * H2 database trigger used to populate history tables to support the @History feature.
018 */
019public class H2HistoryTrigger implements Trigger {
020
021  private static final Logger logger = LoggerFactory.getLogger(H2HistoryTrigger.class);
022
023  /**
024   * Hardcoding the column and history table suffix for now. Not sure how to get that
025   * configuration into the trigger instance nicely as it is instantiated by H2.
026   */
027  private static final String SYS_PERIOD_START = "SYS_PERIOD_START";
028  private static final String SYS_PERIOD_END = "SYS_PERIOD_END";
029  private static final String HISTORY_SUFFIX = "_history";
030
031  /**
032   * SQL to insert into the history table.
033   */
034  private String insertHistorySql;
035
036  /**
037   * Position of SYS_PERIOD_START column in the Object[].
038   */
039  private int effectStartPosition;
040
041  /**
042   * Position of SYS_PERIOD_END column in the Object[].
043   */
044  private int effectEndPosition;
045
046  @Override
047  public void init(Connection conn, String schemaName, String triggerName, String tableName, boolean before, int type) throws SQLException {
048
049    // get the columns for the table
050    ResultSet rs = conn.getMetaData().getColumns(null, schemaName, tableName, null);
051
052    // build the insert into history table SQL
053    StringBuilder insertSql = new StringBuilder(150);
054    insertSql.append("insert into ").append(tableName).append(HISTORY_SUFFIX).append(" (");
055
056    int count = 0;
057    List<String> columns = new ArrayList<String>();
058    while (rs.next()) {
059      if (++count > 1) {
060        insertSql.append(",");
061      }
062      String columnName = rs.getString("COLUMN_NAME");
063      if (columnName.equalsIgnoreCase(SYS_PERIOD_START)) {
064        this.effectStartPosition = count - 1;
065      } else if (columnName.equalsIgnoreCase(SYS_PERIOD_END)) {
066        this.effectEndPosition = count - 1;
067      }
068      insertSql.append(columnName);
069      columns.add(columnName);
070    }
071    insertSql.append(") values (");
072    for (int i = 0; i < count; i++) {
073      if (i > 0) {
074        insertSql.append(",");
075      }
076      insertSql.append("?");
077    }
078    insertSql.append(");");
079
080    this.insertHistorySql = insertSql.toString();
081    logger.debug("History table insert sql: {}", insertHistorySql);
082  }
083
084  @Override
085  public void fire(Connection connection, Object[] oldRow, Object[] newRow) throws SQLException {
086
087    if (oldRow != null) {
088      // a delete or update event
089      Timestamp now = new Timestamp(System.currentTimeMillis());
090      oldRow[effectEndPosition] = now;
091      if (newRow != null) {
092        // update event. Set the effective start timestamp to now.
093        newRow[effectStartPosition] = now;
094      }
095      if (logger.isDebugEnabled()) {
096        logger.debug("History insert: {}", Arrays.toString(oldRow));
097      }
098      insertIntoHistory(connection, oldRow);
099    }
100  }
101
102  /**
103   * Insert the data into the history table.
104   */
105  private void insertIntoHistory(Connection connection, Object[] oldRow) throws SQLException {
106
107    PreparedStatement stmt = connection.prepareStatement(insertHistorySql);
108    try {
109      for (int i = 0; i < oldRow.length; i++) {
110        stmt.setObject(i + 1, oldRow[i]);
111      }
112      stmt.executeUpdate();
113    } finally {
114      stmt.close();
115    }
116  }
117
118  @Override
119  public void close() throws SQLException {
120
121  }
122
123  @Override
124  public void remove() throws SQLException {
125
126  }
127}