DBHandler.java revision bcb85423bc6855cb1c7accc69fa051e1771c000a
/**
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at opensso/legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* $Id: DBHandler.java,v 1.19 2009/12/15 17:59:16 bigfatrat Exp $
*
*/
/*
* Portions Copyrighted 2011-2012 ForgeRock Inc
*/
/**
* DBHandler takes log messages from the Logger and exports
* them to a specified Database. DBHandler reads LogManager's
* configuration to get information about Database userid,
* password, Database Driver, Database location. It takes from the caller the
* table name which will be created if it doesnt already exists.
* <p>
* By default DBFormatter is used for formatting the logRecords.
*/
private String databaseURL;
private int recCountLimit;
private int recMaxDBMem = 2;
private LinkedList recordBuffer;
private TimeBufferingTask bufferTask;
private boolean timeBufferingEnabled = false;
//
// this is to keep track when the connection to the DB
// is lost so that debug messages are logged only on
// transitions.
//
private boolean connectionToDBLost = false;
private boolean isMySQL = false;
private String oraDataType;
private String mysqlDataType;
private int dbFieldMax = 0;
private void configure() throws NullLocationException,
{
try {
setEncoding("UTF-8");
} catch (UnsupportedEncodingException e) {
":DBHandler: unsupportedEncodingException ", e);
}
try {
} catch (NumberFormatException nfe) {
":DBHandler: NumberFormatException ", nfe);
if (Debug.messageEnabled()) {
":DBHandler: Setting buffer size to 1");
}
recCountLimit = 1;
}
} else {
":DBHandler: Invalid buffer size: " + bufferSize);
if (Debug.messageEnabled()) {
":DBHandler: Setting buffer size to 1");
}
recCountLimit = 1;
}
try {
} catch (NumberFormatException nfe) {
":DBHandler:recMaxDBMem (" + recMaxDBMemStr +
"): NumberFormatException ", nfe);
if (Debug.messageEnabled()) {
":DBHandler: Setting Max DB Mem Buffer Size to 2x (" +
recCountLimit + ")");
}
}
} else {
":DBHandler: Invalid buffer size: " + bufferSize);
if (Debug.messageEnabled()) {
":DBHandler: Defaulting Max DB Mem Buffer Size " +
"to 2x Buffer Size");
}
}
if (recMaxDBMem < recCountLimit) {
":DBHandler:Maximum DB memory buffer size < Buffer Size, " +
}
timeBufferingEnabled = true;
}
throw new NullLocationException("Database URL location is null");
}
throw new FormatterInitException(
"Unable To Initialize DBFormatter");
}
throw new NullLocationException("userName is null");
}
throw new NullLocationException("password not provided");
}
throw new NullLocationException("driver not provided");
}
//
// don't know about drivers other than Oracle and MySQL
//
isMySQL = false;
isMySQL = true;
} else {
isMySQL = false;
":DBHandler:configure:assuming driver: '" + driver +
"' is Oracle-compatible.");
}
try {
} catch (Exception e) { // should be Invalid Formatter Exception
throw new FormatterInitException(
"Unable To Initialize DBFormatter " + e.getMessage());
}
}
{
//Monit start
if (MonitoringUtil.isRunning()) {
if (dbLogHandlerForMonitoring == null) {
}
if (dbLogHandlerForMonitoring != null) {
}
}
//Monit end
try {
this.conn =
} catch (ClassNotFoundException e) {
":DBHandler: ClassNotFoundException " + e.getMessage());
//Monit start
}
//Monit end
throw new DriverLoadException(e.getMessage());
} catch (SQLException sqle) {
//
// if start up with Oracle DB down, can get sqle.getErrorCode()
// == 1034, "ORA-01034: ORACLE not available"
// MySQL returns error code 0, message:
// "unable to connect to any hosts due to
// exception: java.net.ConnectException: Connection refused
//
//Monit start
}
//Monit end
}
//Monit start
}
//Monit end
}
//
// detected that connection to the DB had failed previously;
// this routine reestablishes the connection, and checks that
// the table exists (creating it if it doesn't).
//
private void reconnectToDatabase()
{
//Monit start
}
//Monit end
try {
} catch (ClassNotFoundException e) {
//Monit start
}
//Monit end
throw new DriverLoadException(e.getMessage());
} catch (SQLException sqle) {
sqle.getMessage());
//Monit start
}
//Monit end
}
//Monit start
}
//Monit end
}
/**
* Constructor takes the tableName as a parameter. Gets the configuration
* information from LogManager regarding the user name, password, database
* driver, the log location. Gets the formatter class from the
* configuration, loads it and sets it as default Formatter. Connects to
* the database using the username and password. If the table by that name
* doesnot exists, creates the table.
* @param tableName Database table name for logger.
*/
return;
}
try {
configure();
} catch (NullLocationException nle) {
} catch (FormatterInitException fie) {
":DBHandler: Unable to Initialize formatter", fie);
}
if (Debug.messageEnabled()) {
", LOG_STATUS = " + stat);
}
connectionToDBLost = true;
try {
connectionToDBLost = false;
} catch (SQLException sqe) {
":DBHandler: sql operation unsuccessful (" +
} catch (ConnectionException ce) {
":DBHandler: Could not connect to database:" +
ce.getMessage());
} catch (DriverLoadException dle) {
":DBHandler: Could not load driver", dle);
} catch (UnsupportedEncodingException use) {
}
connectionToDBLost = false;
}
recordBuffer = new LinkedList();
if (timeBufferingEnabled) {
}
if (MonitoringUtil.isRunning()) {
}
}
/**
* Formats and publishes the LogRecord.
* <p>
* A sql command is to be generated to write a record to the database. A
* typical sql command for writing a record to the database can be given
* as follows:
* <p>
* insert into "amSSO_access" (time, date, loginid, domain, level, message)
* values ('10:10:10', '10th June, 2002', 'uid=amadmin,o=sun.com',
* 'o=sun.com', 'NULL', 'SESSION CREATE SUCCESSFUL')
* <p>
* To construct the above sql query, this publish method uses the
* Formatter's getHead method to get the string containing COMMA seperated
* all field set, uses format method to get the COMMA seperated field
* values corresponding to those fields.
*
* @param logRecord the log record to be published.
*
*/
//Monit start
}
//Monit end
if (!isLoggable(logRecord)) {
return;
}
synchronized (this) {
if (Debug.messageEnabled()) {
recCountLimit + " writing all");
}
}
}
}
private String getColString() {
if (Debug.messageEnabled()) {
}
.append(" (")
.append(")")
.append(" values (");
} else {
.append(" values (");
}
return colStrBuffer.toString();
}
/**
* Flush any buffered messages.
*/
protected void nonBlockingFlush() {
synchronized (this) {
if (Debug.messageEnabled()) {
":DBHandler:flush: no records in buffer to write");
}
return;
}
tableName = getTableName();
":DBHandler:flush:NullLocationException: table name is" +
" null");
//Monit start
}
//Monit end
return;
} else {
recordBuffer = new LinkedList();
}
}
try {
// Get an instance as required otherwise it can cause issues on container restart.
} catch (ThreadPoolException ex) {
// use current thread to flush the data if ThreadPool is shutdown
synchronized (this) {
}
}
}
public void flush() {
synchronized (this) {
return;
}
tableName = getTableName();
//Monit start
}
//Monit end
return;
}
//
// check if the connection to the db had problems before
// if so, try to reconnect and then make sure the table's
// there.
//
//
// either the connection was never initially made, or it
// was lost somewhere along the line. try to make the
// connection now.
//
try {
} catch (DriverLoadException dle) {
//
// if the max mem buffer is exceeded, dump the records
//
} catch (ConnectionException ce) {
//
// if the max mem buffer is exceeded, dump the records
//
throw new AMLogException(
}
//
// re-established the connection to the DB. now
// check on the table.
//
connectionToDBLost = false;
try {
//
// any exception from createTable() might mean the
// table's not in the DB... just record the error, and
// let the insert let us know if the record didn't get
// logged.
//
} catch (SQLException se) {
// the error will be handled at later part of the code
} catch (UnsupportedEncodingException usee) {
// the error will be handled at later part of the code
}
}
//
// when using oracle, and the db is down, you get an
// exception on the createStatement. unfortunately,
// it's a TTC message (e.g. [ORA-]17310... the getErrorCode
// returns 17310), a vendor-specific error code.
//
// MySQL db, on the other hand seems to return from
// the createStatement() call "ok". catch it on the
// executeUpdate(), below.
//
try {
} catch (SQLException se) {
//
// try reconnecting to DB once. if can't, dump the record
// and wait for the next attempt.
//
try {
} catch (SQLException ex) {
}
connectionToDBLost = true;
try {
} catch (DriverLoadException dle) {
//
// if the max mem buffer is exceeded, dump the records
//
} catch (ConnectionException ce) {
//
// if the max mem buffer is exceeded, dump the records
//
throw new AMLogException(
}
connectionToDBLost = false;
//
// connection's reestablished, now do the table check.
//
try {
} catch (SQLException sqle) {
// the error will be handled by later part of the code
} catch (UnsupportedEncodingException usee) {
// the error will be handled by later part of the code
}
try {
} catch (SQLException sqle) {
//
// second time this failed (note that this whole block
// started with the createStatement()).
// log the error message, and continue on (for now)
//
throw new AMLogException(
}
}
try {
//Monit start
null){
}
//Monit end
} catch (SQLException sqle) {
/*
* as mentioned above, connection errors to oracle
* seem to get caught in the createStatement(), while
* with mysql, they get caught here.
*
* the other thing that could happen is the table was
* dropped, but not the connection.
*/
boolean tableDoesNotExist = false;
/*
* unfortunately have to check which db and specific
* error codes...
* see if table's missing
* MySQL: 1146
* Oracle: 942
*/
/*
* connection to DB's there, but table's missing
*
* gotta make the table; try the executeUpdate()
* again
*/
try {
} catch (SQLException se) {
// the error will be handled by later part of the code
} catch (UnsupportedEncodingException usee) {
// the error will be handled by later part of the code
}
try {
} catch (SQLException sqle2) {
// guess NOW it's an error
throw new AMLogException (
}
(sqleErrCode == 17410))))
{
/*
* connection's probably gone gotta try everything
* up to this point again, starting with
* reconnecting to the db. any failure along the
* line this time gets an exception.
*/
try {
} catch (SQLException ex) {
// the error will be handled by later part of the code
}
connectionToDBLost = true;
try {
} catch (DriverLoadException dle) {
/*
* if the max mem buffer is exceeded,
* dump the records
*/
throw new AMLogException (
} catch (ConnectionException ce) {
/*
* if the max mem buffer is exceeded,
* dump the records
*/
throw new AMLogException (
}
connectionToDBLost = false;
/*
* bunch the createTable, createStatement, and
* executeUpdate together because if any of these
* fail, throw an exception.
*/
try {
//Monit start
if (MonitoringUtil.isRunning() &&
{
}
//Monit end
} catch (SQLException sqe) {
/*
* if the max mem buffer is exceeded,
* dump the records
*/
throw new AMLogException (
} catch (UnsupportedEncodingException usee) {
/*
* if the max mem buffer is exceeded,
* dump the records
*/
throw new AMLogException (
}
} else {
/*
* not sure what to do here yet. log the error,
* throw an exception, and see what happens next.
*
* just for informational purposes, you get the
* following if the columns don't exist:
* if ((isMySQL && (sqleErrCode == 1054)) ||
* (!isMySQL && ((sqleErrCode == 904) ||
* (sqleErrCode == 913))))
*/
// if the max mem buffer is exceeded, dump the
// records
throw new AMLogException (
}
}
}
try {
} catch (SQLException se) {
// don't need to handle as the function will be exited.
}
}
}
/**
* Flush any buffered messages and close the current output stream.
*/
public void close() {
try {
flush();
} catch (AMLogException ale) {
ale.getMessage());
}
try {
}
catch (SQLException ce) {
":DBHandler: Unable To Close Connection", ce);
}
}
}
}
private String getTableName() {
return tableName;
}
synchronized (this) {
if (buffer != recordBuffer) {
}
if (reccnt > recMaxDBMem) {
removeCount + " records.");
}
for(int i = 0; i < removeCount; ++i) {
} else {
}
}
//Monit start
}
//Monit end
}
}
}
}
}
/**
* This method first checks to see if the table already exists
* if not it creates a table with the fields.
* assigned varchar 255. In case we had idea about whether the field is
* time or ipaddress or whatever... we could have assigned space
* accordingly. The trade off is b/w the ability to keep DBHandler
* indpendent of the fields(we need not hard core the fields) and space.
*/
{
// form a query string to check if the table exists.
try {
if(!isMySQL){
/*
* unlike MySQL, Oracle seems to make the table name
* all uppercase. wonder if you can query using the
* original case?
*/
"select count(table_name) from all_tables where table_name = ");
.append("'");
} else {
/*
* MySQL makes the table (at least running on solaris)
* the same case as specified in the create, so the
* query needs to specify the same case.
*/
.append("'");
}
boolean foundTable = false;
if(isMySQL) {
}
foundTable = true;
}
} else {
int result = 0;
}
if (result == 1) {
foundTable = true;
}
}
try{
} catch(SQLException ex){
}
/*
* if the table's in the DB, then check if it has all
* the columns we're going to want to write.
* if table's not in the DB, fall through (as before),
* to create it.
*/
if (foundTable == true) {
if (isMySQL) {
} else {
"select column_name from USER_TAB_COLUMNS " +
}
try {
} catch (SQLException sqe) {
sqe.getMessage());
/*
* guess we'll have to just return here, and
* let the flush handle the error...
*/
return;
}
int tempj = 0;
/*
* Oracle appears to return column names in
* all uppercase, 1 per cursor position.
* MySQL returns column names in the case created,
* also 1 per cursor position, but also information
* about the column (type ['varchar(255)'], and
* | Null | Key | Default | Extra). the column name
* is in the first column (#1) of the row.
*/
for (int i = 0; i < numCols; i++) {
}
tempj++;
}
try{
} catch(SQLException ex){
}
/*
* check that the columns we want to write are
* already in the table. if not, going to issue
* an alter command. except for Data field, both
* Oracle and MySQL lengths are 255 (see in create, below).
*
* Oracle seems to return the column names in uppercase.
*/
boolean addedOne = false;
if (isMySQL) {
} else {
}
if (addedOne) {
}
addedOne = true;
}
}
try {
} catch (SQLException sqle) {
sqle.getMessage());
/*
* guess we'll have to just return here, and
* let the flush handle the error...
*
* there's a return right after this, so just
* fall through.
*/
}
}
return;
}
} catch (SQLException e) {
":DBHandler:createTable:Query:SQLException (" +
// rethrow the exception
throw e;
} catch (UnsupportedEncodingException use) {
":DBHandler:createTable:Query:UE: "+
use.getMessage());
// rethrow the exception
throw use;
}
// didn't find the table in the DB, so create it.
sbuffer = new StringBuffer();
try {
} catch (UnsupportedEncodingException uee) {
":DBHandler: unsupported encoding exception uee", uee);
}
if (isMySQL) {
} else {
}
/*
* next column is the DATA column, which will be a non-VARCHAR*
* type, since it *can* contain a large quantity of 'character'
* (note not binary) data. Oracle uses "CLOB", while MySQL
* uses "LONGTEXT".
*/
if(isMySQL) {
varCharX = "varchar";
} else {
}
int i = 0;
}
try {
if (Debug.messageEnabled()) {
":DBHandler: the query string for creating is " +
}
} catch (SQLException sqe) {
":DBHandler:createTable:Execute:SQLEx (" +
//
// rethrow the exception
//
throw sqe;
}
}
private LinkedList buffer;
}
public void run() {
//
// check if the connection to the db had problems before
// if so, try to reconnect and then make sure the table's
// there.
//
//
// either the connection was never initially made, or it
// was lost somewhere along the line. try to make the
// connection now.
//
try {
":DBHandler:flush:reconnectToDatabase" +
" successful.");
} catch (DriverLoadException dle) {
":DBHandler:flush:reconnectToDatabase:DLE: " +
dle.getMessage());
//
// if the max mem buffer is exceeded, dump the records
//
} catch (ConnectionException ce) {
":DBHandler:flush:reconnectToDatabase:CE: " +
ce.getMessage());
//
// if the max mem buffer is exceeded, dump the records
//
throw new AMLogException(
}
//
// re-established the connection to the DB. now
// check on the table.
//
connectionToDBLost = false;
try {
//
// any exception from createTable() might mean the
// table's not in the DB... just record the error, and
// let the insert let us know if the record didn't get
// logged.
//
} catch (SQLException se) {
if (Debug.messageEnabled()) {
":DBHandler:flush:reconnect:cTable:SQLE (" +
}
} catch (UnsupportedEncodingException usee) {
if (Debug.messageEnabled()) {
":DBHandler:flush:reconnect:cTable:UE: " +
usee.getMessage());
}
}
}
//
// when using oracle, and the db is down, you get an
// exception on the createStatement. unfortunately,
// it's a TTC message (e.g. [ORA-]17310... the getErrorCode
// returns 17310), a vendor-specific error code.
//
// MySQL db, on the other hand seems to return from
// the createStatement() call "ok". catch it on the
// executeUpdate(), below.
//
try {
} catch (SQLException se) {
//
// observed that when Oracle's down, it's detected here.
// error code 1034.
//
":DBHandler:flush:cStatement:SQLE (" +
//
// try reconnecting to DB once. if can't, dump the record
// and wait for the next attempt.
//
try {
} catch (SQLException ex) {
//
// ignore exception and continue
//
if (Debug.messageEnabled()) {
":DBHandler:flush:cStatement:close:SQLE (" +
}
}
connectionToDBLost = true;
try {
":DBHandler:flush:cStatement:reconnect" +
" successful.");
} catch (DriverLoadException dle) {
":DBHandler:flush:cStatement:reconnect:DLE: " +
dle.getMessage());
//
// if the max mem buffer is exceeded, dump the records
//
} catch (ConnectionException ce) {
":DBHandler:flush:cStatement:reconnect:CE: " +
ce.getMessage());
//
// if the max mem buffer is exceeded, dump the records
//
throw new AMLogException(
}
connectionToDBLost = false;
//
// connection's reestablished, now do the table check.
//
try {
} catch (SQLException sqle) {
if (Debug.messageEnabled()) {
":DBHandler:flush:cStatement:reconnect:" +
sqle.getMessage());
}
} catch (UnsupportedEncodingException usee) {
if (Debug.messageEnabled()) {
":DBHandler:flush:cStatement:reconnect:" +
}
}
try {
} catch (SQLException sqle) {
//
// second time this failed (note that this whole block
// started with the createStatement()).
// log the error message, and continue on (for now)
//
":DBHandler:flush:cStatement:reconnect:cSt:SQLE ("
throw new AMLogException(
}
}
if (Debug.messageEnabled()) {
}
if (Debug.messageEnabled()) {
":DBHandler:insertString is: " + insertStr);
}
try {
//Monit start
null){
}
//Monit end
} catch (SQLException sqle) {
/*
* as mentioned above, connection errors to oracle
* seem to get caught in the createStatement(), while
* with mysql, they get caught here.
*
* the other thing that could happen is the table was
* dropped, but not the connection.
*/
boolean tableDoesNotExist = false;
if (Debug.messageEnabled()) {
"DBHandler:execute:SQLException: insertStr = "
+ insertStr);
":DBHandler:execute:SQLException (" +
}
/*
* unfortunately have to check which db and specific
* error codes...
* see if table's missing
* MySQL: 1146
* Oracle: 942
*/
/*
* connection to DB's there, but table's missing
*
* gotta make the table; try the executeUpdate()
* again
*/
try {
} catch (SQLException se) {
// just log the message and continue, for now
":DBHandler:flush:execUpdate:cTable:SQLE ("
se.getMessage());
} catch (UnsupportedEncodingException usee) {
// just log the message and continue, for now
":DBHandler:flush:execUpdate:cTable:UE: " +
usee.getMessage());
}
try {
} catch (SQLException sqle2) {
// guess NOW it's an error
":DBHandler:flush:execUpdate:exUpdate:" +
sqle2.getMessage());
throw new AMLogException (
}
(sqleErrCode == 17410))))
{
/*
* connection's probably gone gotta try everything
* up to this point again, starting with
* reconnecting to the db. any failure along the
* line this time gets an exception.
*/
try {
} catch (SQLException ex) {
// log and continue
if (Debug.messageEnabled()) {
":DBHandler:flush:execUpdate:close:" +
ex.getMessage());
}
}
connectionToDBLost = true;
try {
":DBHandler:flush:execUpdate:" +
"reconnect successful.");
} catch (DriverLoadException dle) {
if (Debug.messageEnabled()) {
":DBHandler:flush:execUpdate:" +
}
/*
* if the max mem buffer is exceeded,
* dump the records
*/
throw new AMLogException (
} catch (ConnectionException ce) {
if (Debug.messageEnabled()) {
":DBHandler:flush:execUpdate:" +
}
/*
* if the max mem buffer is exceeded,
* dump the records
*/
throw new AMLogException (
}
connectionToDBLost = false;
/*
* bunch the createTable, createStatement, and
* executeUpdate together because if any of these
* fail, throw an exception.
*/
try {
//Monit start
if (MonitoringUtil.isRunning() &&
{
}
//Monit end
} catch (SQLException sqe) {
":DBHandler:flush:executeUpd:reconnect:" +
+ sqe.getMessage());
/*
* if the max mem buffer is exceeded,
* dump the records
*/
throw new AMLogException (
} catch (UnsupportedEncodingException usee) {
":DBHandler:flush:execUpd:reconnect:" +
/*
* if the max mem buffer is exceeded,
* dump the records
*/
throw new AMLogException (
}
} else {
/*
* not sure what to do here yet. log the error,
* throw an exception, and see what happens next.
*
* just for informational purposes, you get the
* following if the columns don't exist:
* if ((isMySQL && (sqleErrCode == 1054)) ||
* (!isMySQL && ((sqleErrCode == 904) ||
* (sqleErrCode == 913))))
*/
":DBHandler:flush:executeUpdate failed (" +
":DBHandler:execute:SQLException: insertStr = "
+ insertStr);
// if the max mem buffer is exceeded, dump the
// records
throw new AMLogException (
}
}
}
try {
} catch (SQLException se) {
if (Debug.warningEnabled()) {
}
}
}
}
private class TimeBufferingTask extends GeneralTaskRunnable {
private long runPeriod;
public TimeBufferingTask(long runPeriod) {
}
/**
* The method which implements the GeneralTaskRunnable.
*/
public void run() {
if (Debug.messageEnabled()) {
":DBHandler:TimeBufferingTask.run() called");
}
}
/**
* Methods that need to be implemented from GeneralTaskRunnable.
*/
public boolean isEmpty() {
return true;
}
return false;
}
return false;
}
public long getRunPeriod() {
return runPeriod;
}
}
private void startTimeBufferingThread() {
long interval;
} else {
}
interval *= 1000;
if(bufferTask == null){
try {
1000));
} catch (IllegalArgumentException e) {
e.getMessage());
} catch (IllegalStateException e) {
if (Debug.messageEnabled()) {
e.getMessage());
}
}
if (Debug.messageEnabled()) {
":DBHandler: Time Buffering Thread Started");
}
}
}
private void stopBufferTimer() {
if(bufferTask != null) {
bufferTask.cancel();
bufferTask = null;
if (Debug.messageEnabled()) {
}
}
}
}