Java-Based Collection Plug-in Example
Learn about the Java plug-in code and Java-based collection plug-in manifest file packages and structure for Oracle Database Security Central.
Java Collection Plug-in Code
This examples shows a complete Java-based collection plug-in.
This example is the end result of the discussion, “How to Create a Java-Based Collection Plug-in”.
Example 11 SampleEventCollectorFactory.java
package oracle.av.plugin.sample.collector;
import oracle.av.platform.agent.collfwk.AuditEventCollector;
import oracle.av.platform.agent.collfwk.AuditEventCollectorException;
import oracle.av.platform.agent.collfwk.AuditEventCollectorFactory;
import oracle.av.platform.agent.collfwk.CollectorContext;
public class SampleEventCollectorFactory implements AuditEventCollectorFactory {
public AuditEventCollector createAuditCollection(
CollectorContext collectorContext) throws AuditEventCollectorException {
// It simply creates and returns an instance of SampleEventCollector
return new SampleEventCollector();
}
}
Example 12 SampleEventCollector.java
package oracle.av.plugin.sample.collector;
import java.io.Reader;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Map;
import oracle.av.platform.agent.collfwk.AuditEventCollector;
import oracle.av.platform.agent.collfwk.AuditEventCollectorException;
import oracle.av.platform.agent.collfwk.AuditService;
import oracle.av.platform.agent.collfwk.CollectorContext;
import oracle.av.platform.agent.collfwk.SetAttributeException;
import oracle.av.platform.common.ErrorCodes;
import oracle.av.platform.common.dao.ConnectionManager;
import oracle.av.platform.common.dao.ConnectionManagerImpl;
import oracle.av.platform.common.exception.AuditException;
import oracle.av.platform.common.util.AVLogger;
/**
* This collector collects events from AUD table and sends them to Collection
* Framework. It connects to the Source database during initialization and uses
* the same connection till close() is called. It maintains one ResultSet
* containing events. Once the ResultSet gets exhausted, the Collector sets a
* checkpoint and creates another ResultSet.
*
* @author myellu
*
*/
public class SampleEventCollector extends AuditEventCollector {
// The delay used when querying events.
private static final long DELAY = 5 * 1000;
private static final Map<Integer, String> eventNameMap =
new HashMap<Integer, String>();
static {
eventNameMap.put(1, "CREATE");
eventNameMap.put(2, "INSERT");
eventNameMap.put(3, "SELECT");
eventNameMap.put(4, "CREATE");
eventNameMap.put(15, "ALTER");
eventNameMap.put(30, "AUDIT");
eventNameMap.put(34, "CREATE");
eventNameMap.put(35, "ALTER");
eventNameMap.put(51, "CREATE");
eventNameMap.put(52, "CREATE");
}
// This map contains mapping from the source event ids to Audit Vault target
// types.
private static final Map<Integer, String> targetTypeMap = new HashMap<Integer,
String>();
static {
targetTypeMap.put(1, "TABLE");
targetTypeMap.put(2, "TABLE");
targetTypeMap.put(3, "TABLE");
targetTypeMap.put(4, "CLUSTER");
targetTypeMap.put(15, "TABLE");
targetTypeMap.put(30, "OBJECT");
targetTypeMap.put(34, "DATABASE");
targetTypeMap.put(35, "DATABASE");
targetTypeMap.put(51, "USER");
targetTypeMap.put(52, "ROLE");
}
// This map contains mapping from the source event ids to Source Event Names.
// This is necessary since source event ids do not describe the Source Event.
private static final Map<Integer, String> sourceEventMap = new HashMap<Integer,
String>();
static {
targetTypeMap.put(1, "OBJECT:CREATED:TABLE");
targetTypeMap.put(2, "INSERT INTO TABLE");
targetTypeMap.put(3, "SELECT FROM TABLE");
targetTypeMap.put(4, "OBJECT:CREATED:TABLE");
targetTypeMap.put(15, "OBJECT:ALTERED:TABLE");
targetTypeMap.put(30, "AUDIT OBJECT");
targetTypeMap.put(34, "OBJECT:CREATED:DATABASE");
targetTypeMap.put(35, "OBJECT:ALTERED:DATABASE");
targetTypeMap.put(51, "OBJECT:CREATED:USER");
targetTypeMap.put(52, "OBJECT:CREATED:ROLE");
}
// holds a connection to the Source database.
private ConnectionManager m_connectionManager;
// Connection to the Source.
private Connection m_connection;
// PreparedStatement used to get ResultSet.
private PreparedStatement m_preparedStatement;
// holds the ResultSet containing records.
private ResultSet m_resultSet;
// AuditService will be used to set checkpoint.
private AuditService m_auditService;
// previous checkpoint set.
private Timestamp m_previousCheckpoint;
// next checkpoint to be set.
private Timestamp m_nextCheckpoint;
private AVLogger m_logger;
// The CollectorContext received from the Collection Framework.
private CollectorContext m_collectorContext;
private long m_timeZoneOffset;
/**
* It connects to the database using the credentials and Connection String
* from the CollectorContext.
*
* @throws AuditEventCollectorException
*/
private void connectToSource() throws AuditEventCollectorException {
m_logger.logDebugMethodEntered();
// Get connection information from collector context.
String user = m_collectorContext.getSecuredTargetUser();
String password = new String(m_collectorContext.getSecuredTargetPassword());
String connectionString = m_collectorContext.getSecuredTargetLocation();
// Create a ConnectionManager object.
try {
m_connectionManager = new ConnectionManagerImpl(connectionString,
user, password.toCharArray());
m_connection = m_connectionManager.getConnection();
} catch (AuditException ex) {
throw new AuditEventCollectorException(
ErrorCodes.FAILED_CONNECT_TO_SOURCE,
new Object[] { connectionString }, ex);
}
m_logger.logDebugMethodExited();
}
/**
* converts the timone offset specified in String to a number of
* milliseconds.
*
*/
private long getTimeZoneOffsetInMs(String offset) {
if (offset == null)
return 0;
long timeZoneOffset;
/** process offset to get value in milliseconds */
int hour = Integer.parseInt(offset.substring(1, 3));
int min = Integer.parseInt(offset.substring(4, 6));
timeZoneOffset = (hour * 60 * 60 + min * 60) * 1000;
if (offset.charAt(0) == '-')
timeZoneOffset *= -1;
return timeZoneOffset;
}
/**
* Initializes the Collector with the values from CollectorContext. It also
* connects to the database.
*/
public void initializeCollector(CollectorContext collectorContext)
throws AuditEventCollectorException {
m_collectorContext = collectorContext;
m_auditService = m_collectorContext.getAuditService();
m_previousCheckpoint = m_collectorContext.getCheckpoint();
m_logger = m_collectorContext.getLogger();
// Get the timone offset for the Source.
String offset = m_collectorContext.getAttribute("TimeZoneOffset");
if (offset != null) {
m_timeZoneOffset = getTimeZoneOffsetInMs(offset);
}
connectToSource();
fetchEvents();
}
/**
* Queries the Source to get audit events that occurred from previous
* checkpoint to the current time. Apart from during the initialization, this
* method should be called only when ResultSet is exhausted. There are two
* reasons for this. * 1. This method will set the checkpoint. Checkpoint should only be set when
* the ResultSet is exhausted as the results with in the ResultSet can be in
* random order. * 2. This method will create a new ResultSet. Hence the contents of the old
* ResultSet will be inaccessible after this function is called.
*
* @throws AuditEventCollectorException
*/
private void fetchEvents() throws AuditEventCollectorException {
m_logger.logDebugMethodEntered();
if (m_nextCheckpoint != null) {
m_auditService.setCheckpoint(m_nextCheckpoint);
m_previousCheckpoint = m_nextCheckpoint;
}
// It is not good to hold on to the Connection for long. As this is the
// only place we can release the connection, we release and reacquire the
// connection.
try {
if (m_connection != null) {
m_connectionManager.releaseConnection(m_connection);
}
} catch (AuditException ex) {
throw new AuditEventCollectorException(
ErrorCodes.FAILED_TO_RELEASE_CONNECTION_TO_DB, null, ex);
}
try {
m_connection = m_connectionManager.getConnection();
} catch (AuditException ex) {
throw new AuditEventCollectorException(
ErrorCodes.FAILED_TO_GET_CONNECTION_TO_DB, null, ex);
}
// Now we need to aim for the next checkpoint. We will query for all
// events from previous checkpoint to the next checkpoint. So we want to
// make sure that all the events with event time lesser than the next
// checkpoint are already available in the table. However, the events
// might take a small amount of time before they are present in the table.
// Hence the next checkpoint we aim will be current time minus delta time.
m_nextCheckpoint = new Timestamp(System.currentTimeMillis() - DELAY);
String query = null;
try {
if (m_previousCheckpoint == null) {
query = "select * from AUD where EVENT_TIME <= ?";
m_preparedStatement = m_connection.prepareStatement(query);
m_preparedStatement.setTimestamp(1, m_nextCheckpoint);
} else {
query = "select * from AUD where EVENT_TIME > ? and EVENT_TIME <= ?";
m_preparedStatement = m_connection.prepareStatement(query);
m_preparedStatement.setTimestamp(1, m_previousCheckpoint);
m_preparedStatement.setTimestamp(2, m_nextCheckpoint);
}
m_resultSet = m_preparedStatement.executeQuery();
} catch (SQLException ex) {
throw new AuditEventCollectorException(
ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE,
new Object[] { query }, ex);
}
m_logger.logDebugMethodExited();
}
/**
* If the result set is not exhausted this will return true. If it has
* exhausted, it will query to get the events till the current time. If it
* could get any events, it will return true, false otherwise.
*/
public boolean hasNext() throws AuditEventCollectorException {
boolean hasMore;
try {
if(m_resultSet == null) {
fetchEvents();
return m_resultSet.next();
}
hasMore = m_resultSet.next();
if (!hasMore) {
fetchEvents();
hasMore = m_resultSet.next();
}
} catch (SQLException ex) {
throw new AuditEventCollectorException(
ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
return hasMore;
}
// All the getter methods make use of the ResultSet get methods and return
// the value appropriately.
public String getUserName() throws AuditEventCollectorException {
try {
return m_resultSet.getString("USER_ID");
} catch (SQLException ex) {
throw new AuditEventCollectorException(
ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
}
public String getOSUserName() throws AuditEventCollectorException {
try {
return m_resultSet.getString("OS_USER_ID");
} catch (SQLException ex) {
throw new AuditEventCollectorException(
ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
}
public String getCommandClass() throws AuditEventCollectorException {
try {
int eventId = m_resultSet.getInt("ACTION");
return eventNameMap.get(eventId);
} catch (SQLException ex) {
throw new AuditEventCollectorException(
ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
}
public String getEventName() throws AuditEventCollectorException {
try {
int eventId = m_resultSet.getInt("ACTION");
return sourceEventMap.get(eventId);
} catch (SQLException ex) {
throw new AuditEventCollectorException(
ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
}
public EventStatus getEventStatus() throws AuditEventCollectorException {
try {
int status = m_resultSet.getInt("STATUS");
if (status == 1) {
return EventStatus.SUCCESS;
} else if (status == 0) {
return EventStatus.FAILURE;
} else {
return EventStatus.UNKNOWN;
}
} catch (SQLException ex) {
throw new AuditEventCollectorException(
ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
}
public Timestamp getEventTimeUTC() throws AuditEventCollectorException {
try {
Timestamp eventTime = m_resultSet.getTimestamp("EVENT_TIME");
// As the method name suggests, the timestamp must be returned only in
// UTC timone.
return new Timestamp(eventTime.getTime() - m_timeZoneOffset);
} catch (SQLException ex) {
throw new AuditEventCollectorException( ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
}
public String getErrorMessage() throws AuditEventCollectorException {
// There is no corresponding field for ErrorMessage. Hence we
// return NULL always.
return null;
}
public String getErrorId() throws AuditEventCollectorException {
// There is no corresponding field for ErrorId. Hence we
// return NULL always.
return null;
}
public String getTargetObject() throws AuditEventCollectorException {
try {
return m_resultSet.getString("OBJ_NAME");
} catch (SQLException ex) {
throw new AuditEventCollectorException( ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
}
public String getTargetType() throws AuditEventCollectorException {
try {
int eventId = m_resultSet.getInt("ACTION");
return targetTypeMap.get(eventId);
} catch (SQLException ex) {
throw new AuditEventCollectorException(
ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
}
public String getTargetOwner() throws AuditEventCollectorException {
try {
return m_resultSet.getString("OBJ_CREATOR");
} catch (SQLException ex) {
throw new AuditEventCollectorException(
ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
}
public String getClientHostName() throws AuditEventCollectorException {
try {
return m_resultSet.getString("USER_HOST");
} catch (SQLException ex) {
throw new AuditEventCollectorException(
ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
}
public String getClientIP() throws AuditEventCollectorException {
// There is no corresponding field for IP address. Hence we
// return NULL always.
return null;
}
public String getExtension() throws AuditEventCollectorException {
try {
StringBuilder sb = new StringBuilder();
// Here we will put those fields which are not sent in other getter
// methods.
sb.append("DB_ID=" + m_resultSet.getString("DB_ID") + ";");
sb.append("INSTANCE=" + m_resultSet.getString("INSTANCE") + ";");
sb.append("PROCESS=" + m_resultSet.getString("PROCESS"));
return sb.toString();
} catch (SQLException ex) {
throw new AuditEventCollectorException(
ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
}
public Reader getCommandText() throws AuditEventCollectorException {
try {
// Clobs and the Readers contained in the Clobs are alive only
// as long as the Connection to the Source is alive. So if the Source
// Connection is closed, Collection Framework will fail when it tries
// to send the events to AV Server. If there is any need to close and
// recreate a connection that should be done immediately after setting
// the checkpoint. Setting the checkpoint causes the Collection
// Framework to flush all the events it is holding. So immediately
// after setting the checkpoint, we are sure that the Framework is not
// holding any events.
Clob clob = m_resultSet.getClob("SQL_TEXT");
return clob.getCharacterStream();
} catch (SQLException ex) {
throw new AuditEventCollectorException(
ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
}
public Reader getCommandParam() throws AuditEventCollectorException {
try {
Clob clob = m_resultSet.getClob("SQL_BIND");
return clob.getCharacterStream();
} catch (SQLException ex) {
throw new AuditEventCollectorException(
ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
}
public String getMarker() throws AuditEventCollectorException {
// ENTRY_ID will identify an audit event uniquely with in a session. Hence
// ENTRY_ID along with SESSION_ID will uniquely identify an audit event
// across sessions.
try {
return m_resultSet.getString("SESSION_ID") + ":"
+ m_resultSet.getString("ENTRY_ID");
} catch (SQLException ex) {
throw new AuditEventCollectorException(
ErrorCodes.ERROR_GETTING_DATA_FROM_SOURCE, null, ex);
}
}
public void setAttribute(String name, String value)
throws SetAttributeException {
if (name.equalsIgnoreCase("TimeZoneOffset")) {
m_timeZoneOffset = getTimeZoneOffsetInMs(value);
} else {
throw new SetAttributeException(ErrorCodes.INVALID_ATTRIBUTE_NAME,
new Object[] { name, value }, null);
}
}
public void close() {
try {
if (m_resultSet != null) {
m_resultSet.close();
m_resultSet = null;
}
if (m_connectionManager != null) {
m_connectionManager.destroy();
m_connectionManager = null;
}
m_previousCheckpoint = null;
m_nextCheckpoint = null;
m_logger = null;
} catch (SQLException ex) {
m_logger.logError("SampleEventCollector", "close",
"SQLException occurred. ", ex);
} catch (AuditException ex) {
m_logger.logError("SampleEventCollector", "close",
"AuditException occurred. ", ex);
}
}
}
Java Based Collection Plug-in Manifest File
See how to set up a Java-based collection plug-in for Oracle Database Security Central.
This is a sample manifest file for a Java-based collection.
Example 13 Java-Based Manifest File
<?xml version="1.0"?>
<plugin xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.oracle.com/av/plugin plugin-manifest.xsd"
xmlns="http://xmlns.oracle.com/av/plugin"
name="Sample Plugin"
id="com.oracle.av.plugin.sample"
version="12.1.0.0.0"
provider-name="Oracle Corp."
copyright="Copyright Oracle Corp. 2011">
<!-- targetVersion: Version of Oracle Audit Vault supported by
this plugin. This is represented by the "min" attribute of
targetVersion> tag -->
<targetVersion min="12.1.0.0.0"/>
<extensionSet>
<extensionPoint type= "securedTargetType">
<!-- fileList: Lists *all* the files that ship with the plugin -->
<fileList>
<jars>
<include file="samplecollector.jar"/>
<!-- All your collector Java jar binaries go here -->
</jars>
<templates>
<bin>
<!-- All your collector native binaries go here -->
</bin>
<config>
<!-- Any configuration information (such as .properites files)
go here -->
</config>
<shell>
<!-- Any shell scripts that your collector relies on go here -->
</shell>
<patch>
<!-- Oracle provided patches go here -->
</patch>
<unresolved-external>
<!-- Any files belonging to the unresolved-external category here -->
</unresolved-external>
</fileList>
<!-- securedTargetTypeInfo: Contains source type and trail information
-->
<securedTargetTypeInfo name="Sample"/>
<!-- trailType: contains trail type, location , classname for
source type testSource -->
<trailInfo>
<trailType>TABLE</trailType>
<className name="oracle.av.plugin.sample.collector.SampleEventCollectorFactory" />
</trailInfo >
</extensionPoint>
</extensionSet>
</plugin>