Kafka Processor does not keep the state of attributes of flowfile - apache-nifi

I update few attributes of the flowfile and put the same in kafka but when I consume the same from consumekafka_2.0 processor that attributes are lost.
Is this not supported ? Do I need to customise this processor?
When I saw the below source code of the processor then I got that it is already reading the attributes from record and writing the same in flowfile then Why these are not available in the flowfile?
private void writeData(final ProcessSession session, ConsumerRecord<byte[], byte[]> record, final TopicPartition topicPartition) {
FlowFile flowFile = session.create();
final BundleTracker tracker = new BundleTracker(record, topicPartition, keyEncoding);
tracker.incrementRecordCount(1);
final byte[] value = record.value();
if (value != null) {
flowFile = session.write(flowFile, out -> {
out.write(value);
});
}
flowFile = session.putAllAttributes(flowFile, getAttributes(record));
tracker.updateFlowFile(flowFile);
populateAttributes(tracker);
session.transfer(tracker.flowFile, REL_SUCCESS);
}

In order to pass attributes you must make use of Kafka headers, otherwise there is no way to pass the attributes across since they are not part of the body of the flow file which is what will become the body of the message in Kafka.
On the publish side, PublishKafka_2_0 has the following property to specify which attributes to send as headers:
static final PropertyDescriptor ATTRIBUTE_NAME_REGEX = new PropertyDescriptor.Builder()
.name("attribute-name-regex")
.displayName("Attributes to Send as Headers (Regex)")
.description("A Regular Expression that is matched against all FlowFile attribute names. "
+ "Any attribute whose name matches the regex will be added to the Kafka messages as a Header. "
+ "If not specified, no FlowFile attributes will be added as headers.")
.addValidator(StandardValidators.REGULAR_EXPRESSION_VALIDATOR)
.expressionLanguageSupported(ExpressionLanguageScope.NONE)
.required(false)
.build();
On the consume side, ConsumeKafka_2_0 has the following property to specify which header fields to add as attributes:
static final PropertyDescriptor HEADER_NAME_REGEX = new PropertyDescriptor.Builder()
.name("header-name-regex")
.displayName("Headers to Add as Attributes (Regex)")
.description("A Regular Expression that is matched against all message headers. "
+ "Any message header whose name matches the regex will be added to the FlowFile as an Attribute. "
+ "If not specified, no Header values will be added as FlowFile attributes. If two messages have a different value for the same header and that header is selected by "
+ "the provided regex, then those two messages must be added to different FlowFiles. As a result, users should be cautious about using a regex like "
+ "\".*\" if messages are expected to have header values that are unique per message, such as an identifier or timestamp, because it will prevent NiFi from bundling "
+ "the messages together efficiently.")
.addValidator(StandardValidators.REGULAR_EXPRESSION_VALIDATOR)
.expressionLanguageSupported(ExpressionLanguageScope.NONE)
.required(false)
.build();

Related

for GET Rest api call using spring-integration, how to read user defined headers?

