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}