16 Developing Custom Filters, Formatters, and Handlers

This chapter discusses writing Java code to implement an event filter, a custom formatter for a built-in handler, or a custom event handler. Specifying custom formatting through a Velocity template is also covered.

 The Java package names are compliant with the Oracle standard. You must migrate the any previous release custom formatters, handlers, or filters to the new package names.

This chapter includes the following sections:

16.1 Filtering Events

By default, all transactions, operations and metadata events are passed to the DataSourceListener event handlers. An event filter can be implemented to filter the events sent to the handlers. The filter could select certain operations on certain tables containing certain column values, for example

Filters are additive: if more than one filter is set for a handler, then all filters must return true in order for the event to be passed to the handler.

You can configure filters using the Java application properties file:

# handler "foo" only receives certain events
gg.handler.one.type=jms
gg.handler.one.format=mytemplate.vm
gg.handler.one.filter=com.mycompany.MyFilter

To activate the filter, you write the filter and set it on the handler; no additional logic needs to be added to specific handlers.

You can write a custom filter by implementing  the oracle.goldengate.datasource.DsEventFilterinterface filter, which contains filter contracts as in the following example:

package com.mycompany;

import oracle.goldengate.datasource.DsConfiguration;
import oracle.goldengate.datasource.DsEventFilter;
import oracle.goldengate.datasource.DsOperation;
import oracle.goldengate.datasource.DsTransaction;
import oracle.goldengate.datasource.meta.DsMetaData;
import oracle.goldengate.datasource.meta.TableMetaData;

public class MyFilter implements DsEventFilter {
  @Override public void init(DsConfiguration conf) {
  }

  @Override public void destroy() {
  }