MessageHeaders has predefined headers like TIMESTAMP, ERROR_CHANNEL etc. but how to access user defined header?
My api has http://localhost:8082/load/1234567?source=ABC and headers like username:testuser
message.getPayload() gives me just this 1234567 so that header is not part of payload, but
Map<String, Object> headers = new HashMap<String, Object>();
Set<String> keys = message.getHeaders().keySet();
MessageHeaders msgHeader = message.getHeaders();
for(String key : keys) {
headers.put(key, msgHeader.get(key));
}
& headers.get("username") returns null.
could someone please help?
I hope that you mean you set a username HTTP header in request.
The HTTP Inbound Channel Adapter (or Gateway) come with a DefaultHttpHeaderMapper by default. This one does only standard HTTP Request headers mapping by default:
private static final String[] HTTP_REQUEST_HEADER_NAMES =
{
HttpHeaders.ACCEPT,
HttpHeaders.ACCEPT_CHARSET,
HttpHeaders.ACCEPT_ENCODING,
HttpHeaders.ACCEPT_LANGUAGE,
HttpHeaders.ACCEPT_RANGES,
HttpHeaders.AUTHORIZATION,
HttpHeaders.CACHE_CONTROL,
HttpHeaders.CONNECTION,
HttpHeaders.CONTENT_LENGTH,
HttpHeaders.CONTENT_TYPE,
HttpHeaders.COOKIE,
HttpHeaders.DATE,
HttpHeaders.EXPECT,
HttpHeaders.FROM,
HttpHeaders.HOST,
HttpHeaders.IF_MATCH,
HttpHeaders.IF_MODIFIED_SINCE,
HttpHeaders.IF_NONE_MATCH,
HttpHeaders.IF_RANGE,
HttpHeaders.IF_UNMODIFIED_SINCE,
HttpHeaders.MAX_FORWARDS,
HttpHeaders.PRAGMA,
HttpHeaders.PROXY_AUTHORIZATION,
HttpHeaders.RANGE,
HttpHeaders.REFERER,
HttpHeaders.TE,
HttpHeaders.UPGRADE,
HttpHeaders.USER_AGENT,
HttpHeaders.VIA,
HttpHeaders.WARNING
};
To include your custom header into a message this channel adapter produces, you just need to incorporate this configuration option:
/**
* Provide the pattern array for request headers to map.
* #param patterns the patterns for request headers to map.
* #return the current Spec.
* #see DefaultHttpHeaderMapper#setOutboundHeaderNames(String[])
*/
public S mappedRequestHeaders(String... patterns) {
and use, for example, just * to map all the headers, or if your requirements are strict only for your headers, then pass their names over there.
See more info in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/http.html#http-header-mapping

How to update tag value for exported metric in micrometer?

Im using micrometer for exporting summery of third party api consumption.
Now I want to precisely count failed requests and export each failed request ids.
Invoking below method for each restTemplate exchange call.
private DistributionSummary incFailedCounter(String requestId) {
this.registry = beanProvider.getRegistry();
DistributionSummary summary = summarys.get(myCounter);
if (summary == null) {
Builder tags = DistributionSummary.builder("failed.test").tags("req_id", requestId, "count", "1");
summary = tags.register(registry);
summarys.put(myCounter, summary);
} else {
String tag = summary.getId().getTag("req_id");
String[] split = tag.split(",");
summary.close();
summarys.put(myCounter,
DistributionSummary.builder("failed.test")
.tags("req_id", tag + ", " + requestId, "count", String.valueOf(split.length + 1))
.register(registry));
}
return summary;
}
This code insert new line to metric for each request.
failed_test_count{count="1",instance="localhost:8080",job="monitor-app",req_id="1157408321"}
failed_test_count{count="2",instance="localhost:8080",job="monitor-app",req_id="1157408321, 1157408321"}
failed_test_count{count="3",instance="localhost:8080",job="monitor-app",req_id="1157408321, 1157408321, 1157408321"}
Problem is this metric size is increased with many requests.
Is there way to remove or replace same tag and export only one dynamic metric with updated req_ids ?
Can not remove or update tags, cause they are immutable. One way is to unregister current meter. used below method to removed registered meter and applied new one.
registry.remove(summary.getId());
This produces one line metric.
failed_test_count{count="4",instance="localhost:8080",job="monitor-app",req_id="1157408321, 58500184, 58500184, 58500184"}

Parsing multi-format & multi line data file in spring batch job

I am writing a spring batch job to process the below mentioned data file and write it into a db.
Sample data file is of this format where I have multiple headers and
each header has a bunch of rows associated with it .
I can have million of records for each header and I can have n number
of headers in a flat file that am processing.My requirement is to
pick a few readers which am concerned .
For all the picked readers I need to pick all the data rows .Each
header and its data format is also different .I can receive either of
these data in my processor and need to write them into my DB.
HDR01
A|41|57|Data1|S|62|Data2|9|N|2017-02-01 18:01:05|2017-02-01 00:00:00
A|41|57|Data1|S|62|Data2|9|N|2017-02-01 18:01:05|2017-02-01 00:00:00
HDR02
A|41|57|Data1|S|62|Data2|9|N|
A|41|57|Data1|S|62|Data2|9|N|
I tried exploring the PatternMatchingCompositeLineMapper where I can
map the different header pattern I have to a tokenizer and
corresponding FieldSetMapper but I need to read the body and not the
header here .
Don't have any footer to Crete a end of line policy of my own as well .
Also tried using AggregateItemReader but don't want to club all the
records of a header before I process them .
Each rows corresponding a header should be processed parallel .
#Bean
public LineMapper myLineMapper() {
PatternMatchingCompositeLineMapper< Domain > mapper = new PatternMatchingCompositeLineMapper<>();
final Map<String, LineTokenizer> tokenizers = new HashMap<String, LineTokenizer>();
tokenizers.put("* HDR01*", new DelimitedLineTokenizer());
tokenizers.put("*HDR02*", new DelimitedLineTokenizer());
tokenizers.put("*", new DelimitedLineTokenizer("|"));
mapper.setTokenizers(tokenizers);
Map<String, FieldSetMapper<VMSFeedStyleInfo>> mappers = new HashMap<String, FieldSetMapper<VMSFeedStyleInfo>>();
try {
mappers.put("* HDR01*", customMapper());
mappers.put("*HDR02*", customMapper());
mappers.put("*", customMapper() );
} catch (Exception e) {
e.printStackTrace();
}
mapper.setFieldSetMappers(mappers);
return mapper;
}
Can somebody help me provide some inputs as to how should I achieve this .

NiFi SpringContextProcessor interrupts the flow

I added a SpringContextProcessor to my NiFi flow, it executes as expected and updates the FlowFile content and attributes. But in the data provenance section of NiFi instead of seeing SEND/RECEIVE, I am seeing
03/27/2017 11:47:57.164 MDT RECEIVE 42fa1c3f-edde-4cb7-8e73-ce752f7e3d66
03/27/2017 11:47:57.163 MDT DROP 667094a7-8eef-4657-981a-dc9fdc6c4056
03/27/2017 11:47:57.163 MDT SEND 667094a7-8eef-4657-981a-dc9fdc6c4056
Looks like the original message is being dropped and replaced by a new message. I haven't seen this behavior in other components, i.e. they all seem to preserve the original Flow File UUID. Simplified version of the Spring processor code:
#ServiceActivator(inputChannel = "fromNiFi", outputChannel = "toNiFi")
public Message<byte[]> process1(Message<byte[]> inMessage) {
String inMessagePayload = new String(inMessage.getPayload());
String userId = getUserIdFromDb(inMessagePayload);
String outMessagePayload = inMessagePayload + userId;
return MessageBuilder.withPayload(outMessagePayload.getBytes())
.copyHeaders(inMessage.getHeaders())
.setHeader("userId", userId)
.build();
}
Is there a way to preserve the original Flow File UUID in the outgoing message?
This is probably an oversight on our end, so yes please do raise a JIRA.
However as a workaround you can try to extract FlowFile attributes from the incoming Message headers and then propagate them back to the outgoing message.
public Message<byte[]> process1(Message<byte[]> inMessage) {
String myHeader = inMessage.getHeader("someHeader");
. . .
return MessageBuilder.withPayload(outMessagePayload.getBytes())
.copyHeaders(inMessage.getHeaders())
.setHeader("userId", userId)
.setHeader("someHeader", myHeader)
.build();
}

NFC External record is returning in wrong format?

I've successfully written an external record to an NFC tag. When I use a 3rd party tag reader to evaluate the external record that was written, I see the appropriate value, which is a single, positive integer.
However, when I run my code (below) to see what the value of the payload (external record) is on the tag (using a Toast) in order to incorporate that value into an "if" statement, I get different values. So far, I've seen the following:
B#41fb4278 or B#41fb1190.
At this point, the value of the external record is just "2". How can I just return/write simply 2?
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if(intent.hasExtra(NfcAdapter.EXTRA_TAG))
{
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
byte[] payload = "2".getBytes(); ///this is where the ID (payload) for the tag is assigned.
NdefRecord[] ndefRecords = new NdefRecord[2];
ndefRecords[0] = NdefRecord.createExternal("com.example.bmt_admin", "externaltype", payload);
ndefRecords[1] = NdefRecord.createApplicationRecord("com.example.bmt_01");
NdefMessage ndefMessage = new NdefMessage(ndefRecords);
writeNdefMessage(tag, ndefMessage);
Toast.makeText(this, "NFC Scan: " + payload, Toast.LENGTH_SHORT).show();
}
}
Thanks for any help!!
payload is defined as byte[]. When you use payload in your toast() statment, you use it a pointer to that array. Therefore what you see is the address of the array. When you want to get a string representation of a byte[], you can use for example:
String s = new String(payload);

Resources