Pierre Villard created NIFI-8050:
------------------------------------
Summary: Custom Groovy writer breaks during upgrade
Key: NIFI-8050
URL: https://issues.apache.org/jira/browse/NIFI-8050
Project: Apache NiFi
Issue Type: Bug
Components: Core Framework
Affects Versions: 1.12.1, 1.11.4
Reporter: Pierre Villard
A couple of issues when upgrading NiFi and using a custom scripted writer with
Groovy.
The scripted writer was something like:
{code:java}
import ...
class GroovyRecordSetWriter implements RecordSetWriter {
...
@Override
WriteResult write(Record r) throws IOException {
...
}
@Override
String getMimeType() { ... }
@Override
WriteResult write(final RecordSet rs) throws IOException {
...
}
public void beginRecordSet() throws IOException { ... }
@Override
public WriteResult finishRecordSet() throws IOException { ... }
@Override
public void close() throws IOException {}
@Override
public void flush() throws IOException {}
}
class GroovyRecordSetWriterFactory extends AbstractControllerService implements
RecordSetWriterFactory {
@Override
RecordSchema getSchema(Map<String, String> variables, RecordSchema
readSchema) throws SchemaNotFoundException, IOException {
null
}
@Override
RecordSetWriter createWriter(ComponentLog logger, RecordSchema schema,
OutputStream out) throws SchemaNotFoundException, IOException {
new GroovyRecordSetWriter(out)
}
}
writer = new GroovyRecordSetWriterFactory()
{code}
With NIFI-6318 we changed a method in the interface RecordSetWriterFactory.
When using the above code in NiFi 1.9.2, it works fine but after an upgrade on
1.11.4, this breaks. The Controller Service, when enabled, is throwing the
below message:
{quote}Can't have an abstract method in a non-abstract class. The class
'GroovyRecordSetWriterFactory' must be declared abstract or the method
'org.apache.nifi.serialization.RecordSetWriter
createWriter(org.apache.nifi.logging.ComponentLog,
org.apache.nifi.serialization.record.RecordSchema, java.io.OutputStream,
java.util.Map)' must be implemented.
{quote}
However the controller service is successfully enabled and the processors
referencing it can be started. When using the ConvertRecord processor with the
problematic controller service, it will throw the below NPE:
{code:java}
2020-11-26 15:46:13,876 ERROR [Timer-Driven Process Thread-25]
o.a.n.processors.standard.ConvertRecord
ConvertRecord[id=8b5456ae-71dc-3bd3-d0c0-df50d196fc00] Failed to process
StandardFlowFileRecord[uuid=adebfcf6-b449-4d01-90a7-0463930aade0,claim=StandardContentClaim
[resourceClaim=StandardResourceClaim[id=1606401933295-1, container=default,
section=1], offset=80,
length=296],offset=0,name=adebfcf6-b449-4d01-90a7-0463930aade0,size=296]; will
route to failure: java.lang.NullPointerException
java.lang.NullPointerException: null at
org.apache.nifi.processors.standard.AbstractRecordProcessor$1.process(AbstractRecordProcessor.java:151)
at
org.apache.nifi.controller.repository.StandardProcessSession.write(StandardProcessSession.java:2986)
at
org.apache.nifi.processors.standard.AbstractRecordProcessor.onTrigger(AbstractRecordProcessor.java:122)
at
org.apache.nifi.processor.AbstractProcessor.onTrigger(AbstractProcessor.java:27)
at
org.apache.nifi.controller.StandardProcessorNode.onTrigger(StandardProcessorNode.java:1173)
at
org.apache.nifi.controller.tasks.ConnectableTask.invoke(ConnectableTask.java:214)
at
org.apache.nifi.controller.scheduling.TimerDrivenSchedulingAgent$1.run(TimerDrivenSchedulingAgent.java:117)
at org.apache.nifi.engine.FlowEngine$2.run(FlowEngine.java:110) at
java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at
java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) at
java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
at
java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
{code}
The fix is quite simple, it's just required to add the proper implementation
method in the Groovy script. Something like:
{code:java}
class GroovyRecordSetWriterFactory extends AbstractControllerService implements
RecordSetWriterFactory {
@Override
RecordSchema getSchema(Map<String, String> variables, RecordSchema
readSchema) throws SchemaNotFoundException, IOException {
null
}
@Override
RecordSetWriter createWriter(ComponentLog logger, RecordSchema schema,
OutputStream out) throws SchemaNotFoundException, IOException {
new GroovyRecordSetWriter(out)
}
@Override
RecordSetWriter createWriter(ComponentLog logger, RecordSchema schema,
OutputStream out, Map<String, String> variables) throws
SchemaNotFoundException, IOException {
return createWriter(logger, schema, out)
}
}
{code}
However, there are two things to improve:
* if possible the controller service should not be successfully enabled -
throwing a bulletin is nice but not enough in some environments where flow
deployment is completely automated without the access to the UI
* the NPE should be handled properly to provide a more meaningful message
--
This message was sent by Atlassian Jira
(v8.3.4#803005)