What is the equivalent of destination-type from jms:listener-container in JavaConfig? - spring

What is the equivalent of destination-type from jms:listener-container in JavaConfig?
I have checked in the API these two following classes without results.
DefaultMessageListenerContainer
MessageListenerAdapter
I am trying to create consumers for a topic, many tutorials in the web use destination-type="topic"
According with the 23.6 JMS Namespace Support section, there is the Table 23.2. Attributes of the JMS element table. Where for the destination-type attribute says:
The JMS destination type for this listener: queue, topic or durableTopic. The default is queue.
For the audience: consider the two following links if you are trying to do a migration from jms:listener-container and jms:listener for JavaConfig.
complete jms:listener migration to JavaConfig
How to add multiple JMS MessageListners in a single MessageListenerContainer for Spring Java Config

When in doubt, look at the parser (in this case AbstractListenerContainerParser); that attribute doesn't map to a single property, it maps to pubSubDomain and subscriptionDurable...
String destinationType = ele.getAttribute(DESTINATION_TYPE_ATTRIBUTE);
boolean pubSubDomain = false;
boolean subscriptionDurable = false;
if (DESTINATION_TYPE_DURABLE_TOPIC.equals(destinationType)) {
pubSubDomain = true;
subscriptionDurable = true;
}
else if (DESTINATION_TYPE_TOPIC.equals(destinationType)) {
pubSubDomain = true;
}
else if ("".equals(destinationType) || DESTINATION_TYPE_QUEUE.equals(destinationType)) {
// the default: queue
}
else {
parserContext.getReaderContext().error("Invalid listener container 'destination-type': " +
"only \"queue\", \"topic\" and \"durableTopic\" supported.", ele);
}
configDef.getPropertyValues().add("pubSubDomain", pubSubDomain);
configDef.getPropertyValues().add("subscriptionDurable", subscriptionDurable);

Though this is a bit late, I would suggest to use the following approach for anyone who is still searching for the answer.
I have created a new Class DefaultMessageListenerContainerExtended which extends DefaultMessageListenerContainer and I have added one more method as setDestinationType. This does the trick in a nice and familiar way.
Following is the link to source code, which can be found on git:
https://github.com/HVT7/spring-jms-set-destination-type/blob/master/DefaultMessageListenerContainerExtended.java
Also to add, try to use spring version 4.2.5, as there are minor updates in that version (Had to dig a lot due to version issues as I was using 4.1.5 and Listener Containers did not had function to set "ReplyPubSubDomain" property).

Related

How to combine sink.asFlux() with Server-Sent Events (SSE) using Spring WebFlux?

