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>