3333import org .apache .nifi .expression .ExpressionLanguageScope ;
3434import org .apache .nifi .flowfile .FlowFile ;
3535import org .apache .nifi .migration .PropertyConfiguration ;
36+ import org .apache .nifi .processor .DataUnit ;
3637import org .apache .nifi .processor .ProcessContext ;
3738import org .apache .nifi .processor .ProcessSession ;
3839import org .apache .nifi .processor .Relationship ;
7576 @ ReadsAttribute (attribute = AbstractAMQPProcessor .AMQP_CLUSTER_ID_ATTRIBUTE , description = "The ID of the AMQP Cluster" ),
7677})
7778public class PublishAMQP extends AbstractAMQPProcessor <AMQPPublisher > {
79+ private static final long MAXIMUM_INPUT_FLOWFILE_SIZE_LIMIT = 128 * 1024 * 1024L ;
7880
7981 public static final PropertyDescriptor EXCHANGE = new PropertyDescriptor .Builder ()
8082 .name ("Exchange Name" )
@@ -108,6 +110,16 @@ public class PublishAMQP extends AbstractAMQPProcessor<AMQPPublisher> {
108110 .allowableValues (DeliveryGuarantee .class )
109111 .defaultValue (DeliveryGuarantee .AT_MOST_ONCE )
110112 .build ();
113+ public static final PropertyDescriptor MAXIMUM_INPUT_FLOWFILE_SIZE = new PropertyDescriptor .Builder ()
114+ .name ("Maximum Input FlowFile Size" )
115+ .description ("Maximum size of an input FlowFile that will be read into memory before publishing. PublishAMQP reads FlowFile content into a byte array "
116+ + "before publishing, so FlowFiles larger than this value are routed to failure before content is read. Configure this value according to "
117+ + "broker limits and available JVM memory." )
118+ .required (true )
119+ .defaultValue ("128 MB" )
120+ .expressionLanguageSupported (ExpressionLanguageScope .ENVIRONMENT )
121+ .addValidator (StandardValidators .createDataSizeBoundsValidator (1 , MAXIMUM_INPUT_FLOWFILE_SIZE_LIMIT ))
122+ .build ();
111123 public static final PropertyDescriptor HEADERS_SOURCE = new PropertyDescriptor .Builder ()
112124 .name ("Headers Source" )
113125 .description ("The source of the headers which will be applied to the published message." )
@@ -150,6 +162,7 @@ public class PublishAMQP extends AbstractAMQPProcessor<AMQPPublisher> {
150162 EXCHANGE ,
151163 ROUTING_KEY ,
152164 DELIVERY_GUARANTEE ,
165+ MAXIMUM_INPUT_FLOWFILE_SIZE ,
153166 HEADERS_SOURCE ,
154167 HEADERS_PATTERN ,
155168 HEADER_SEPARATOR
@@ -165,7 +178,7 @@ public class PublishAMQP extends AbstractAMQPProcessor<AMQPPublisher> {
165178 /**
166179 * Will construct AMQP message by extracting its body from the incoming {@link FlowFile}. AMQP Properties will be extracted from the
167180 * {@link FlowFile} and converted to {@link BasicProperties} to be sent along with the message. Upon success the incoming {@link FlowFile} is
168- * transferred to 'success' {@link Relationship} and upon failure FlowFile is penalized and transferred to the 'failure' {@link Relationship}
181+ * transferred to 'success' {@link Relationship} and upon failure FlowFile is transferred to the 'failure' {@link Relationship}
169182 * <br>
170183 * <p>
171184 * NOTE: Attributes extracted from {@link FlowFile} are considered candidates for AMQP properties if their names are prefixed with
@@ -180,6 +193,14 @@ protected void processResource(final Connection connection, final AMQPPublisher
180193 return ;
181194 }
182195
196+ final long maximumInputFlowFileSize = context .getProperty (MAXIMUM_INPUT_FLOWFILE_SIZE ).evaluateAttributeExpressions ().asDataSize (DataUnit .B ).longValue ();
197+ if (flowFile .getSize () > maximumInputFlowFileSize ) {
198+ getLogger ().warn ("FlowFile {} with size {} bytes exceeds configured maximum input FlowFile size of {} bytes; routing to failure" ,
199+ flowFile , flowFile .getSize (), maximumInputFlowFileSize );
200+ session .transfer (flowFile , REL_FAILURE );
201+ return ;
202+ }
203+
183204 final String routingKey = context .getProperty (ROUTING_KEY ).evaluateAttributeExpressions (flowFile ).getValue ();
184205 if (routingKey == null ) {
185206 throw new IllegalArgumentException ("Failed to determine 'routing key' with provided value '"
@@ -201,7 +222,7 @@ protected void processResource(final Connection connection, final AMQPPublisher
201222 session .rollback ();
202223 throw e ;
203224 } catch (AMQPException e ) {
204- session .transfer (session . penalize ( flowFile ) , REL_FAILURE );
225+ session .transfer (flowFile , REL_FAILURE );
205226 throw e ;
206227 }
207228
@@ -235,7 +256,7 @@ public void migrateProperties(final PropertyConfiguration config) {
235256 * Extracts contents of the {@link FlowFile} as byte array.
236257 */
237258 private byte [] extractMessage (final FlowFile flowFile , ProcessSession session ) {
238- final byte [] messageContent = new byte [( int ) flowFile .getSize ()];
259+ final byte [] messageContent = new byte [Math . toIntExact ( flowFile .getSize () )];
239260 session .read (flowFile , in -> StreamUtils .fillBuffer (in , messageContent , true ));
240261 return messageContent ;
241262 }
@@ -332,16 +353,26 @@ private Map<String, Object> validateAMQPHeaderProperty(final String amqpPropValu
332353 for (String strEntry : strEntries ) {
333354 final String [] kv = strEntry .split ("=" , -1 ); // without using limit, trailing delimiter would be ignored
334355 if (kv .length == 2 ) {
335- headers . put ( kv [0 ]. trim () , kv [1 ].trim ());
356+ addHeader ( headers , amqpPropValue , strEntry , kv [0 ], kv [1 ].trim ());
336357 } else if (kv .length == 1 ) {
337- headers . put ( kv [0 ]. trim () , null );
358+ addHeader ( headers , amqpPropValue , strEntry , kv [0 ], null );
338359 } else {
339360 getLogger ().warn ("Malformed key value pair in AMQP header property ({}): {}" , amqpPropValue , strEntry );
340361 }
341362 }
342363 return headers ;
343364 }
344365
366+ private void addHeader (final Map <String , Object > headers , final String amqpPropValue , final String strEntry , final String headerKey , final Object headerValue ) {
367+ final String trimmedHeaderKey = headerKey .trim ();
368+ if (trimmedHeaderKey .isEmpty ()) {
369+ getLogger ().warn ("Skipping AMQP header with empty key in property ({}): {}" , amqpPropValue , strEntry );
370+ return ;
371+ }
372+
373+ headers .put (trimmedHeaderKey , headerValue );
374+ }
375+
345376 protected Pattern getPattern (ProcessContext context , InputHeaderSource selectedHeaderSource ) {
346377 return switch (selectedHeaderSource ) {
347378 case FLOWFILE_ATTRIBUTES -> Pattern .compile (context .getProperty (HEADERS_PATTERN ).evaluateAttributeExpressions ().getValue ());
0 commit comments