org.jumpmind.symmetric.db.AbstractEmbeddedTrigger Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of symmetric-ds Show documentation
Show all versions of symmetric-ds Show documentation
SymmetricDS is an open source database synchronization solution. It is platform-independent,
web-enabled, and database-agnostic. SymmetricDS was first built to replicate changes between 'retail store'
databases and ad centralized 'corporate' database.
The newest version!
/*
* Licensed to JumpMind Inc under one or more contributor
* license agreements. See the NOTICE file distributed
* with this work for additional information regarding
* copyright ownership. JumpMind Inc licenses this file
* to you under the GNU Lesser General Public License (the
* "License"); you may not use this file except in compliance
* with the License.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* .
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jumpmind.symmetric.db;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.jumpmind.symmetric.db.h2.H2Trigger;
import org.jumpmind.symmetric.db.hsqldb.HsqlDbTrigger;
/**
* An implementation of logic that can be used in Java database triggers to
* capture data for SymmetricDS.
*
* @see H2Trigger
* @see HsqlDbTrigger
*
*
*/
abstract public class AbstractEmbeddedTrigger {
protected static final char[] HEX = "0123456789abcdef".toCharArray();
protected static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
protected static final String KEY_CONDITION_SQL = "CONDITION_SQL";
protected static final String KEY_INSERT_DATA_SQL = "INSERT_DATA_SQL";
protected static final String TEMPLATE_TABLE_SUFFIX = "_CONFIG";
protected String triggerName;
protected Map templates = null;
/**
* This method should be called by the database engine once when
* initializing the trigger.
*
* @param conn
* a connection to the database
* @param triggerName
* the name of the trigger used in the CREATE TRIGGER statement
* @param tableName
* the name of the table
*/
protected void init(Connection conn, String triggerName, String tableName) throws SQLException {
if (this.templates == null) {
this.triggerName = triggerName;
this.templates = getTemplates(conn);
}
if (templates == null || templates.size() == 0) {
throw new IllegalStateException(String.format(
"The '%s' SymmetricDS trigger is in an invalid state. It needs to be dropped.", triggerName));
}
}
/**
* This method is called for each triggered action.
*
* @param conn
* a connection to the database
* @param oldRow
* the old row, or null if no old row is available (for INSERT)
* @param newRow
* the new row, or null if no new row is available (for DELETE)
* @throws SQLException
* if the operation must be undone
*/
public void fire(Connection conn, Object[] oldRow, Object[] newRow) throws SQLException {
String sql = null;
try {
Statement stmt = conn.createStatement();
sql = fillVirtualTableSql(templates.get(KEY_CONDITION_SQL), oldRow, newRow);
ResultSet rs = stmt.executeQuery(sql);
if (rs.next() && rs.getInt(1) > 0) {
rs.close();
sql = fillVirtualTableSql(templates.get(KEY_INSERT_DATA_SQL), oldRow, newRow);
stmt.executeUpdate(sql);
}
stmt.close();
} catch (SQLException ex) {
System.err.println("This sql failed: " + sql);
Throwable rootException = ex;
while (rootException.getCause() != null && !rootException.getCause().equals(ex)) {
rootException = ex.getCause();
}
rootException.printStackTrace();
throw ex;
}
}
protected String fillVirtualTableSql(String sql, Object[] oldRow, Object[] newRow) throws SQLException {
int columnCount = oldRow != null ? oldRow.length : newRow.length;
StringBuilder out = new StringBuilder();
String[] tokens = sql.split("\\?");
int tokenIndex = 0;
tokenIndex = forEachColumn(columnCount, newRow, out, tokenIndex, tokens);
tokenIndex = forEachColumn(columnCount, oldRow, out, tokenIndex, tokens);
out.append(tokens[tokenIndex]);
return out.toString();
}
private int forEachColumn(int columnCount, Object[] data, StringBuilder out, int tokenIndex, String[] tokens) {
for (int i = 0; i < columnCount; i++) {
out.append(tokens[tokenIndex++]);
if (data != null) {
data[i] = appendVirtualTableStringValue(data[i], out);
} else {
out.append("null");
}
}
return tokenIndex;
}
protected Object appendVirtualTableStringValue(Object value, StringBuilder out) {
if (value == null) {
out.append("null");
} else if (value instanceof String || value instanceof BufferedReader) {
if (value instanceof BufferedReader) {
try {
BufferedReader reader = (BufferedReader) value;
value = readStringAndClose(reader, -1);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
out.append("'");
out.append(escapeString(value));
out.append("'");
} else if (value instanceof Number) {
out.append(value);
} else if (value instanceof Boolean) {
out.append(value);
} else if (value instanceof ByteArrayInputStream || value instanceof BufferedInputStream) {
out.append("'");
try {
value = convertBytesToString(readBytesAndClose((InputStream) value, -1));
} catch (IOException e) {
throw new RuntimeException(e);
}
out.append(escapeString(value));
out.append("'");
} else if (value instanceof Date) {
out.append("'");
synchronized (DATE_FORMATTER) {
out.append(DATE_FORMATTER.format(value));
}
out.append("'");
} else {
throw new IllegalStateException(String.format("Type not supported: %s", value.getClass().getName()));
}
return value;
}
protected String readStringAndClose(Reader paramReader, int paramInt) throws IOException {
try {
if (paramInt <= 0) {
paramInt = 2147483647;
}
int i = Math.min(4096, paramInt);
StringWriter localStringWriter = new StringWriter((paramInt == 2147483647) ? i : paramInt);
char[] arrayOfChar = new char[i];
while (paramInt > 0) {
int j = Math.min(i, paramInt);
j = paramReader.read(arrayOfChar, 0, j);
if (j < 0) {
break;
}
localStringWriter.write(arrayOfChar, 0, j);
paramInt -= j;
}
String str = localStringWriter.toString();
return str;
} finally {
paramReader.close();
}
}
public static byte[] readBytesAndClose(InputStream paramInputStream, int paramInt) throws IOException {
try {
if (paramInt <= 0) {
paramInt = 2147483647;
}
int i = Math.min(4096, paramInt);
ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream(i);
byte[] arrayOfByte1 = new byte[i];
while (paramInt > 0) {
int j = Math.min(i, paramInt);
j = paramInputStream.read(arrayOfByte1, 0, j);
if (j < 0) {
break;
}
localByteArrayOutputStream.write(arrayOfByte1, 0, j);
paramInt -= j;
}
byte[] arrayOfByte2 = localByteArrayOutputStream.toByteArray();
return arrayOfByte2;
} finally {
paramInputStream.close();
}
}
public static String convertBytesToString(byte[] paramArrayOfByte) {
return convertBytesToString(paramArrayOfByte, paramArrayOfByte.length);
}
public static String convertBytesToString(byte[] paramArrayOfByte, int paramInt) {
char[] arrayOfChar1 = new char[paramInt + paramInt];
char[] arrayOfChar2 = HEX;
for (int i = 0; i < paramInt; ++i) {
int j = paramArrayOfByte[i] & 0xFF;
arrayOfChar1[(i + i)] = arrayOfChar2[(j >> 4)];
arrayOfChar1[(i + i + 1)] = arrayOfChar2[(j & 0xF)];
}
return new String(arrayOfChar1);
}
protected String escapeString(Object val) {
return val == null ? null : val.toString().replaceAll("'", "''");
}
protected Map getTemplates(Connection conn) throws SQLException {
Map templates = new HashMap();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(String.format("select * from %s%s", triggerName, TEMPLATE_TABLE_SUFFIX));
if (rs.next()) {
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
templates.put(metaData.getColumnName(i), rs.getString(i));
}
return templates;
} else {
throw new SQLException(String.format("%s is in an invalid state. %s%s did not return a row.", triggerName,
triggerName, TEMPLATE_TABLE_SUFFIX));
}
}
}