com.jkoolcloud.tnt4j.streams.parsers.KafkaProducerRecordParser Maven / Gradle / Ivy
/*
* Copyright 2014-2023 JKOOL, LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jkoolcloud.tnt4j.streams.parsers;
import java.text.ParseException;
import java.util.EnumSet;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.kafka.clients.producer.ProducerRecord;
import com.jkoolcloud.tnt4j.core.OpLevel;
import com.jkoolcloud.tnt4j.sink.EventSink;
import com.jkoolcloud.tnt4j.streams.fields.ActivityFieldDataType;
import com.jkoolcloud.tnt4j.streams.fields.ActivityFieldLocator;
import com.jkoolcloud.tnt4j.streams.fields.ActivityFieldLocatorType;
import com.jkoolcloud.tnt4j.streams.utils.*;
/**
* Implements an activity data parser that assumes each activity data item is an plain java {@link ProducerRecord} data
* structure, where each field is represented by declared class field and the field name is used to map each field into
* its corresponding activity field.
*
* List of supported field names:
*
* - topic - topic name record is being sent to
* - partition - partition identifier to which the record will be sent
* - timestamp - record timestamp value
* - key - record key
* - value - record data
* - headers - record headers iterable
*
*
* If {@code key} or {@code value} contains complex data, use stacked parsers to parse that data. Or if it can be
* treated as simple Java object (POJO), particular field value can be resolved defining class field names within
* locator path string. Locator path string should be used resolving particular {@code headers} collection contained
* value: path element should define header key or index.
*
* This activity parser supports configuration properties from {@link GenericActivityParser} (and higher hierarchy
* parsers).
*
* This activity parser supports those activity field locator types:
*
* - {@link com.jkoolcloud.tnt4j.streams.fields.ActivityFieldLocatorType#Label}
* - {@link com.jkoolcloud.tnt4j.streams.fields.ActivityFieldLocatorType#StreamProp}
* - {@link com.jkoolcloud.tnt4j.streams.fields.ActivityFieldLocatorType#Cache}
* - {@link com.jkoolcloud.tnt4j.streams.fields.ActivityFieldLocatorType#Activity}
* - {@link com.jkoolcloud.tnt4j.streams.fields.ActivityFieldLocatorType#Expression}
* - {@link com.jkoolcloud.tnt4j.streams.fields.ActivityFieldLocatorType#ParserProp}
* - {@link com.jkoolcloud.tnt4j.streams.fields.ActivityFieldLocatorType#SystemProp}
* - {@link com.jkoolcloud.tnt4j.streams.fields.ActivityFieldLocatorType#EnvVariable}
*
*
* @version $Revision: 1 $
*/
public class KafkaProducerRecordParser extends GenericActivityParser> {
private static final EventSink LOGGER = LoggerUtils.getLoggerSink(KafkaProducerRecordParser.class);
/**
* Constructs a new KafkaProducerRecordParser.
*/
public KafkaProducerRecordParser() {
super(ActivityFieldDataType.AsInput);
}
@Override
protected EventSink logger() {
return LOGGER;
}
/**
* Returns whether this parser supports the given format of the activity data. This is used by activity streams to
* determine if the parser can parse the data in the format that the stream has it.
*
* This parser supports the following class types (and all classes extending/implementing any of these):
*
* - {@link org.apache.kafka.clients.producer.ProducerRecord}
*
*
* @param data
* data object whose class is to be verified
* @return {@code true} if this parser can process data in the specified format, {@code false} - otherwise
*/
@Override
protected boolean isDataClassSupportedByParser(Object data) {
return data instanceof ProducerRecord;
}
@Override
public void setProperty(String name, String value) {
super.setProperty(name, value);
// // no any additional properties are required yet.
// if (false) {
// logger().log(OpLevel.DEBUG,
// StreamsResources.getString(StreamsResources.RESOURCE_BUNDLE_NAME, "ActivityParser.setting"), name,
// value);
// }
}
@Override
public Object getProperty(String name) {
return super.getProperty(name);
}
@Override
protected Object resolveLocatorValue(ActivityFieldLocator locator, ActivityContext cData,
AtomicBoolean formattingNeeded) throws ParseException {
Object val = null;
String locStr = locator.getLocator();
String[] valPath = (String[]) getPreparedLocator(locStr,
k -> Utils.getNodePath(k, StreamsConstants.DEFAULT_PATH_DELIM));
try {
val = getRecordValue(valPath, cData.getData(), 0);
} catch (Exception exc) {
Utils.logThrowable(LOGGER, OpLevel.WARNING,
StreamsResources.getBundle(KafkaStreamConstants.RESOURCE_BUNDLE_NAME),
"KafkaProducerRecordParser.resolve.locator.value.failed", exc);
}
return val;
}
/**
* Resolves {@link org.apache.kafka.clients.producer.ProducerRecord} instance field value defined by {@code pRecord}
* fields names {@code path} array.
*
* If producer record {@code key} and {@code value} fields classes are known, it can be processed further defining
* field names of those classes as {@code path} elements.
*
* @param path
* fields path as array of producer record field names
* @param pRecord
* producer record instance to resolve value
* @param i
* processed locator path element index
* @return resolved producer record value, or {@code null} if value is not resolved
* @throws java.lang.RuntimeException
* if field can't be found or accessed
*
* @see KafkaUtils#getHeaders(String[], org.apache.kafka.common.header.Headers, int)
* @see Utils#getFieldValue(String[], Object, int)
*/
protected Object getRecordValue(String[] path, ProducerRecord, ?> pRecord, int i) throws RuntimeException {
if (ArrayUtils.isEmpty(path) || pRecord == null) {
return null;
}
Object val = null;
String propStr = path[i];
if ("topic".equalsIgnoreCase(propStr)) { // NON-NLS
val = pRecord.topic();
} else if ("partition".equalsIgnoreCase(propStr)) { // NON-NLS
val = pRecord.partition();
} else if ("timestamp".equalsIgnoreCase(propStr)) { // NON-NLS
val = pRecord.timestamp();
} else if ("headers".equalsIgnoreCase(propStr)) { // NON-NLS
val = KafkaUtils.getHeaders(path, pRecord.headers(), i);
} else if ("key".equalsIgnoreCase(propStr)) { // NON-NLS
val = Utils.getFieldValue(path, pRecord.key(), i + 1);
} else if ("value".equalsIgnoreCase(propStr)) { // NON-NLS
val = Utils.getFieldValue(path, pRecord.value(), i + 1);
}
return val;
}
@SuppressWarnings("deprecation")
private static final EnumSet UNSUPPORTED_LOCATOR_TYPES = EnumSet
.of(ActivityFieldLocatorType.Index, ActivityFieldLocatorType.Range, ActivityFieldLocatorType.REMatchId);
/**
* {@inheritDoc}
*
* Unsupported activity locator types are:
*
* - {@link com.jkoolcloud.tnt4j.streams.fields.ActivityFieldLocatorType#Index}
* - {@link com.jkoolcloud.tnt4j.streams.fields.ActivityFieldLocatorType#Range}
* - {@link com.jkoolcloud.tnt4j.streams.fields.ActivityFieldLocatorType#REMatchId}
*
*/
@Override
protected EnumSet getUnsupportedLocatorTypes() {
return UNSUPPORTED_LOCATOR_TYPES;
}
}