  @Override public boolean doProcess(DsTransaction tx, DsMetaData metaData) {return true// transactionBegin(), transactionCommit(), transactionRollback() will be allowed.}

  @Override public boolean doProcess(DsOperation op, DsMetaData metaData) {return true// operationAdded() will be allowed.}

  @Override public boolean doProcess(TableMetaData tbl,DsMetaData meta) {return true// metaDataChanged() will be allowed.}
}

16.2 Custom Formatting

You can customize the output format of a built-in handler by:

  • Writing a custom formatter in Java or

  • Using a Velocity template

16.2.1 Using a Velocity Template

As an alternative to writing Java code for custom formatting, Velocity templates can be a good alternative to quickly prototype formatters. For example, the following template could be specified as the format of a JMS or file handler:

Transaction: numOps='$tx.size' ts='$tx.timestamp'
#for each( $op in $tx )
operation: $op.sqlType, on table "$op.tableName":
#for each( $col in $op )
$op.tableName, $col.meta.columnName = $col.value
#end
#end

If the template were named sample.vm, it could be placed in the classpath, for example:

gg_install_dir/dirprm/sample.vm

Note:

If using Velocity templates, the file name must end with the suffix .vm; otherwise the formatter is presumed to be a Java class.

Update the Java application properties file to use the template:

# set properties on 'one'
gg.handler.one.type=file
gg.handler.one.format=sample.vm
gg.handler.one.file=output.xml

When modifying templates, there is no need to recompile any Java source; simply save the template and re-run the Java application. When the application is run, the following output would be generated (assuming a table named SCHEMA.SOMETABLE, with columns TESTCOLA and TESTCOLB):

Transaction: numOps='3' ts='2008-12-31 12:34:56.000'
operation: UPDATE, on table "SCHEMA.SOMETABLE":
SCHEMA.SOMETABLE, TESTCOLA = value 123
SCHEMA.SOMETABLE, TESTCOLB = value abc
operation: UPDATE, on table "SCHEMA.SOMETABLE":
SCHEMA.SOMETABLE, TESTCOLA = value 456
SCHEMA.SOMETABLE, TESTCOLB = value def
operation: UPDATE, on table "SCHEMA.SOMETABLE":
SCHEMA.SOMETABLE, TESTCOLA = value 789
SCHEMA.SOMETABLE, TESTCOLB = value ghi

16.2.2 Coding a Custom Formatter in Java

The preceding examples show a JMS handler and a file output handler using the same formatter (com.mycompany.MyFormatter). The following is an example of how this formatter may be implemented.

Example 16-1 Custom Formatting Implementation

package com.mycompany.MyFormatter;
import oracle.goldengate.datasource.DsOperation;
import oracle.goldengate.datasource.DsTransaction;
import oracle.goldengate.datasource.format.DsFormatterAdapter;
import oracle.goldengate.datasource.meta.ColumnMetaData;
import oracle.goldengate.datasource.meta.DsMetaData;
import oracle.goldengate.datasource.meta.TableMetaData;
import java.io.PrintWriter;
public class MyFormatter extends DsFormatterAdapter {
        public MyFormatter() { }
        @Override
        public void formatTx(DsTransaction tx,
DsMetaData meta,
PrintWriter out)
        {
            out.print("Transaction: " );
            out.print("numOps=\'" + tx.getSize() + "\' " );
            out.println("ts=\'" + tx.getStartTxTimeAsString() + "\'");
            for(DsOperation op: tx.getOperations()) {
TableName currTable = op.getTableName();
TableMetaData tMeta = dbMeta.getTableMetaData(currTable);
String opType = op.getOperationType().toString();
String table = tMeta.getTableName().getFullName();
out.println(opType + " on table \"" + table + "\":" );
int colNum = 0;
for(DsColumn col: op.getColumns())
{
ColumnMetaData cMeta = tMeta.getColumnMetaData( colNum++ );
out.println(
cMeta.getColumnName() + " = " + col.getAfterValue() );
}
        }
        @Override
        public void formatOp(DsTransaction tx,
DsOperation op,
TableMetaData tMeta,
PrintWriter out)
        {
            // not used...
        }
}

The formatter defines methods for either formatting complete transactions (after they are committed) or individual operations (as they are received, before the commit). If the formatter is in operation mode, then formatOp(...) is called; otherwise, formatTx(...) is called at transaction commit.

To compile and use this custom formatter, include the Oracle GoldenGate for Java JARs in the classpath and place the compiled .class files in gg_install_dir/dirprm:

javac -d gg_install_dir/dirprm
-classpath ggjava/ggjava.jar MyFormatter.java

The resulting class files are located in resources/classes (in correct package structure):

gg_install_dir/dirprm/com/mycompany/MyFormatter.class

Alternatively, the custom classes can be put into a JAR; in this case, either include the JAR file in the JVM classpath using the user exit properties (using java.class.path in the jvm.bootoptions property), or by setting the Java application properties file to include your custom JAR:

# set properties on 'one'
gg.handler.one.type=file
gg.handler.one.format=com.mycompany.MyFormatter
gg.handler.one.file=output.xml
gg.classpath=/path/to/my.jar,/path/to/directory/of/jars/*

16.2.3 Coding a Custom Handler in Java

A custom handler can be implemented by extending AbstractHandler as in the following example:

import oracle.goldengate.datasource.*;
import static oracle.goldengate.datasource.GGDataSource.Status;
public class SampleHandler extends AbstractHandler {
        @Override
        public void init(DsConfiguration conf, DsMetaData metaData) {
            super.init(conf, metaData);
            // ... do additional config...
        }
        @Override
        public Status operationAdded(DsEvent e, DsTransaction tx, DsOperation op) { ... }
        @Override
        public Status transactionCommit(DsEvent e, DsTransaction tx) { ... }
        @Override
        public Status metaDataChanged(DsEvent e, DsMetaData meta) { .... }
        @Override
        public void destroy() { /* ... do cleanup ... */ }
        @Override
        public String reportStatus() { return "status report..."; }
        @Override
        public Status ddlOperation(OpType opType, ObjectType objectType, String objectName, String ddlText) }

The method in AbstractHandler is not abstract rather it has a body. In the body it performs cached metadata invalidation by marking the metadata object as dirty. It also provides TRACE-level logging of DDL events when the ddlOperation method is specified. You can override this method in your custom handler implementations. You should always call the super method before any custom handling to ensure the functionality in AbstractHandler is executed

When a transaction is processed from the Extract, the order of calls into the handler is as follows:

  1. Initialization:

    • First, the handler is constructed.

    • Next, all the "setters" are called on the instance with values from the property file.

    • Finally, the handler is initialized; the init(...) method is called before any transactions are received. It is important that the init(...) method call super.init(...) to properly initialize the base class.

  2. Metadata is thenreceived. If the user exit is processing an operation on a table not yet seen during this run, a metadata event is fired, and the metadataChanged(...) method is called. Typically, there is no need to implement this method. The DsMetaData is automatically updated with new data source metadata as it is received.

  3. A transaction is started. A transaction event is fired, causing the transactionBegin(...) method on the handler to be invoked (this is not shown). This is typically not used, since the transaction has zero operations at this point.

  4. Operations are added to the transaction, one after another. This causes the operationAdded(...) method to be called on the handler for each operation added. The containing transaction is also passed into the method, along with the data source metadata that contains all processed table metadata. The transaction has not yet been committed, and could be aborted before the commit is received.

    Each operation contains the column values from the transaction (possibly just the changed values when Extract is processing with compressed updates.) The column values may contain both before and after values.

    For the ddlOperation method, the options are:

    • opType - Is an enumeration that identifies the DDL operation type that is occurring (CREATE, ALTER, and so on).

    • objectType - Is an enumeration that identifies the type of the target of the DDL (TABLE, VIEW, and so on).

    • objectName - Is the fully qualified source object name; typically a fully qualified table name.

    • ddlText - Is the raw DDL text executed on the source relational database.

  5. The transaction is committed. This causes the transactionCommit(...) method to be called.

  6. Periodically, reportStatus may be called; it is also called at process shutdown. Typically, this displays the statistics from processing (the number of operations andtransactions processed and other details).

An example of a simple printer handler, which just prints out very basic event information for transactions, operations and metadata follows. The handler also has a property myoutput for setting the output file name; this can be set in the Java application properties file as follows:

gg.handlerlist=sample
# set properties on 'sample'
gg.handler.sample.type=sample.SampleHandler
gg.handler.sample.myoutput=out.txt

To use the custom handler,

  1. Compile the class

  2. Include the class in the application classpath,

  3. Add the handler to the list of active handlers in the Java application properties file.

To compile the handler, include the Oracle GoldenGate for Java JARs in the classpath and place the compiled .class files in gg_install_dir/javaue/resources/classes:

javac -d gg_install_dir/dirprm
-classpath ggjava/ggjava.jar SampleHandler.java

The resulting class files would be located in resources/classes, in correct package structure, such as:

gg_install_dir/dirprm/sample/SampleHandler.class

Note:

For any Java application development beyond hello world examples, either Ant or Maven would be used to compile, test and package the application. The examples showing javac are for illustration purposes only.

Alternatively, custom classes can be put into a JAR and included in the classpath. Either include the custom JAR file(s) in the JVM classpath using the user exit properties (using java.class.path in the jvm.bootoptions property), or by setting the Java application properties file to include your custom JAR:

# set properties on 'one'
gg.handler.one.type=sample.SampleHandler
gg.handler.one.myoutput=out.txt
gg.classpath=/path/to/my.jar,/path/to/directory/of/jars/*

The classpath property can be set on any handler to include additional individual JARs, a directory (which would contain resources or extracted class files) or a whole directory of JARs. To include a whole directory of JARs, use the Java 6 style syntax:

c:/path/to/directory/* (or on UNIX: /path/to/directory/* )

Only the wildcard * can be specified; a file pattern cannot be used. This automatically matches all files in the directory ending with the .jar suffix. To include multiple JARs or multiple directories, you can use the system-specific path separator (on UNIX, the colon and on Windows the semicolon) or you can use platform-independent commas, as shown in the preceding example.

If the handler requires many properties to be set, just include the property in the parameter file, and your handler's corresponding "setter" will be called. For example:

gg.handler.one.type=com.mycompany.MyHandler
gg.handler.one.myOutput=out.txt
gg.handler.one.myCustomProperty=12345

The preceding example would invoke the following methods in the custom handler:

public void setMyOutput(String s) {
        // use the string...
} public void setMyCustomProperty(int j) {
        // use the int...
}

Any standard Java type may be used, such as int, long, String, boolean. For custom types, you may create a custom property editor to convert the String to your custom type.

16.2.4 Coding a Custom Formatter for Java Delivery

You can develop a custom formatter for use with Java Delivery. The following ExampleFormatter.java example demonstrates how to:
  • access and output metadata,

  • access and output the column data values of the operation, and

  • ascertain if the operation is a insert, update, delete, or primary key update.

Example 16-2 Custom Formatter for Java Delivery

/*
 *
 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
 *
 */
package oracle.goldengate.datasource.format;

//Logging imports
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.PrintWriter;
import oracle.goldengate.datasource.DsColumn;
import oracle.goldengate.datasource.DsConfiguration;
import oracle.goldengate.datasource.DsOperation;
import oracle.goldengate.datasource.DsToken;
import oracle.goldengate.datasource.DsTransaction;
import oracle.goldengate.datasource.TxOpMode;
import oracle.goldengate.datasource.meta.ColumnMetaData;
import oracle.goldengate.datasource.meta.DsMetaData;
import oracle.goldengate.datasource.meta.TableMetaData;
import oracle.goldengate.datasource.meta.TableName;
import oracle.goldengate.util.CDataUtil;

/**
 * This class provides an example for Oracle GoldenGate Java Adapter customers 
 * to write their own custom formatter which can be plugged into the Java file 
 * writer or Java JMS handler.
 * This example does NOT work with the new Big Data integrations such as HDFS, Kafka,
 * or Flume as formatter interface for those has been changed.  
 * This formatter formats data in CSV format, by default.  The field and line 
 * delimiters are configurable with the default as a comma and a line feed
 * respectively.
 * The goal is to provide an example custom formatter for customers wishing
 * to develop a custom formatter, to see how it works.  The functionality
 * can be extended and/or altered as needed to fulfill the specific needs of 
 * the customer.
 * User configures the use of this formatter by setting the follow configuration
 * in the Oracle GoldenGate Java Adapter properties file.
 * gg.handler.name.format=oracle.goldengate.datasource.format.ExampleFormatter
 * @author Tom Campbell 
 * @email thomas.campbell@oracle.com
 */
public class ExampleFormatter extends DsFormatterAdapter{
    private static final Logger logger=LoggerFactory.getLogger(ExampleFormatter.class);
    
    //The field delimiter defaults to a comma
    private String fieldDelimiter = ",";
    //The line delimiter defaults to the line separator
    private String lineDelimiter = System.lineSeparator();
    
    /**
     * Default Constructor
     */
    public ExampleFormatter() {
        super(TxOpMode.op); 
        //IMPLEMENTERS - Add constructor implementation code below
    }
    
    /**
     * Constructor with operation mode argument.
     * @param mode Mode of operation Transaction (tx) or Operation (op)
     */
    public ExampleFormatter(TxOpMode mode) {
        super(mode);
        //IMPLEMENTERS - Add constructor implementation code below
    }
    
    /**
     * Setter method to set the field delimiter.  User can configure 
     * generic setters on the formatter than are configured in the Oracle 
     * GoldenGate Java Adapter properties file as follows:
     * gg.handler.name.format.fieldDelimiter=somevalue
     * The user is free to generate any setter methods for the formatter.  The
     * configuration initialization code uses Java reflection to find and invoke
     * the corresponding setter method.  If the method is not found an exception
     * will be thrown and the Oracle GoldenGate process will ABEND.
     * @param fd The field delimiter value
     */
    void setFieldDelimiter(String fd){
        //The CDataUtil.unwrapCData allows the user to configure values that
        //are considered to be whitespace.  Examples are spaces, tabs, carriage
        //returns, line feeds, etc.  To configure white space use the CDATA[] wrapper
        //as follows.
        //gg.handler.name.format.fieldDelimiter=CDATA[somevalue]
        //The configuration value will be "somevalue".
        //The CDATA[] functionality is needed to preserve whitespace because the
        //default behavior of the GG config code is to trim whitespace.
        fieldDelimiter = CDataUtil.unwrapCData(fd);
    }
    
    /**
     * Setter method to set the line delimiter.  User can configure 
     * generic setters on the formatter than are configured in the Oracle 
     * GoldenGate Java Adapter properties file as follows:
     * gg.handler.name.format.lineDelimiter=somevalue
     * The user is free to generate any setter methods for the formatter.  The
     * configuration initialization code uses Java reflection to find and invoke
     * the corresponding setter method.  If the method is not found an exception
     * will be thrown and the Oracle GoldenGate process will ABEND.
     * @param fd The field delimiter value
     */
    void setLineDelimiter(String ld){
        //The CDataUtil.unwrapCData allows the user to configure values that
        //are considered to be whitespace.  Examples are spaces, tabs, carriage
        //returns, line feeds, etc.  To configure whitespace use the CDATA[] wrapper
        //as follows.
        //gg.handler.name.format.fieldDelimiter=CDATA[\n]
        //The configuration value will be a line feed.
        //The CDATA[] functionality is needed to preserve whitespace because the
        //default behavior of the GG config code is to trim whitespace.
        lineDelimiter = CDataUtil.unwrapCData(ld);
    }
    
    /**
     * Initialization method.  This is called once after all of the setter
     * methods are called to set the explicit configuration methods of the 
     * formatter.
     * @param conf data source configuration info from properties file
     * @param meta metadata from data source, describing columns/tables, etc
     */
    @Override
    public void init(DsConfiguration conf, DsMetaData meta) {
        //Call the base class first.
        super.init(conf, meta);
        //IMPLEMENTERS - Add initialization code below
        logger.info("Initializing the example formatter.");
        logger.info("  The field delimiter is configured as [" + fieldDelimiter + "].");
        logger.info("  The line delimiter is configured as [" + lineDelimiter + "].");
    }

    /**
     * The format operation method.  This is where the bulk of the formatting
     * logic will reside.
     * @param tx The transaction object
     * @param op The operation object
     * @param tmeta The table metadata object
     * @param writer The print writer object to which results should be written
     */
    @Override
    public void formatOp(DsTransaction tx, DsOperation op, TableMetaData tmeta, 
            PrintWriter writer) {
        logger.info("Method formatOp called...");
        //IMPLEMENTERS - Below is the call the example implementation
        //comment out and insert custom implementation here.
        internalFormatOperation(tx, op, tmeta, writer);
    }

    @Override
    public void formatTx(DsTransaction tx, DsMetaData dbmeta, PrintWriter writer) {
        logger.info("Method formatTx called...");
        //In transaction mode (i.e. gg.handler.name.mode=tx) the handler may 
        //be configured to call this method to format data at the end of the 
        //into a single print writer.  This is probably not the usual use case.
        //Using replicat transaction grouping (see GROUPTRANSOPS replicat configuration)
        //the number of operations in a grouped transaction can be substantial.
        //Replicat GROUPTRANSOPS is set to 1000 by default meaning as many as
        //1000 source transactions are getting grouped into a single target
        //transaction.  
        for (DsOperation op : tx) {
            TableName currTable = op.getTableName();
            TableMetaData tmeta = dbmeta.getTableMetaData(currTable);
            //Then call the internal formatting
            internalFormatOperation(tx, op, tmeta, writer);
        }
    }
    
    private void internalFormatOperation(DsTransaction tx, DsOperation op, 
            TableMetaData tmeta, PrintWriter writer) {
        //Use a string builder for performance.
        StringBuilder sb = new StringBuilder();
        
        //IMPLEMENTERS - example code shown below outputs commonly used data.
        //Modify the code below as need for the specific implementation.
        
        //Format the table name
        formatTableName(op, sb);
        //Format the operation type.
        formatOperationType(op, sb);
        //Format the operation timestamp.
        formatOperationTimestamp(op, sb);
        //Format the position
        formatPosition(op, sb);
        //Add primary keys
        formatPrimaryKeys(tmeta, sb);
        //Format tokens
        formatGGTokens(op, sb);
        //Format the column data
        formatColumnData(op, tmeta, sb);  
        
        //Transfer the message to the print writer.
        writer.print(sb.toString());
    }
    
    /**
     * This method formats the table name for output
     * @param op The operation object
     * @param sb The string builder object to append the data
     */
    private void formatTableName(DsOperation op, StringBuilder sb){
        sb.append("TableName");
        sb.append(fieldDelimiter);
        //The original fully qualified table name in the original case
        sb.append(op.getTableName().getOriginalName());
        sb.append(fieldDelimiter);
    }
    
    /**
     * Method to format the operation type data.  Operation types are 
     * insert, update, delete, and primary key update.
     * @param op The operation object
     * @param sb The string builder object to append the data
     */
    private void formatOperationType(DsOperation op, StringBuilder sb){
                //Output the operation type
        sb.append("OperationType");
        sb.append(fieldDelimiter);
        if (op.getOperationType().isInsert()){
            sb.append("insert");
        }else if (op.getOperationType().isDelete()){
            sb.append("delete");
        }else if (op.getOperationType().isPkUpdate()){
            //This is a specialzied use case of update.  The isUpdate() call also
            //returns true for primary key updates.
            sb.append("primarykeyupdate");
        }else if (op.getOperationType().isUpdate()){
            sb.append("update");
        }
        sb.append(fieldDelimiter);
    }
    
    /**
     * Method to format the operation timestamp.  This is the transaction commit
     * time of the transaction that contains the operation.  All operations
     * within a transaction have the same operation timestamp.
     * @param op The operation object
     * @param sb The string builder object to append the data
     */
    private void formatOperationTimestamp(DsOperation op, StringBuilder sb){
        sb.append("OperationTimestamp");
        sb.append(fieldDelimiter);
        sb.append(op.getTimestampAsString());
        sb.append(fieldDelimiter);
    }
    
    /**
     * Method to format the position.
     * The position is the concatentated trail file sequence number followed
     * by the RBA number (offset in the trail file).  The two together provide
     * traceability of the operation back to source trail file.
     * @param op The operation object
     * @param sb The string builder object to append the data.
     */
    private void formatPosition(DsOperation op, StringBuilder sb){
        sb.append("Position");
        sb.append(fieldDelimiter);
        sb.append(op.getPosition());
        sb.append(fieldDelimiter);
    }
    
    /**
     * Method to format the primary keys.
     * @param tmeta The table metadata object.
     * @param sb The string builder object to append the data.
     */
    private void formatPrimaryKeys(TableMetaData tmeta, StringBuilder sb){
        sb.append("PrimaryKeys");
        for(ColumnMetaData cmeta :tmeta.getColumnMetaData()){
            if (cmeta.isKeyCol()){
                sb.append(fieldDelimiter);
                sb.append(cmeta.getOriginalColumnName());
            }
        }
    }
    
    /**
     * Method to format the GoldenGate token key/value pairs from the source
     * trail file.
     * @param op The operation object.
     * @param sb The string builder object to append the data.
     */
    private void formatGGTokens(DsOperation op, StringBuilder sb){
        //If this is false there will not be any tokens
        if(op.getIncludeTokens()){
            sb.append(fieldDelimiter);
            sb.append("GGTokens");
            sb.append(fieldDelimiter);
            for(DsToken token : op.getTokens().values()){
                sb.append(token.getKey());
                sb.append(fieldDelimiter);
                sb.append(token.getValue());
                sb.append(fieldDelimiter);
            }
        }
    }
    
    /**
     * Method to output the column value data.  This data starts with the column
     * name, "before", the before image value, "after, and the after image value.
     * This before and after column values include special handling for missing
     * and null values.  Missing values are output as "MISSING".  Null values are
     * output as "NULL"
     * @param op The operation object.
     * @param tmeta The table metadata object
     * @param sb 
     */
    private void formatColumnData(DsOperation op, TableMetaData tmeta, StringBuilder sb){
        int cIndex = 0;
        sb.append(fieldDelimiter);
        sb.append("TableData");
        for(DsColumn col : op.getColumns()) {
            //Get the column metadata
            ColumnMetaData cmeta = tmeta.getColumnMetaData(cIndex++);
            //Output the column name
            sb.append(fieldDelimiter);
            sb.append(cmeta.getOriginalColumnName());
            sb.append(fieldDelimiter);
            
            //Output the before value
            //Insert operations have no before values
            //Delete operations generally only have column values for primary
            //keys and not the complete row.
            //Updates will have all column values if NOCOMPRESSUPDATES is configured
            //else updates will have only primary keys and the column values that changed.
            sb.append("Before");
            sb.append(fieldDelimiter);
            if (col.getBefore() == null){
                //The before image value is missing for the column.
                sb.append("MISSING");
            }else if(col.getBefore().isValueNull()){
                sb.append("NULL");
            }else{
                sb.append(col.getBefore().getValue());
            }
            sb.append(fieldDelimiter);
            
            //Output the after value
            //Insert operations have after values
            //Delete operations have no after values
            //Update operations will have all column values if NOCOMPRESSUPDATES is configured
            //else updates will have only primary keys and the column values that changed.
            sb.append("After");
            sb.append(fieldDelimiter);
            if (col.getAfter() == null){
                //The before image value is missing for the column.
                sb.append("MISSING");
            }else if(col.getAfter().isValueNull()){
                sb.append("NULL");
            }else{
                sb.append(col.getAfter().getValue());
            }       
        }
        //Add the line delimiter at the end
        sb.append(lineDelimiter);
    }

}

Note:

This example does not work with Oracle GoldenGate for Big Data handlers.

This type of custom formatter works well with the JMS and the Java File Writer handlers. Once processed, this outputs operation data in a CSV format. Operation metadata is output first, and then the column data including before and after change column image data. The field and line delimiters are configurable.

Tip:

 You can obtain a copy of the maven project that you import to your Java IDE by contacting Oracle Support.

16.3 Additional Resources

There is Javadoc available for the Java API. The Javadoc has been intentionally reduced to a set of core packages, classes and interfaces in order to only distribute the relevant interfaces and classes useful for customizing and extension.

In each package, some classes have been intentionally omitted for clarity. The important classes are:

  • oracle.goldengate.datasource.DsTransaction: represents a database transaction. A transaction contains zero or more operations.

  • oracle.goldengate.datasource.DsOperation: represents a database operation (insert, update, delete). An operation contains zero or more column values representing the data-change event. Columns indexes are offset by zero in the Java API.

  • oracle.goldengate.datasource.DsColumn: represents a column value. A column value is a composite of a before and an after value. A column value may be 'present' (having a value or be null) or 'missing' (is not included in the source trail).

    • oracle.goldengate.datasource.DsColumnComposite is the composite

    • oracle.goldengate.datasource.DsColumnBeforeValue is the column value before the operation (this is optional, and may not be included in the operation)

    • oracle.goldengate.datasource.DsColumnAfterValue is the value after the operation

  • oracle.goldengate.datasource.meta.DsMetaData: represents all database metadata seen; initially, the object is empty. DsMetaData contains a hash map of zero or more instances of TableMetaData, using the TableName as a key.

  • oracle.goldengate.datasource.meta.TableMetaData: represents all metadata for a single table; contains zero or more ColumnMetaData.

  • oracle.goldengate.datasource.meta.ColumnMetaData: contains column names and data types, as defined in the database or in the Oracle GoldenGate source definitions file.

See the Javadoc for additional details.