1
1
/*
2
- * Copyright 2002-2023 the original author or authors.
2
+ * Copyright 2002-2024 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
17
17
package org .springframework .integration .jms ;
18
18
19
19
import java .util .Map ;
20
+ import java .util .concurrent .atomic .AtomicInteger ;
20
21
21
22
import io .micrometer .observation .ObservationRegistry ;
22
23
import jakarta .jms .DeliveryMode ;
30
31
import org .springframework .beans .factory .BeanFactory ;
31
32
import org .springframework .beans .factory .BeanFactoryAware ;
32
33
import org .springframework .beans .factory .InitializingBean ;
34
+ import org .springframework .core .AttributeAccessor ;
33
35
import org .springframework .core .log .LogAccessor ;
34
36
import org .springframework .expression .Expression ;
35
37
import org .springframework .expression .spel .support .StandardEvaluationContext ;
38
+ import org .springframework .integration .IntegrationMessageHeaderAccessor ;
39
+ import org .springframework .integration .StaticMessageHeaderAccessor ;
36
40
import org .springframework .integration .core .MessagingTemplate ;
37
41
import org .springframework .integration .expression .ExpressionUtils ;
38
42
import org .springframework .integration .gateway .MessagingGatewaySupport ;
43
+ import org .springframework .integration .jms .support .JmsMessageHeaderErrorMessageStrategy ;
39
44
import org .springframework .integration .support .DefaultMessageBuilderFactory ;
45
+ import org .springframework .integration .support .ErrorMessageUtils ;
40
46
import org .springframework .integration .support .MessageBuilderFactory ;
41
47
import org .springframework .integration .support .management .TrackableComponent ;
42
48
import org .springframework .integration .support .management .metrics .MetricsCaptor ;
54
60
import org .springframework .messaging .MessageChannel ;
55
61
import org .springframework .messaging .MessagingException ;
56
62
import org .springframework .messaging .support .ErrorMessage ;
63
+ import org .springframework .retry .RecoveryCallback ;
64
+ import org .springframework .retry .RetryOperations ;
65
+ import org .springframework .retry .support .RetrySynchronizationManager ;
66
+ import org .springframework .retry .support .RetryTemplate ;
57
67
import org .springframework .util .Assert ;
58
68
59
69
/**
@@ -347,6 +357,30 @@ public void setReceiverObservationConvention(
347
357
this .gatewayDelegate .setReceiverObservationConvention (observationConvention );
348
358
}
349
359
360
+ /**
361
+ * Set a {@link RetryTemplate} to use for retrying a message delivery within the
362
+ * adapter. Unlike adding retry at the container level, this can be used with an
363
+ * {@code ErrorMessageSendingRecoverer} {@link RecoveryCallback} to publish to the
364
+ * error channel after retries are exhausted. You generally should not configure an
365
+ * error channel when using retry here, use a {@link RecoveryCallback} instead.
366
+ * @param retryTemplate the template.
367
+ * @since 6.3
368
+ * @see #setRecoveryCallback(RecoveryCallback)
369
+ */
370
+ public void setRetryTemplate (RetryTemplate retryTemplate ) {
371
+ this .gatewayDelegate .retryTemplate = retryTemplate ;
372
+ }
373
+
374
+ /**
375
+ * Set a {@link RecoveryCallback} when using retry within the adapter.
376
+ * @param recoveryCallback the callback.
377
+ * @since 6.3
378
+ * @see #setRetryTemplate(RetryTemplate)
379
+ */
380
+ public void setRecoveryCallback (RecoveryCallback <Message <?>> recoveryCallback ) {
381
+ this .gatewayDelegate .recoveryCallback = recoveryCallback ;
382
+ }
383
+
350
384
@ Override
351
385
public void setBeanFactory (BeanFactory beanFactory ) throws BeansException {
352
386
this .beanFactory = beanFactory ;
@@ -367,6 +401,9 @@ public void onMessage(jakarta.jms.Message jmsMessage, Session session) throws JM
367
401
}
368
402
369
403
Map <String , Object > headers = this .headerMapper .toHeaders (jmsMessage );
404
+ if (this .gatewayDelegate .retryTemplate != null ) {
405
+ headers .put (IntegrationMessageHeaderAccessor .DELIVERY_ATTEMPT , new AtomicInteger ());
406
+ }
370
407
requestMessage =
371
408
(result instanceof Message <?>) ?
372
409
this .messageBuilderFactory .fromMessage ((Message <?>) result ).copyHeaders (headers ).build () :
@@ -385,10 +422,10 @@ public void onMessage(jakarta.jms.Message jmsMessage, Session session) throws JM
385
422
}
386
423
387
424
if (!this .expectReply ) {
388
- this .gatewayDelegate .send (requestMessage );
425
+ this .gatewayDelegate .send (jmsMessage , requestMessage );
389
426
}
390
427
else {
391
- Message <?> replyMessage = this .gatewayDelegate .sendAndReceiveMessage (requestMessage );
428
+ Message <?> replyMessage = this .gatewayDelegate .sendAndReceiveMessage (jmsMessage , requestMessage );
392
429
if (replyMessage != null ) {
393
430
Destination destination = getReplyDestination (jmsMessage , session );
394
431
this .logger .debug (() -> "Reply destination: " + destination );
@@ -424,6 +461,12 @@ public void afterPropertiesSet() {
424
461
this .gatewayDelegate .setBeanFactory (this .beanFactory );
425
462
}
426
463
this .gatewayDelegate .afterPropertiesSet ();
464
+ if (this .gatewayDelegate .retryTemplate != null ) {
465
+ Assert .state (this .gatewayDelegate .getErrorChannel () == null ,
466
+ "Cannot have an 'errorChannel' property when a 'RetryTemplate' is "
467
+ + "provided; use an 'ErrorMessageSendingRecoverer' in the 'recoveryCallback' property to "
468
+ + "send an error message when retries are exhausted" );
469
+ }
427
470
this .messageBuilderFactory = IntegrationUtils .getMessageBuilderFactory (this .beanFactory );
428
471
this .evaluationContext = ExpressionUtils .createStandardEvaluationContext (this .beanFactory );
429
472
}
@@ -551,21 +594,65 @@ private record DestinationNameHolder(String name, boolean isTopic) {
551
594
552
595
private class GatewayDelegate extends MessagingGatewaySupport {
553
596
597
+ private static final ThreadLocal <AttributeAccessor > ATTRIBUTES_HOLDER = new ThreadLocal <>();
598
+
599
+ @ Nullable
600
+ private RetryOperations retryTemplate ;
601
+
602
+ @ Nullable
603
+ private RecoveryCallback <Message <?>> recoveryCallback ;
604
+
554
605
GatewayDelegate () {
606
+ setErrorMessageStrategy (new JmsMessageHeaderErrorMessageStrategy ());
555
607
}
556
608
557
- @ Override
558
- protected void send (Object request ) { // NOSONAR - not useless, increases visibility
559
- super .send (request );
609
+ private void send (jakarta .jms .Message jmsMessage , Message <?> requestMessage ) {
610
+ try {
611
+ if (this .retryTemplate == null ) {
612
+ setAttributesIfNecessary (jmsMessage , requestMessage );
613
+ send (requestMessage );
614
+ }
615
+ else {
616
+ this .retryTemplate .execute (
617
+ context -> {
618
+ StaticMessageHeaderAccessor .getDeliveryAttempt (requestMessage ).incrementAndGet ();
619
+ setAttributesIfNecessary (jmsMessage , requestMessage );
620
+ send (requestMessage );
621
+ return null ;
622
+ }, this .recoveryCallback );
623
+ }
624
+ }
625
+ finally {
626
+ if (this .retryTemplate == null ) {
627
+ ATTRIBUTES_HOLDER .remove ();
628
+ }
629
+ }
560
630
}
561
631
562
- @ Override
563
- protected Message <?> sendAndReceiveMessage (Object request ) { // NOSONAR - not useless, increases visibility
564
- return super .sendAndReceiveMessage (request );
632
+ private Message <?> sendAndReceiveMessage (jakarta .jms .Message jmsMessage , Message <?> requestMessage ) {
633
+ try {
634
+ if (this .retryTemplate == null ) {
635
+ setAttributesIfNecessary (jmsMessage , requestMessage );
636
+ return sendAndReceiveMessage (requestMessage );
637
+ }
638
+ else {
639
+ return this .retryTemplate .execute (
640
+ context -> {
641
+ StaticMessageHeaderAccessor .getDeliveryAttempt (requestMessage ).incrementAndGet ();
642
+ setAttributesIfNecessary (jmsMessage , requestMessage );
643
+ return sendAndReceiveMessage (requestMessage );
644
+ }, this .recoveryCallback );
645
+ }
646
+ }
647
+ finally {
648
+ if (this .retryTemplate == null ) {
649
+ ATTRIBUTES_HOLDER .remove ();
650
+ }
651
+ }
565
652
}
566
653
567
654
protected ErrorMessage buildErrorMessage (Throwable throwable ) {
568
- return super . buildErrorMessage (null , throwable );
655
+ return buildErrorMessage (null , throwable );
569
656
}
570
657
571
658
protected MessagingTemplate getMessagingTemplate () {
@@ -582,6 +669,29 @@ public String getComponentType() {
582
669
}
583
670
}
584
671
672
+ @ Override
673
+ protected AttributeAccessor getErrorMessageAttributes (@ Nullable Message <?> message ) {
674
+ AttributeAccessor attributes = ATTRIBUTES_HOLDER .get ();
675
+ return (attributes != null ) ? attributes : super .getErrorMessageAttributes (message );
676
+ }
677
+
678
+ private void setAttributesIfNecessary (Object jmsMessage , Message <?> message ) {
679
+ boolean needHolder = getErrorChannel () != null && this .retryTemplate == null ;
680
+ boolean needAttributes = needHolder || this .retryTemplate != null ;
681
+ if (needHolder ) {
682
+ ATTRIBUTES_HOLDER .set (ErrorMessageUtils .getAttributeAccessor (null , null ));
683
+ }
684
+ if (needAttributes ) {
685
+ AttributeAccessor attributes =
686
+ this .retryTemplate != null
687
+ ? RetrySynchronizationManager .getContext ()
688
+ : ATTRIBUTES_HOLDER .get ();
689
+ if (attributes != null ) {
690
+ attributes .setAttribute (ErrorMessageUtils .INPUT_MESSAGE_CONTEXT_KEY , message );
691
+ attributes .setAttribute (JmsMessageHeaderErrorMessageStrategy .JMS_RAW_MESSAGE , jmsMessage );
692
+ }
693
+ }
694
+ }
585
695
}
586
696
587
697
}
0 commit comments