Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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. See accompanying
* LICENSE file.
*/
package com.pivotal.gemfirexd.callbacks;
import java.io.FileInputStream;
import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.sql.SQLNonTransientConnectionException;
import java.sql.SQLNonTransientException;
import java.sql.SQLTransientException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.concurrent.ConcurrentSkipListMap;
import com.gemstone.gemfire.cache.RegionDestroyedException;
import com.pivotal.gemfirexd.execute.QueryObserver;
import com.pivotal.gemfirexd.execute.QueryObserverHolder;
/**
* DBSynchronizer class instance persists the GemFireXD operations to an external
* database having a JDBC driver.
*/
public class DBSynchronizer implements AsyncEventListener {
/** the JDBC connection URL for the external database */
protected String dbUrl;
/** user name to use for establishing JDBC connection */
protected String userName;
/** password for the user to use for establishing JDBC connection */
protected String passwd;
/** file to write errors */
protected String errorFile;
/** number of retries after an error before dumping to {@link #errorFile} */
protected int numErrorTries = 0;
/**
* default number of retries after an error is 3 when {@link #errorFile} is
* defined
*/
protected static final int DEFAULT_ERROR_TRIES = 3;
/**
* the transformation (e.g. AES/ECB/PKCS5Padding or Blowfish) to use for
* encrypting the password
*/
protected String transformation;
/** the key size of the algorithm being used for encrypted the password */
protected int keySize;
/**
* fully qualified name of the implementation class of the JDBC {@link Driver}
* interface
*/
protected String driverClass;
/**
* the {@link Driver} implementation instance loaded using the
* {@link #driverClass}
*/
protected Driver driver;
/**
* the current JDBC connection being used by this instance
*/
protected Connection conn;
/**
* Flag to indicate whether the current {@link #driver} is JDBC4 compliant for
* BLOB/CLOB related API. Value can be one of {@link #JDBC4DRIVER_UNKNOWN},
* {@link #JDBC4DRIVER_FALSE} or {@link #JDBC4DRIVER_TRUE}.
*/
private byte isJDBC4Driver = JDBC4DRIVER_UNKNOWN;
/**
* if it is unknown whether the driver is JDBC4 compliant for BLOB/CLOB
* related API
*/
protected static final byte JDBC4DRIVER_UNKNOWN = -1;
/**
* if it is known that the driver is not JDBC4 compliant for BLOB/CLOB related
* API
*/
protected static final byte JDBC4DRIVER_FALSE = 0;
/**
* if it is known that the driver is JDBC4 compliant for BLOB/CLOB related API
*/
protected static final byte JDBC4DRIVER_TRUE = 1;
/** true if this instance has been closed or not initialized */
protected volatile boolean shutDown = true;
protected final AsyncEventHelper helper = AsyncEventHelper.newInstance();
protected final Logger logger = Logger
.getLogger(AsyncEventHelper.LOGGER_NAME);
/**
* true if "TraceDBSynchronizer" debug flag is enabled in this GemFireXD
* instance (via gemfirexd.debug.true=TraceDBSynchronizer).
*/
protected boolean traceDBSynchronizer = helper.traceDBSynchronizer()
&& logger.isLoggable(Level.INFO);
/**
* true if "TraceDBSynchronizer" debug flag is enabled in this GemFireXD
* instance (via gemfirexd.debug.true=TraceDBSynchronizer).
*/
protected boolean traceDBSynchronizerHA = helper.traceDBSynchronizerHA()
&& logger.isLoggable(Level.INFO);
/** the cached {@link PreparedStatement}s for inserts */
protected final HashMap insertStmntMap =
new HashMap();
/** the cached {@link PreparedStatement}s for primary key based updates */
protected final HashMap updtStmntMap =
new HashMap();
/** the cached {@link PreparedStatement}s for primary key based deletes */
protected final HashMap deleteStmntMap =
new HashMap();
/** the map of table name to its current metadata object */
protected final HashMap metadataMap =
new HashMap();
/** the map of table name to its current primary key metadata object */
protected final HashMap pkMetadataMap =
new HashMap();
/**
* the cached {@link PreparedStatement}s for other non-primary key based DML
* operations
*/
protected final HashMap bulkOpStmntMap =
new HashMap();
/**
* if true then skip identity columns in sync else also set the values of
* identity columns as in GemFireXD
*/
protected boolean skipIdentityColumns = true;
// keys used in external property file
protected static final String DBDRIVER = "driver";
protected static final String DBURL = "url";
protected static final String USER = "user";
protected static final String PASSWORD = "password";
protected static final String SECRET = "secret";
protected static final String TRANSFORMATION = "transformation";
protected static final String KEYSIZE = "keysize";
protected static final String ERRORFILE = "errorfile";
protected static final String ERRORTRIES = "errortries";
protected static final String SKIP_IDENTITY_COLUMNS = "skipIdentityColumns";
// Log strings
protected static final String Gfxd_DB_SYNCHRONIZER__1 = "DBSynchronizer::"
+ "processEvents: Exception while fetching prepared statement "
+ "for event '%s': %s";
protected static final String Gfxd_DB_SYNCHRONIZER__2 = "DBSynchronizer::"
+ "processEvents: Unexpected Exception occured while processing "
+ "Events. The list of unprocessed events is: %s. "
+ "Attempt will be made to rollback the changes.";
protected static final String Gfxd_DB_SYNCHRONIZER__3 = "DBSynchronizer::"
+ "processEvents: Operation failed for event '%s' "
+ "due to exception: %s";
protected static final String Gfxd_DB_SYNCHRONIZER__4 = "DBSynchronizer::"
+ "closeStatements: Exception in closing prepared statement "
+ "with DML string: %s";
protected static final String Gfxd_DB_SYNCHRONIZER__5 = "DBSynchronizer::"
+ "close: Exception in closing SQL Connection: %s";
protected static final String Gfxd_DB_SYNCHRONIZER__6 = "DBSynchronizer::"
+ "init: Exception while initializing connection for driver class '%s' "
+ "and db url = %s";
protected static final String Gfxd_DB_SYNCHRONIZER__7 = "DBSynchronizer::"
+ "processEvents: Exception occured while committing '%s' "
+ "to external DB: %s";
protected static final String Gfxd_DB_SYNCHRONIZER__8 = "DBSynchronizer::init"
+ ": Illegal format of init string '%s', expected ,,...";
protected static final String Gfxd_DB_SYNCHRONIZER__9 = "DBSynchronizer::"
+ "init: Exception in loading properties file '%s' for initialization";
protected static final String Gfxd_DB_SYNCHRONIZER__10 = "DBSynchronizer::"
+ "init: missing Driver or URL properties in file '%s'";
protected static final String Gfxd_DB_SYNCHRONIZER__11 = "DBSynchronizer::"
+ "init: unknown property '%s' in file '%s'";
protected static final String Gfxd_DB_SYNCHRONIZER__12 = "DBSynchronizer::"
+ "init: both password and secret properties specified in file '%s'";
protected static final String Gfxd_DB_SYNCHRONIZER__13 = "DBSynchronizer::"
+ "init: initialized with URL '%s' using driver class '%s'";
/** Holds event that failed to be applied to underlying database and the time of failure*/
private static class ErrorEvent implements Comparable {
Event ev;
long errortime;
@Override
public int compareTo(Object o) {
ErrorEvent ee = (ErrorEvent)o;
// If events are equal, nevermind the time, else allow sorting by failure time, earlier first.
if(ee.ev.equals(this.ev)) {
return 0;
} else if (ee.errortime > this.errortime) {
return -1;
} else {
return 1;
}
}
}
/** keeps track of the retries that have been done for an event */
protected final ConcurrentSkipListMap errorTriesMap = new ConcurrentSkipListMap();
/**
* Enumeration that defines the action to be performed in case an exception is
* received during processing by {@link DBSynchronizer#processEvents(List)}
*/
protected static enum SqlExceptionHandler {
/**
* ignore the exception and continue to process other events in the current
* batch
*/
IGNORE {
@Override
public void execute(DBSynchronizer synchronizer) {
// No -op
synchronizer.logger.info("DBSynchronizer::Ignoring error");
}
@Override
public boolean breakTheLoop() {
return false;
}
},
/**
* ignore the exception and break the current batch of events being
* processed
*/
IGNORE_BREAK_LOOP {
@Override
public boolean breakTheLoop() {
return true;
}
@Override
public void execute(DBSynchronizer synchronizer) {
// No -op
}
},
/**
* create a new database connection since the current one is no longer
* usable
*/
REFRESH {
@Override
public void execute(DBSynchronizer synchronizer) {
synchronized (synchronizer) {
try {
if (!synchronizer.conn.isClosed()) {
if (synchronizer.helper.logFineEnabled()) {
synchronizer.logger.fine("DBSynchronizer::"
+ "SqlExceptionHandler: before rollback");
}
// For safe side just roll back the transaction so far
synchronizer.conn.rollback();
if (synchronizer.helper.logFineEnabled()) {
synchronizer.logger.fine("DBSynchronizer::"
+ "SqlExceptionHandler: after rollback");
}
}
} catch (SQLException sqle) {
synchronizer.helper.log(synchronizer.logger, Level.WARNING, sqle,
"DBSynchronizer::SqlExceptionHandler: "
+ "could not successfully rollback");
}
synchronizer.basicClose();
if (!synchronizer.shutDown) {
// Do not recreate the connection in case of shutdown
synchronizer.logger.info("DBSynchronizer::Attempting to reconnect to database");
synchronizer.instantiateConnection();
}
}
}
},
/** close the current connection */
CLEANUP {
@Override
public void execute(DBSynchronizer synchronizer) {
synchronized (synchronizer) {
try {
if (synchronizer.conn!= null && !synchronizer.conn.isClosed()) {
if (synchronizer.helper.logFineEnabled()) {
synchronizer.logger.fine("DBSynchronizer::"
+ "SqlExceptionHandler: before rollback");
}
// For safeside just roll back the transactions so far
synchronizer.conn.rollback();
if (synchronizer.helper.logFineEnabled()) {
synchronizer.logger.fine("DBSynchronizer::"
+ "SqlExceptionHandler: after rollback");
}
}
} catch (SQLException sqle) {
synchronizer.helper.log(synchronizer.logger, Level.WARNING, sqle,
"DBSynchronizer::SqlExceptionHandler: "
+ "could not successfully rollback");
}
synchronizer.basicClose();
}
}
};
/**
* execute an action specified by different enumeration values after an
* unexpected exception is received by
* {@link DBSynchronizer#processEvents(List)}
*/
public abstract void execute(DBSynchronizer synchronizer);
/**
* Returns true if processing for the current batch of events has to be
* terminated for the current exception. Default handling is to return true,
* unless specified otherwise by an enumerated action.
*/
public boolean breakTheLoop() {
return true;
}
}
/**
* Close this {@link DBSynchronizer} instance.
*
* To prevent a possible concurrency issue between closing thread & the
* processor thread, access to this method is synchronized on 'this'
*/
public synchronized void close() {
// Flush any pending error events to XML log
this.flushErrorEventsToLog();
this.shutDown = true;
this.basicClose();
this.helper.close();
}
/**
* Basic actions to be performed to close the {@link DBSynchronizer} instance
* though the instance will itself not be marked as having shut down.
*
* To prevent a possible concurrency issue between closing thread & the
* processor thread, access to this method is synchronized on 'this'
*/
public final synchronized void basicClose() {
// I believe that by this time the processor thread has stopped. No harm
// in checking ppep statement cache clear even if connection is not active.
closeStatements(this.insertStmntMap);
closeStatements(this.updtStmntMap);
closeStatements(this.deleteStmntMap);
closeStatements(this.bulkOpStmntMap);
try {
if (this.conn != null && !this.conn.isClosed()) {
this.conn.close();
}
} catch (SQLException sqle) {
if (logger.isLoggable(Level.INFO)) {
helper.logFormat(logger, Level.INFO, sqle, Gfxd_DB_SYNCHRONIZER__5,
this.conn);
}
}
}
/**
* Return {@link #JDBC4DRIVER_TRUE} if this driver is known to be JDBC4
* compliant for LOB support, {@link #JDBC4DRIVER_FALSE} if it is known to not
* be JDBC4 compliant and {@link #JDBC4DRIVER_UNKNOWN} if the status is
* unknown.
*/
protected final byte isJDBC4Driver() {
return this.isJDBC4Driver;
}
/**
* Set the JDBC4 status of this driver as per {@link #isJDBC4Driver()}.
*/
protected final void setJDBC4Driver(byte status) {
this.isJDBC4Driver = status;
}
/*
* to prevent a possible concurrency issue between closing thread & the
* processor thread, access to this method is synchronized on 'this'
*/
protected void closeStatements(Map psMap) {
Iterator> itr = psMap.entrySet()
.iterator();
while (itr.hasNext()) {
Map.Entry entry = itr.next();
try {
entry.getValue().close();
} catch (SQLException sqle) {
if (logger.isLoggable(Level.INFO)) {
helper.logFormat(logger, Level.INFO, sqle, Gfxd_DB_SYNCHRONIZER__4,
entry.getKey());
}
} finally {
itr.remove();
}
}
}
/**
* Initialize this {@link DBSynchronizer} instance, creating a new JDBC
* connection to the backend database as per the provided parameter.
*
* The recommended format of the parameter string is:
*
* file=<path>
*
* The file is a properties file specifying the driver, JDBC URL, user and
* password.
*
* Driver=<driver-class>
* URL=<JDBC URL>
* User=<user name>
*
* Secret=<encrypted password>
* Transformation=<transformation for the encryption cipher>
* KeySize=<size of the private key to use for encryption>
* -- OR --
* Password=<password>
*
* The password provided in the "Secret" property should be an encrypted one
* generated using the "gfxd encrypt-password external" command, else the
* "Password" property can be used to specify the password in plain-text. The
* "Transformation" and "KeySize" properties optionally specify the
* transformation and key size used for encryption else the defaults are used
* ("AES" and 128 respectively). User and password are optional and when not
* provided then JDBC URL will be used as is for connection.
*
* The above properties may also be provided inline like below:
*
* <driver-class>,<JDBC
* URL>[,<user>[,<password>|secret
* =<secret>][,transformation=<transformation>][,keysize=<key
* size>]
*
* The user and password parts are optional and can be possibly embedded in
* the JDBC URL itself. The password can be encrypted one generated using the
* "gfxd encrypt-password external" command in which case it should be
* prefixed with "secret=". It can also specify the transformation and keysize
* using the optional "transformation=..." and "keysize=..." properties.
*/
public void init(String initParamStr) {
this.driver = null;
this.driverClass = null;
this.dbUrl = null;
this.userName = null;
this.passwd = null;
this.transformation = null;
this.keySize = 0;
this.numErrorTries = 0;
String secret = null;
// check the new "file=" option first
if (initParamStr.startsWith("file=")) {
String propsFile = initParamStr.substring("file=".length());
FileInputStream fis = null;
final Properties props = new Properties();
try {
fis = new FileInputStream(propsFile);
props.load(fis);
} catch (Exception e) {
throw helper.newRuntimeException(
String.format(Gfxd_DB_SYNCHRONIZER__9, propsFile), e);
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (Exception e) {
// ignored
}
}
try {
for (Map.Entry