org.apache.flume.channel.jdbc.impl.JdbcChannelProviderImpl Maven / Gradle / Ivy
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.flume.channel.jdbc.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicLong;
import javax.sql.DataSource;
import org.apache.commons.dbcp.ConnectionFactory;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.pool.KeyedObjectPoolFactory;
import org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.channel.jdbc.ConfigurationConstants;
import org.apache.flume.channel.jdbc.DatabaseType;
import org.apache.flume.channel.jdbc.JdbcChannelException;
import org.apache.flume.channel.jdbc.JdbcChannelProvider;
import org.apache.flume.channel.jdbc.TransactionIsolation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JdbcChannelProviderImpl implements JdbcChannelProvider {
private static final Logger LOGGER =
LoggerFactory.getLogger(JdbcChannelProviderImpl.class);
private static final String EMBEDDED_DERBY_DRIVER_CLASSNAME
= "org.apache.derby.jdbc.EmbeddedDriver";
private static final String DEFAULT_DRIVER_CLASSNAME
= EMBEDDED_DERBY_DRIVER_CLASSNAME;
private static final String DEFAULT_USERNAME = "sa";
private static final String DEFAULT_PASSWORD = "";
private static final String DEFAULT_DBTYPE = "DERBY";
/** The connection pool. */
private GenericObjectPool connectionPool;
/** The statement cache pool */
private KeyedObjectPoolFactory statementPool;
/** The data source. */
private DataSource dataSource;
/** The database type. */
private DatabaseType databaseType;
/** The schema handler. */
private SchemaHandler schemaHandler;
/** Transaction factory */
private JdbcTransactionFactory txFactory;
/** Connection URL */
private String connectUrl;
/** Driver Class Name */
private String driverClassName;
/** Capacity Counter if one is needed */
private long maxCapacity = 0L;
/** The current size of the channel. */
private AtomicLong currentSize = new AtomicLong(0L);
@Override
public void initialize(Context context) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Initializing JDBC Channel provider with props: "
+ context);
}
initializeSystemProperties(context);
initializeDataSource(context);
initializeSchema(context);
initializeChannelState(context);
}
private void initializeSystemProperties(Context context) {
Map sysProps = new HashMap();
Map sysPropsOld = context.getSubProperties(
ConfigurationConstants.OLD_CONFIG_JDBC_SYSPROP_PREFIX);
if (sysPropsOld.size() > 0) {
LOGGER.warn("Long form configuration prefix \""
+ ConfigurationConstants.OLD_CONFIG_JDBC_SYSPROP_PREFIX
+ "\" is deprecated. Please use the short form prefix \""
+ ConfigurationConstants.CONFIG_JDBC_SYSPROP_PREFIX
+ "\" instead.");
sysProps.putAll(sysPropsOld);
}
Map sysPropsNew = context.getSubProperties(
ConfigurationConstants.CONFIG_JDBC_SYSPROP_PREFIX);
// Override the deprecated values with the non-deprecated
if (sysPropsNew.size() > 0) {
sysProps.putAll(sysPropsNew);
}
for (String key: sysProps.keySet()) {
String value = sysProps.get(key);
if(key != null && value != null) {
System.setProperty(key, value);
}
}
}
private void initializeChannelState(Context context) {
String maxCapacityStr = getConfigurationString(context,
ConfigurationConstants.CONFIG_MAX_CAPACITY,
ConfigurationConstants.OLD_CONFIG_MAX_CAPACITY, "0");
long maxCapacitySpecified = 0;
try {
maxCapacitySpecified = Long.parseLong(maxCapacityStr);
} catch (NumberFormatException nfe) {
LOGGER.warn("Invalid value specified for maximum channel capacity: "
+ maxCapacityStr, nfe);
}
if (maxCapacitySpecified > 0) {
this.maxCapacity = maxCapacitySpecified;
LOGGER.info("Maximum channel capacity: {}", maxCapacity);
} else {
LOGGER.warn("JDBC channel will operate without a capacity limit.");
}
if (maxCapacity > 0) {
// Initialize current size
JdbcTransactionImpl tx = null;
try {
tx = getTransaction();
tx.begin();
Connection conn = tx.getConnection();
currentSize.set(schemaHandler.getChannelSize(conn));
tx.commit();
} catch (Exception ex) {
tx.rollback();
throw new JdbcChannelException("Failed to initialize current size", ex);
} finally {
if (tx != null) {
tx.close();
}
}
long currentSizeLong = currentSize.get();
if (currentSizeLong > maxCapacity) {
LOGGER.warn("The current size of channel (" + currentSizeLong
+ ") is more than the specified maximum capacity (" + maxCapacity
+ "). If this situation persists, it may require resizing and "
+ "replanning of your deployment.");
}
LOGGER.info("Current channel size: {}", currentSizeLong);
}
}
private void initializeSchema(Context context) {
String createSchemaFlag = getConfigurationString(context,
ConfigurationConstants.CONFIG_CREATE_SCHEMA,
ConfigurationConstants.OLD_CONFIG_CREATE_SCHEMA, "true");
boolean createSchema = Boolean.valueOf(createSchemaFlag);
LOGGER.debug("Create schema flag set to: " + createSchema);
// First check if the schema exists
schemaHandler = SchemaHandlerFactory.getHandler(databaseType, dataSource);
if (!schemaHandler.schemaExists()) {
if (!createSchema) {
throw new JdbcChannelException("Schema does not exist and "
+ "auto-generation is disabled. Please enable auto-generation of "
+ "schema and try again.");
}
String createIndexFlag = getConfigurationString(context,
ConfigurationConstants.CONFIG_CREATE_INDEX,
ConfigurationConstants.OLD_CONFIG_CREATE_INDEX, "true");
String createForeignKeysFlag = getConfigurationString(context,
ConfigurationConstants.CONFIG_CREATE_FK,
ConfigurationConstants.OLD_CONFIG_CREATE_FK, "true");
boolean createIndex = Boolean.valueOf(createIndexFlag);
if (!createIndex) {
LOGGER.warn("Index creation is disabled, indexes will not be created.");
}
boolean createForeignKeys = Boolean.valueOf(createForeignKeysFlag);
if (createForeignKeys) {
LOGGER.info("Foreign Key Constraints will be enabled.");
} else {
LOGGER.info("Foreign Key Constratins will be disabled.");
}
// Now create schema
schemaHandler.createSchemaObjects(createForeignKeys, createIndex);
}
// Validate all schema objects are as expected
schemaHandler.validateSchema();
}
@Override
public void close() {
try {
connectionPool.close();
} catch (Exception ex) {
throw new JdbcChannelException("Unable to close connection pool", ex);
}
if (databaseType.equals(DatabaseType.DERBY)
&& driverClassName.equals(EMBEDDED_DERBY_DRIVER_CLASSNAME)) {
// Need to shut down the embedded Derby instance
if (connectUrl.startsWith("jdbc:derby:")) {
int index = connectUrl.indexOf(";");
String baseUrl = null;
if (index != -1) {
baseUrl = connectUrl.substring(0, index+1);
} else {
baseUrl = connectUrl + ";";
}
String shutDownUrl = baseUrl + "shutdown=true";
LOGGER.debug("Attempting to shutdown embedded Derby using URL: "
+ shutDownUrl);
try {
DriverManager.getConnection(shutDownUrl);
} catch (SQLException ex) {
// Shutdown for one db instance is expected to raise SQL STATE 45000
if (ex.getErrorCode() != 45000) {
throw new JdbcChannelException(
"Unable to shutdown embedded Derby: " + shutDownUrl
+ " Error Code: " + ex.getErrorCode(), ex);
}
LOGGER.info("Embedded Derby shutdown raised SQL STATE "
+ "45000 as expected.");
}
} else {
LOGGER.warn("Even though embedded Derby drvier was loaded, the connect "
+ "URL is of an unexpected form: " + connectUrl + ". Therfore no "
+ "attempt will be made to shutdown embedded Derby instance.");
}
}
dataSource = null;
txFactory = null;
schemaHandler = null;
}
@Override
public void persistEvent(String channel, Event event) {
PersistableEvent persistableEvent = new PersistableEvent(channel, event);
JdbcTransactionImpl tx = null;
try {
tx = getTransaction();
tx.begin();
if (maxCapacity > 0) {
long currentSizeLong = currentSize.get();
if (currentSizeLong >= maxCapacity) {
throw new JdbcChannelException("Channel capacity reached: "
+ "maxCapacity: " + maxCapacity + ", currentSize: "
+ currentSizeLong);
}
}
// Persist the persistableEvent
schemaHandler.storeEvent(persistableEvent, tx.getConnection());
tx.incrementPersistedEventCount();
tx.commit();
} catch (Exception ex) {
tx.rollback();
throw new JdbcChannelException("Failed to persist event", ex);
} finally {
if (tx != null) {
tx.close();
}
}
LOGGER.debug("Persisted event: {}", persistableEvent.getEventId());
}
@Override
public Event removeEvent(String channelName) {
PersistableEvent result = null;
JdbcTransactionImpl tx = null;
try {
tx = getTransaction();
tx.begin();
// Retrieve the persistableEvent
result = schemaHandler.fetchAndDeleteEvent(
channelName, tx.getConnection());
if (result != null) {
tx.incrementRemovedEventCount();
}
tx.commit();
} catch (Exception ex) {
tx.rollback();
throw new JdbcChannelException("Failed to persist event", ex);
} finally {
if (tx != null) {
tx.close();
}
}
if (result != null) {
LOGGER.debug("Removed event: {}", ((PersistableEvent) result).getEventId());
} else {
LOGGER.debug("No event found for removal");
}
return result;
}
@Override
public JdbcTransactionImpl getTransaction() {
return txFactory.get();
}
/**
* Initializes the datasource and the underlying connection pool.
* @param properties
*/
private void initializeDataSource(Context context) {
driverClassName = getConfigurationString(context,
ConfigurationConstants.CONFIG_JDBC_DRIVER_CLASS,
ConfigurationConstants.OLD_CONFIG_JDBC_DRIVER_CLASS, null);
connectUrl = getConfigurationString(context,
ConfigurationConstants.CONFIG_URL,
ConfigurationConstants.OLD_CONFIG_URL, null);
String userName = getConfigurationString(context,
ConfigurationConstants.CONFIG_USERNAME,
ConfigurationConstants.OLD_CONFIG_USERNAME, null);
String password = getConfigurationString(context,
ConfigurationConstants.CONFIG_PASSWORD,
ConfigurationConstants.OLD_CONFIG_PASSWORD, null);
String jdbcPropertiesFile = getConfigurationString(context,
ConfigurationConstants.CONFIG_JDBC_PROPS_FILE,
ConfigurationConstants.OLD_CONFIG_JDBC_PROPS_FILE, null);
String dbTypeName = getConfigurationString(context,
ConfigurationConstants.CONFIG_DATABASE_TYPE,
ConfigurationConstants.OLD_CONFIG_DATABASE_TYPE, null);
// If connect URL is not specified, use embedded Derby
if (connectUrl == null || connectUrl.trim().length() == 0) {
LOGGER.warn("No connection URL specified. "
+ "Using embedded derby database instance.");
driverClassName = DEFAULT_DRIVER_CLASSNAME;
userName = DEFAULT_USERNAME;
password = DEFAULT_PASSWORD;
dbTypeName = DEFAULT_DBTYPE;
String homePath = System.getProperty("user.home").replace('\\', '/');
String defaultDbDir = homePath + "/.flume/jdbc-channel";
File dbDir = new File(defaultDbDir);
String canonicalDbDirPath = null;
try {
canonicalDbDirPath = dbDir.getCanonicalPath();
} catch (IOException ex) {
throw new JdbcChannelException("Unable to find canonical path of dir: "
+ defaultDbDir, ex);
}
if (!dbDir.exists()) {
if (!dbDir.mkdirs()) {
throw new JdbcChannelException("unable to create directory: "
+ canonicalDbDirPath);
}
}
connectUrl = "jdbc:derby:" + canonicalDbDirPath + "/db;create=true";
// No jdbc properties file will be used
jdbcPropertiesFile = null;
LOGGER.warn("Overriding values for - driver: " + driverClassName
+ ", user: " + userName + "connectUrl: " + connectUrl
+ ", jdbc properties file: " + jdbcPropertiesFile
+ ", dbtype: " + dbTypeName);
}
// Right now only Derby and MySQL supported
databaseType = DatabaseType.getByName(dbTypeName);
switch (databaseType) {
case DERBY:
case MYSQL:
break;
default:
throw new JdbcChannelException("Database " + databaseType
+ " not supported at this time");
}
// Register driver
if (driverClassName == null || driverClassName.trim().length() == 0) {
throw new JdbcChannelException("No jdbc driver specified");
}
try {
Class.forName(driverClassName);
} catch (ClassNotFoundException ex) {
throw new JdbcChannelException("Unable to load driver: "
+ driverClassName, ex);
}
// JDBC Properties
Properties jdbcProps = new Properties();
if (jdbcPropertiesFile != null && jdbcPropertiesFile.trim().length() > 0) {
File jdbcPropsFile = new File(jdbcPropertiesFile.trim());
if (!jdbcPropsFile.exists()) {
throw new JdbcChannelException("Jdbc properties file does not exist: "
+ jdbcPropertiesFile);
}
InputStream inStream = null;
try {
inStream = new FileInputStream(jdbcPropsFile);
jdbcProps.load(inStream);
} catch (IOException ex) {
throw new JdbcChannelException("Unable to load jdbc properties "
+ "from file: " + jdbcPropertiesFile, ex);
} finally {
if (inStream != null) {
try {
inStream.close();
} catch (IOException ex) {
LOGGER.error("Unable to close file: " + jdbcPropertiesFile, ex);
}
}
}
}
if (userName != null) {
Object oldUser = jdbcProps.put("user", userName);
if (oldUser != null) {
LOGGER.warn("Overriding user from: " + oldUser + " to: " + userName);
}
}
if (password != null) {
Object oldPass = jdbcProps.put("password", password);
if (oldPass != null) {
LOGGER.warn("Overriding password from the jdbc properties with "
+ " the one specified explicitly.");
}
}
if (LOGGER.isDebugEnabled()) {
StringBuilder sb = new StringBuilder("JDBC Properties {");
boolean first = true;
Enumeration> propertyKeys = jdbcProps.propertyNames();
while (propertyKeys.hasMoreElements()) {
if (first) {
first = false;
} else {
sb.append(", ");
}
String key = (String) propertyKeys.nextElement();
sb.append(key).append("=");
if (key.equalsIgnoreCase("password")) {
sb.append("*******");
} else {
sb.append(jdbcProps.get(key));
}
}
sb.append("}");
LOGGER.debug(sb.toString());
}
// Transaction Isolation
String txIsolation = getConfigurationString(context,
ConfigurationConstants.CONFIG_TX_ISOLATION_LEVEL,
ConfigurationConstants.OLD_CONFIG_TX_ISOLATION_LEVEL,
TransactionIsolation.READ_COMMITTED.getName());
TransactionIsolation txIsolationLevel =
TransactionIsolation.getByName(txIsolation);
LOGGER.debug("Transaction isolation will be set to: " + txIsolationLevel);
// Setup Datasource
ConnectionFactory connFactory =
new DriverManagerConnectionFactory(connectUrl, jdbcProps);
connectionPool = new GenericObjectPool();
String maxActiveConnections = getConfigurationString(context,
ConfigurationConstants.CONFIG_MAX_CONNECTIONS,
ConfigurationConstants.OLD_CONFIG_MAX_CONNECTIONS, "10");
int maxActive = 10;
if (maxActiveConnections != null && maxActiveConnections.length() > 0) {
try {
maxActive = Integer.parseInt(maxActiveConnections);
} catch (NumberFormatException nfe) {
LOGGER.warn("Max active connections has invalid value: "
+ maxActiveConnections + ", Using default: " + maxActive);
}
}
LOGGER.debug("Max active connections for the pool: " + maxActive);
connectionPool.setMaxActive(maxActive);
statementPool = new GenericKeyedObjectPoolFactory(null);
// Creating the factory instance automatically wires the connection pool
new PoolableConnectionFactory(connFactory, connectionPool, statementPool,
databaseType.getValidationQuery(), false, false,
txIsolationLevel.getCode());
dataSource = new PoolingDataSource(connectionPool);
txFactory = new JdbcTransactionFactory(dataSource, this);
}
/**
* A callback method invoked from individual transaction instances after
* a successful commit. The argument passed is the net number of events to
* be added to the current size as tracked by the provider.
* @param delta the net number of events to be added to reflect the current
* size of the channel
*/
protected void updateCurrentChannelSize(long delta) {
long currentSizeLong = currentSize.addAndGet(delta);
LOGGER.debug("channel size updated to: " + currentSizeLong);
}
/**
* Helper method to transition the configuration from the old long form
* style configuration to the new short form. If the value is specified for
* both the old and the new forms, the one associated with the new form
* takes precedence.
*
* @param context
* @param key the expected configuration key
* @param oldKey the deprecated configuration key
* @param default value, null if no default
* @return the value associated with the key
*/
private String getConfigurationString(Context context, String key,
String oldKey, String defaultValue) {
String oldValue = context.getString(oldKey);
if (oldValue != null && oldValue.length() > 0) {
LOGGER.warn("Long form configuration key \"" + oldKey
+ "\" is deprecated. Please use the short form key \""
+ key + "\" instead.");
}
String value = context.getString(key);
if (value == null) {
if (oldValue != null) {
value = oldValue;
} else {
value = defaultValue;
}
}
return value;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy