package com.linkedin.databus2.relay;
/*
*
* Copyright 2013 LinkedIn Corp. 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.
*
*/
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import javax.management.MBeanServer;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import com.linkedin.databus.monitoring.mbean.DBStatistics;
import com.linkedin.databus.monitoring.mbean.DBStatisticsMBean;
import com.linkedin.databus.monitoring.mbean.SourceDBStatistics;
import com.linkedin.databus2.producers.EventProducer;
import com.linkedin.databus2.producers.db.EventReaderSummary;
import com.linkedin.databus2.producers.db.OracleTriggerMonitoredSourceInfo;
import com.linkedin.databus2.util.DBHelper;
public class MonitoringEventProducer implements EventProducer , Runnable {
private final List<OracleTriggerMonitoredSourceInfo> _sources ;
private final String _name;
private final String _dbname;
protected enum MonitorState {INIT,RUNNING, PAUSE,SHUT}
MonitorState _state;
private final HashMap<Short, String> _monitorQueriesBySource;
static protected final long MAX_SCN_POLL_TIME = 10*60*1000;
static protected final long PER_SRC_MAX_SCN_POLL_TIME = 30*1000;
private Thread _curThread;
private final String _uri;
/** Logger for error and debug messages. */
private final Logger _log = Logger.getLogger(getClass());
private String _schema;
//stored as state ; as it may be required for graceful shutdown ; in case of exception
private Connection _con;
private DataSource _dataSource;
private final DBStatistics _dbStats;
private final MBeanServer _mbeanServer;
public MonitoringEventProducer(String name,String dbname, String uri,List<OracleTriggerMonitoredSourceInfo> sources,MBeanServer mbeanServer) {
_sources = sources;
_name = name;
_dbname = dbname;
_state = MonitorState.INIT;
_con = null;
_dataSource = null;
_uri = uri;
_schema = null;
_mbeanServer = mbeanServer;
// Generate the event queries for each source
_monitorQueriesBySource = new HashMap<Short, String>();
_dbStats = new DBStatistics(dbname);
for(OracleTriggerMonitoredSourceInfo sourceInfo : sources)
{
if (null==_schema)
{
//all logical sources have same schema
_schema = sourceInfo.getEventSchema()==null ? "" : sourceInfo.getEventSchema()+".";
_log.info("Reading source: _schema = |" + _schema + "|");
}
_dbStats.addSrcStats(new SourceDBStatistics(sourceInfo.getSourceName()));
String eventQuery = generateEventQuery(sourceInfo);
_monitorQueriesBySource.put(sourceInfo.getSourceId(), eventQuery);
}
_dbStats.registerAsMbean(_mbeanServer);
_log.info("Created " + name + " producer ");
}
@Override
public String getName() {
return _name;
}
@Override
public long getSCN() {
return 0;
}
public DBStatisticsMBean getDBStats() {
return _dbStats;
}
public void unregisterMBeans() {
_dbStats.unregisterAsMbean(_mbeanServer);
}
@Override
public synchronized void start(long sinceSCN) {
if (_state == MonitorState.INIT || _state == MonitorState.SHUT) {
_state = MonitorState.RUNNING;
_curThread = new Thread(this);
_curThread.start();
}
}
@Override
public synchronized boolean isRunning() {
return _state==MonitorState.RUNNING;
}
@Override
public synchronized boolean isPaused() {
return false;
}
@Override
public synchronized void unpause() {
_state = MonitorState.RUNNING;
}
@Override
public synchronized void pause() {
}
@Override
public synchronized void shutdown() {
_state=MonitorState.SHUT;
}
protected boolean createDataSource() {
try {
if (_dataSource==null) {
_dataSource = OracleJarUtils.createOracleDataSource(_uri);
}
} catch (Exception e) {
_log.error("Error creating data source", e);
_dataSource = null;
return false;
}
return true;
}
@Override
public void run() {
//check state and behave accordingly
if (createDataSource() && openDbConn()) {
do {
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
long maxDBScn = getMaxTxlogSCN(_con);
_log.info("Max DB Scn = " + maxDBScn);
_dbStats.setMaxDBScn(maxDBScn);
for (OracleTriggerMonitoredSourceInfo source: _sources) {
String eventQuery = _monitorQueriesBySource.get(source.getSourceId());
pstmt = _con.prepareStatement(eventQuery);
pstmt.setFetchSize(10);
//get max scn - exactly one row;
rs = pstmt.executeQuery();
if (rs.next())
{
long maxScn = rs.getLong(1);
_log.info("Source: " + source.getSourceId() + " Max Scn=" + maxScn);
_dbStats.setSrcMaxScn(source.getSourceName(), maxScn);
}
DBHelper.commit(_con);
DBHelper.close(rs,pstmt, null);
if (_state != MonitorState.SHUT) {
Thread.sleep(PER_SRC_MAX_SCN_POLL_TIME);
}
}
if (_state != MonitorState.SHUT) {
Thread.sleep(MAX_SCN_POLL_TIME);
}
} catch (InterruptedException e) {
_log.error("Exception trace", e);
shutDown();
} catch (SQLException e) {
try {
DBHelper.rollback(_con);
} catch (SQLException s){}
_log.error("Exception trace", e);
shutDown();
}
finally
{
DBHelper.close(rs, pstmt, null);
}
} while (_state != MonitorState.SHUT);
_log.info("Shutting down dbMonitor thread");
DBHelper.close(_con);
}
}
protected synchronized void shutDown() {
_state = MonitorState.SHUT;
_curThread = null;
}
private String generateEventQuery(OracleTriggerMonitoredSourceInfo sourceInfo)
{
/*
select scn from sy$txlog where txn = (select max(txn) from sy$member_account);
*/
StringBuilder sql = new StringBuilder();
sql.append("select scn from ").append(_schema).append("sy$txlog ");
sql.append("where txn = ");
sql.append (" ( select max(txn) from ").append(_schema).append("sy$").append(sourceInfo.getEventView()).append(" )");
_log.info("Monitoring Query: " + sql.toString());
return sql.toString();
}
/**
*
* Returns the max SCN from the sy$txlog table
* @param db
* @return the max scn
* @throws SQLException
*/
private long getMaxTxlogSCN(Connection db) throws SQLException
{
long maxScn = EventReaderSummary.NO_EVENTS_SCN;
String sql = "select " +
"max(" + _schema + "sync_core.getScn(scn,ora_rowscn)) " +
"from " + _schema + "sy$txlog where " +
"scn >= (select max(scn) from " + _schema + "sy$txlog)";
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = db.prepareStatement(sql);
rs = pstmt.executeQuery();
if(rs.next())
{
maxScn = rs.getLong(1);
}
}
finally
{
DBHelper.close(rs, pstmt, null);
}
return maxScn;
}
protected boolean openDbConn() {
if (_dataSource == null)
return false;
// Create the OracleDataSource used to get DB connection(s)
try {
// Open the database connection if it is closed (at start or after an SQLException)
if(_con == null || _con.isClosed())
{
_con = _dataSource.getConnection();
_con.setAutoCommit(false);
_con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
}
} catch (SQLException e) {
_log.error("Exception trace", e);
return false;
}
return true;
}
@Override
public void waitForShutdown() throws InterruptedException,
IllegalStateException
{
// TODO Auto-generated method stub
}
@Override
public void waitForShutdown(long arg0) throws InterruptedException,
IllegalStateException
{
// TODO Auto-generated method stub
}
}