I am using Spring Boot 2.7.8 with WebFlux.
I have a sink in my class like this:
private final Sinks.Many<TaskEvent> sink = Sinks.many()
.multicast()
.onBackpressureBuffer();
This can be used to subscribe on like this:
public Flux<List<TaskEvent>> subscribeToTaskUpdates() {
return sink.asFlux()
.buffer(Duration.ofSeconds(1))
.share();
}
The #Controller uses this like this to push the updates as a Server-Sent Event (SSE) to the browser:
#GetMapping("/transferdatestatuses/updates")
public Flux<ServerSentEvent<TransferDateStatusesUpdateEvent>> subscribeToTransferDataStatusUpdates() {
return monitoringSseBroker.subscribeToTaskUpdates()
.map(taskEventList -> ServerSentEvent.<TransferDateStatusesUpdateEvent>builder()
.data(TransferDateStatusesUpdateEvent.of(taskEventList))
.build())
This works fine at first, but if I navigate away in my (Thymeleaf) web application to a page that has no connection with the SSE url and then go back, then the browser cannot connect anymore.
After some investigation, I found out that the problem is that the removal of the subscriber closes the flux and a new subscriber cannot connect anymore.
I have found 3 ways to fix it, but I don't understand the internals enough to decide which one is the best solution and if there any things I need to consider to decide what to use.
Solution 1
Disable the autoCancel on the sink by using the method overload of onBackpressureBuffer that allows to set this parameter:
private final Sinks.Many<TaskEvent> sink = Sinks.many()
.multicast()
.onBackpressureBuffer(Queues.SMALL_BUFFER_SIZE, false);
Solution 2
Use replay(0).autoConnect() instead of share():
public Flux<List<TaskEvent>> subscribeToTaskUpdates() {
return sink.asFlux()
.buffer(Duration.ofSeconds(1))
.replay(0).autoConnect();
}
Solution 3
Use publish().autoConnect() instead of share():
public Flux<List<TaskEvent>> subscribeToTaskUpdates() {
return sink.asFlux()
.buffer(Duration.ofSeconds(1))
.publish().autoConnect();
}
Which of the solutions are advisable to make sure a browser can disconnect and connect again later without problems?
I'm not quite sure if it is the root of your problem, but I didn't have that issue by using a keepAlive Flux.
val keepAlive = Flux.interval(Duration.ofSeconds(10)).map {
ServerSentEvent.builder<Image>()
.event(":keepalive")
.build()
}
return Flux.merge(
keepAlive,
imageUpdateFlux
)
Here is the whole file: Github

Customizing Apache Camels ExchangeFormatter using Spring-Boot

by default i assume that spring-boot/camel is using org.apache.camel.support.processor.DefaultExchangeFormatter.
I wonder how I can set the flag 'showHeaders' inside a spring-boot app.
Because I hope to see the headers in the "org.apache.camel.tracing" log as well.
Wish all a wonderful day
DefaultTracer is used in Camel to trace routes by default.
It is created with showHeaders(false) formatter option set.
Therefore you could implement another Tracer (consider extending from DefaultTracer) to enable putting headers into traced messages.
i need this mostly in my tests. so i have built this into my basic test class
#BeforeEach
public void before() {
if( camelContext.getTracer().getExchangeFormatter() instanceof DefaultExchangeFormatter ) {
DefaultExchangeFormatter def = (DefaultExchangeFormatter)camelContext.getTracer().getExchangeFormatter();
def.setShowHeaders(true);
}
}

Spring Cloud Function - Separate routing-expression for different Consumer

I have a service, which receives different structured messages from different message queues. Having #StreamListener conditions we can choose at every message type how that message should be handled. As an example:
We receive two different types of messages, which have different header fields and values e.g.
Incoming from "order" queue:
Order1: { Header: {catalog:groceries} }
Order2: { Header: {catalog:tools} }
Incoming from "shipment" queue:
Shipment1: { Header: {region:Europe} }
Shipment2: { Header: {region:America} }
There is a binding for each queue, and with according #StreamListener I can process the messages by catalog and region differently
e.g.
#StreamListener(target = OrderSink.ORDER_CHANNEL, condition = "headers['catalog'] == 'groceries'")
public void onGroceriesOrder(GroceryOder order){
...
}
So the question is, how to achieve this with the new Spring Cloud Function approach?
At the documentation https://cloud.spring.io/spring-cloud-static/spring-cloud-stream/3.0.2.RELEASE/reference/html/spring-cloud-stream.html#_event_routing it is mentioned:
Also, for SpEL, the root object of the evaluation context is Message so you can do evaluation on individual headers (or message) as well …​.routing-expression=headers['type']
Is it possible to add the routing-expression to the binding like (in application.yml)
onGroceriesOrder-in-0:
destination: order
routing-expression: "headers['catalog']==groceries"
?
EDIT after first answer
If the above expression at this location is not possible, what the first answer implies, than my question goes as follows:
As far as I understand, an expression like routing-expression: headers['catalog'] must be set globally, because the result maps to certain (consumer) functions.
How can I control that the 2 different messages on each queue will be forwarted to their own consumer function, e.g.
Order1 --> MyOrderService.onGroceriesOrder()
Order2 --> MyOrderService.onToolsOrder()
Shipment1 --> MyShipmentService.onEuropeShipment()
Shipment2 --> MyShipmentService.onAmericaShipment()
That was easy with #StreamListener, because each method gets their own #StreamListener annotation with different conditions. How can this be achieved with the new routing-expression setting?
?
Aside from the fact that the above is not a valid expression, but I think you meant headers['catalog']==groceries. If so, what would you expect to happen from evaluating it as the only two option could be true/false. Anyway, these are rhetorical but helps to understand the problem and how to fix it.
The expression must result in a value of a function to route TO. So. . .
routing-expression: headers['catalog'] - assumes that the actual value of catalog header is the name of the function to invoke
routing-expression: headers['catalog']==groceries ? 'processGroceries' : 'processOther' - maps value 'groceries' to 'processGroceries' function.
For a specific routing, you can use MessageRoutingCallback strategy:
MessageRoutingCallback
The MessageRoutingCallback is a strategy to assist with determining
the name of the route-to function definition.
public interface MessageRoutingCallback {
FunctionRoutingResult routingResult(Message<?> message);
. . .
}
All you need to do is implement and register it as a bean to be picked
up by the RoutingFunction. For example:
#Bean
public MessageRoutingCallback customRouter() {
return new MessageRoutingCallback() {
#Override
FunctionRoutingResult routingResult(Message<?> message) {
return new FunctionRoutingResult((String) message.getHeaders().get("func_name"));
}
};
}
Spring Cloud Function

How to configure Spring XD JMS source to use DefaultMessageListenerContainer?

To make the JMS topic subscription durable, it seems I need to make sure
DefaultMessageListenerContainer (instead of the default
SimpleMessageListenerContainer) is used
stream definition contains "durableSubscription=true acknowledge=transacted subscriptionName=xxxx pubSub=true"
I managed to enable 'dmlc' by specifying spring.profiles.active in xd-singlenode.bat but is there a better way such as using properties or yml?
xd-singlenode.bat
set SPRING_XD_OPTS=-Dspring.profiles.active=singlenode,dmlc
-Dspring.application.name=singlenode -Dlogging.config=%XD_CONFIG_LOCATION%/xd-singlenode-logback.groovy -Dxd.home=%XD_HOME%
According to the JmsSourceModuleOptionsMetadata source code we have:
public String[] profilesToActivate() {
if ("transacted".equals(this.acknowledge)) {
return new String[] { "dmlc" };
}
else {
return new String[] { "smlc" };
}
}
So, looks like your acknowledge=transacted is enough to go ahead with the
container-class="org.springframework.jms.listener.DefaultMessageListenerContainer"
in the JMS Source.

Grails 'withTransaction' with alternate dataSource

Trying to figure out how to open a transaction with an alternate dataSource in grails. I have the following dataSources defined...
dataSource {
dbCreate = "update"
url = "jdbc:h2:mem:testDb;MVCC=TRUE"
}
dataSource_ALT {
dbCreate = "update"
url = "jdbc:h2:mem:altDb;MVCC=TRUE"
}
I'm able to do this with the default dataSource...
Foo.withTransaction { status ->
//...
}
But when I try and do it with the ALT dataSource, I get an exception - 'No transactionManager bean configured'...
Foo.ALT.withTransaction { status ->
//...
}
Is there a way to start a transaction using a different dataSource? I've done some digging around and haven't been able to find much.
Thanks!
#Raphael Your suggestion does work. I was able to go to the next step and get withTransaction working by assigning a transaction manager. They seem to be created, just not attached. They exist as Spring beans named something like transactionManager_ALT (in this example). Not sure what actually creates them, and why they aren't attached to the GormStaticApi.
i.e. here you'd do:
Foo["ALT"].transactionManager = transactionManager_ALT
Probably will try to pick one domain class and assign all the different transaction managers, see if that works, then use that one domain class everywhere to start transactions.

Resources