How to configure Spring XD JMS source to use DefaultMessageListenerContainer? - spring-xd

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.

Related

Evaluate Jms Destination Dynamically from header using JmsSendingMessageHandler

I am trying to send message using JmsSendingMessageHandler but could not find a method which would fetch destination value from header something like messageHandler.setDestinationExpressionString("headers['destination_name']") ?
#MessagingGateway
public interface OutboundMessageGateway {
#Gateway(requestChannel = "outMessageChannel")
void sendMessage(Message<?> message);
}
#Bean
#ServiceActivator(inputChannel = "outMessageChannel" )
private MessageHandler jmsOutboundHandler() {
JmsSendingMessageHandler messageHandler = new JmsSendingMessageHandler(new JmsTemplate(connectionFactory());
messageHandler.setDestinationExpressionString("headers['destination_name']"); // not available
return messageHandler;
}
any solution ? I want to fetch destination dynamically from header I am passing with Message<?>
There is no API like that JmsSendingMessageHandler.setDestinationExpressionString(). Not sure why your IDE doesn't suggest you that you are on a wrong way, but there is other choice. My one shows this:
If you are really sure that you set that destination_name upstream, then you indeed can use that setDestinationExpression(Expression) API and like this:
handler.setDestinationExpression(new SpelExpressionParser().parseExpression("headers['destination_name']"));
Another, more Java-way is like this:
handler.setDestinationExpression(new FunctionExpression<Message<?>>(m -> m.getHeaders().get("destination_name")));
I think we can add that setDestinationExpressionString() anyway, if you insist and can contribute such a fix back to the framework: https://github.com/spring-projects/spring-integration/blob/main/CONTRIBUTING.adoc

Masstransit (non-DI) configuration to autogenerate an Azure Service Bus Topic with Duplicate Detection enabled

I've discovered no Masstransit configuration that allows a service bus Topic to be created with Duplicate Detection enabled.
You can do it with Queues simply enough. But for Topics it seems a bit of a mystery.
Does anybody have a working sample?
Perhaps it is not possible.
I've been trying to use the IServiceBusBusFactoryConfigurator provided by the Bus.Factory.CreateUsingAzureServiceBus method.
I'd thought that some use of IServiceBusBusFactoryConfigurator.Publish method and IServiceBusBusFactoryConfigurator.SubscriptionEndpoint method would accomplish the task, but after a myriad of trials I've come up with no solution.
To configure your message type topic with duplicate detection, you must configure the publish topology in both the producer and the consumer (it only needs to be configured once per bus instance, but if your producer is a separate bus instance, it would also need the configuration). The topic must also not already exist as it would not be updated once created in Azure.
To configure the publish topology:
namespace DupeDetection
{
public interface DupeCommand
{
string Value { get; }
}
}
var busControl = Bus.Factory.CreateUsingAzureServiceBus(cfg =>
{
cfg.Publish<DupeCommand>(x => x.EnableDuplicateDetection(TimeSpan.FromMinutes(10)));
cfg.ReceiveEndpoint("dupe", e =>
{
e.Consumer<DupeConsumer>();
});
}
The consumer is normal (no special settings required).
class DupeConsumer :
IConsumer<DupeCommand>
{
public Task Consume(ConsumeContext<DupeCommand> context)
{
return Task.CompletedTask;
}
}
I've added a unit test to verify this behavior, and can confirm that when two messages with the same MessageId are published back-to-back, only a single message is delivered to the consumer.
Test log output:
10:53:15.641-D Create send transport: sb://masstransit-build.servicebus.windows.net/MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection/DupeCommand
10:53:15.784-D Topic: MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection/DupeCommand (dupe detect)
10:53:16.375-D SEND sb://masstransit-build.servicebus.windows.net/MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection/DupeCommand dc3a0000-ebb8-e450-949c-08d8e8939c7f MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection.DupeCommand
10:53:16.435-D SEND sb://masstransit-build.servicebus.windows.net/MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection/DupeCommand dc3a0000-ebb8-e450-949c-08d8e8939c7f MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection.DupeCommand
10:53:16.469-D RECEIVE sb://masstransit-build.servicebus.windows.net/MassTransit.Azure.ServiceBus.Core.Tests/input_queue dc3a0000-ebb8-e450-949c-08d8e8939c7f MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection.DupeCommand MassTransit.IConsumer<MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection.DupeCommand>(00:00:00.0017972)
You can see the (dupe detect) attribute shown on the topic declaration.
Here is the solution I finally found. It does not rely on trying any of the ReceiveEndpoint or SubscriptionEndpoint configuration methods which never seemed to give me what I wanted.
IBusControl bus = Bus.Factory.CreateUsingAzureServiceBus(cfg =>
{
cfg.Publish<MembershipNotifications.MembershipSignupMessage>(configure =>
{
configure.EnableDuplicateDetection(_DuplicateDetectionWindow);
configure.AutoDeleteOnIdle = _AutoDeleteOnIdle;
configure.DefaultMessageTimeToLive = _MessageTimeToLive;
});
}
await bus.Publish(new MessageTest());

How to configure Filter with specific message type?

I want to consume messages only with specific type and properties set. A sort of message content filter before any consumer instance created.
I'm trying to create a filter for specific ConsumeContext:
public class OrderFilter : IFilter<ConsumeContext<CreateOrderMessage>>
{
public Task Send(ConsumeContext<CreateOrderMessage> context, IPipe<ConsumeContext<CreateOrderMessage>> next)
{
if (context.Message.IsTrustedUser)
{
return next.Send(context); // continue processing
}
return Task.CompletedTask; // stop message processing
}
public void Probe(ProbeContext context) { }
}
How can I register such a filter?
I've tried to register it in the endpoint but with no luck. I have
cfg.ReceiveEndpoint("OrderQueue", ep =>
{
ep.UseFilter(new OrderFilter());
ep.Consumer<CreateOrderConsumer>();
});
I have the following error: Cannot convert instance argument type '{MassTransit.IReceiveEndpointConfigurator,MassTransit.RabbitMqTransport.IRabbitMqReceiveEndpointConfigurator}' to 'GreenPipes.IPipeConfigurator<MassTransit.ConsumeContext<Core.CreateOrderMessage>>'
So, there used to be an extension method for this purpose, but I can't find it. You can add the filter prior to the consumer being created by creating a filter specification and adding it as shown below.
var filter = new OrderFilter();
var specification = new FilterPipeSpecification<ConsumeContext< CreateOrderMessage >>(filter);
ep.AddPipeSpecification(specification);
If you want to execute the filter after the consumer has been created (for instance, if you're using container scope to share information), you can use a scope consume filter (which is described in several answers, as well as the documentation) or you can add your filter during consumer configuration.
ep.Consumer<CreateOrderConsumer>(cc =>
{
cc.Message<CreateOrderMessage>(mc => mc.UseFilter(new OrderFilter()));
}

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

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).

use camel case serialization only for specific actions

I've used WebAPI for a while, and generally set it to use camel case json serialization, which is now rather common and well documented everywhere.
Recently however, working on a much larger project, I came across a more specific requirement: we need to use camel case json serialization, but because of backward compatibility issues with our client scripts, I only want it to happen for specific actions, to avoid breaking other parts of the (extremely large) website.
I figure one option is to have a custom content type, but that then requires client code to specify it.
Is there any other option?
Thanks!
Try this:
public class CamelCasingFilterAttribute : ActionFilterAttribute
{
private JsonMediaTypeFormatter _camelCasingFormatter = new JsonMediaTypeFormatter();
public CamelCasingFilterAttribute()
{
_camelCasingFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
ObjectContent content = actionExecutedContext.Response.Content as ObjectContent;
if (content != null)
{
if (content.Formatter is JsonMediaTypeFormatter)
{
actionExecutedContext.Response.Content = new ObjectContent(content.ObjectType, content.Value, _camelCasingFormatter);
}
}
}
}
Apply this [CamelCasingFilter] attribute to any action you want to camel-case. It will take any JSON response you were about to send back and convert it to use camel casing for the property names instead.

Resources