package com.linkedin.databus2.producers;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.BinaryEncoder;
import org.apache.avro.io.Encoder;
import org.apache.log4j.Logger;
import com.linkedin.databus.core.DbusConstants;
import com.linkedin.databus.core.DbusEventBufferAppendable;
import com.linkedin.databus.core.DbusEventInfo;
import com.linkedin.databus.core.DbusEventKey;
import com.linkedin.databus.core.UnsupportedKeyException;
import com.linkedin.databus.core.monitoring.mbean.DbusEventsStatisticsCollector;
import com.linkedin.databus2.core.DatabusException;
import com.linkedin.databus2.producers.ds.DbChangeEntry;
import com.linkedin.databus2.producers.ds.KeyPair;
import com.linkedin.databus2.producers.ds.PrimaryKeySchema;
import com.linkedin.databus2.relay.config.ReplicationBitSetterStaticConfig;
import com.linkedin.databus2.relay.config.ReplicationBitSetterStaticConfig.SourceType;
import com.linkedin.databus2.schemas.SchemaId;
import com.linkedin.databus2.schemas.utils.SchemaHelper;
public class OpenReplicatorAvroEventFactory
{
/** Avro schema for the generated events. */
protected final Schema _eventSchema;
/** Unique schema ID for the _eventSchema. */
protected final byte[] _schemaId;
/** Source ID for this event source. */
protected final int _sourceId;
/** Physical source id */
protected final int _pSourceId;
/** PartitionFunction used to generate the event partition based on event key. */
protected final PartitionFunction _partitionFunction;
/** Logger for error and debug messages. */
private final Logger _log = Logger.getLogger(getClass());
/** key Schema. */
private final PrimaryKeySchema _pKeySchema ;
/** Replication BitSetter StaticConfig **/
private final ReplicationBitSetterStaticConfig _replSetterConfig;
private final Pattern _replBitSetterPattern;
public static final String MODULE = OpenReplicatorAvroEventFactory.class.getName();
public static final Logger LOG = Logger.getLogger(MODULE);
public OpenReplicatorAvroEventFactory(int sourceId, int pSourceId,
String eventSchema, PartitionFunction partitionFunction,
ReplicationBitSetterStaticConfig replSetterConfig)
throws DatabusException
{
_sourceId = sourceId;
_pSourceId = pSourceId;
_eventSchema = Schema.parse(eventSchema);
_schemaId = SchemaHelper.getSchemaId(eventSchema);
_partitionFunction = partitionFunction;
_replSetterConfig = replSetterConfig;
if ((null != _replSetterConfig) && (SourceType.COLUMN.equals(_replSetterConfig.getSourceType())))
_replBitSetterPattern = Pattern.compile(replSetterConfig.getRemoteUpdateValueRegex());
else
_replBitSetterPattern = null;
String keyName = SchemaHelper.getMetaField(_eventSchema, "pk");
if(keyName == null)
{
throw new DatabusException("The event schema is missing the required field \"key\".");
}
_pKeySchema = new PrimaryKeySchema(keyName);
}
public int createAndAppendEvent(DbChangeEntry changeEntry,
DbusEventBufferAppendable eventBuffer,
boolean enableTracing,
DbusEventsStatisticsCollector dbusEventsStatisticsCollector)
throws EventCreationException, UnsupportedKeyException, DatabusException
{
Object keyObj = obtainKey(changeEntry);
//Construct the Databus Event key, determine the key type and construct the key
DbusEventKey eventKey = new DbusEventKey(keyObj);
short lPartitionId = _partitionFunction.getPartition(eventKey);
//Get the md5 for the schema
SchemaId schemaId = SchemaId.createWithMd5(changeEntry.getSchema());
byte[] payload = serializeEvent(changeEntry.getRecord());
DbusEventInfo eventInfo = new DbusEventInfo(changeEntry.getOpCode(),
changeEntry.getScn(),
(short)_pSourceId,
lPartitionId,
changeEntry.getTimestampInNanos(),
(short)_sourceId,
schemaId.getByteArray(),
payload,
enableTracing,
false);
boolean success = eventBuffer.appendEvent(eventKey, eventInfo, dbusEventsStatisticsCollector);
return success ? payload.length : -1;
}
protected byte[] serializeEvent(GenericRecord record)
throws EventCreationException
{
// Serialize the row
byte[] serializedValue;
try
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Encoder encoder = new BinaryEncoder(bos);
GenericDatumWriter<GenericRecord> writer = new GenericDatumWriter<GenericRecord>(record.getSchema());
writer.write(record, encoder);
serializedValue = bos.toByteArray();
}
catch(IOException ex)
{
throw new EventCreationException("Failed to serialize the Avro GenericRecord", ex);
}
catch(RuntimeException ex)
{
// Avro likes to throw RuntimeExceptions instead of checked exceptions when serialization fails.
_log.error("Exception for record: " + record + " with schema: " + record.getSchema().getFullName());
throw new EventCreationException("Failed to serialize the Avro GenericRecord", ex);
}
return serializedValue;
}
public int getSourceId()
{
return _sourceId;
}
/**
* Given a DBImage, returns the key
* If it is a single key, it returns the object if it is LONG /INT / STRING
* For compound key, it casts the fields as String, delimits the fields and returns the appended string
* @param dbChangeEntry The post-image of the event
* @return Actual key object
* @throws DatabusException
*/
private Object obtainKey(DbChangeEntry dbChangeEntry)
throws DatabusException
{
if (null == dbChangeEntry) {
throw new DatabusException("DBUpdateImage is null");
}
List<KeyPair> pairs = dbChangeEntry.getPkeys();
if (null == pairs || pairs.size() == 0) {
throw new DatabusException("There do not seem to be any keys");
}
if (pairs.size() == 1) {
Object key = pairs.get(0).getKey();
Schema.Type pKeyType = pairs.get(0).getKeyType();
Object keyObj = null;
if (pKeyType == Schema.Type.INT)
{
if (key instanceof Integer)
{
keyObj = key;
}
else
{
throw new DatabusException(
"Schema.Type does not match actual key type (INT) "
+ key.getClass().getName());
}
} else if (pKeyType == Schema.Type.LONG)
{
if (key instanceof Long)
{
keyObj = key;
}
else
{
throw new DatabusException(
"Schema.Type does not match actual key type (LONG) "
+ key.getClass().getName());
}
keyObj = key;
}
else
{
keyObj = key;
}
return keyObj;
} else {
// Treat multiple keys as a separate case to avoid unnecessary casts
Iterator<KeyPair> li = pairs.iterator();
StringBuilder compositeKey = new StringBuilder();
while (li.hasNext())
{
KeyPair kp = li.next();
Schema.Type pKeyType = kp.getKeyType();
Object key = kp.getKey();
if (pKeyType == Schema.Type.INT)
{
if (key instanceof Integer)
compositeKey.append(kp.getKey().toString());
else
throw new DatabusException(
"Schema.Type does not match actual key type (INT) "
+ key.getClass().getName());
}
else if (pKeyType == Schema.Type.LONG)
{
if (key instanceof Long)
compositeKey.append(key.toString());
else
throw new DatabusException(
"Schema.Type does not match actual key type (LONG) "
+ key.getClass().getName());
}
else
{
compositeKey.append(key);
}
if (li.hasNext()) {
// Add the delimiter for all keys except the last key
compositeKey.append(DbusConstants.COMPOUND_KEY_DELIMITER);
}
}
return compositeKey.toString();
}
}
}