Spring & RabbitMQ - register queue at runtime - rabbitmq

How can I create new queue bound to Fanout exchange and run it during runtime? So far I have this:
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-message-ttl", 600000L);
GenericBeanDefinition runtimeQueueBean = new GenericBeanDefinition();
runtimeQueueBean.setBeanClass(Queue.class);
runtimeQueueBean.setLazyInit(false);
runtimeQueueBean.setAbstract(false);
runtimeQueueBean.setAutowireCandidate(true);
ConstructorArgumentValues queueConstrArgs = new ConstructorArgumentValues();
queueConstrArgs.addIndexedArgumentValue(0, queueName);
queueConstrArgs.addIndexedArgumentValue(1, true);
queueConstrArgs.addIndexedArgumentValue(2, false);
queueConstrArgs.addIndexedArgumentValue(3, false);
queueConstrArgs.addIndexedArgumentValue(4, arguments);
runtimeQueueBean.setConstructorArgumentValues(queueConstrArgs);
this.context.registerBeanDefinition("nejm", runtimeQueueBean);
GenericBeanDefinition runtimeFanoutExchange = new GenericBeanDefinition();
runtimeFanoutExchange.setBeanClass(FanoutExchange.class);
runtimeFanoutExchange.setLazyInit(false);
runtimeFanoutExchange.setAbstract(false);
runtimeFanoutExchange.setAutowireCandidate(true);
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
constructorArgumentValues.addIndexedArgumentValue(0, "staticCache");
runtimeFanoutExchange.setConstructorArgumentValues(constructorArgumentValues);
this.context.registerBeanDefinition("staticCache", runtimeFanoutExchange);
GenericBeanDefinition runtimeBinding = new GenericBeanDefinition();
runtimeBinding.setBeanClass(Binding.class);
runtimeBinding.setLazyInit(false);
runtimeBinding.setAbstract(false);
runtimeBinding.setAutowireCandidate(true);
constructorArgumentValues = new ConstructorArgumentValues();
constructorArgumentValues.addIndexedArgumentValue(0, queueName);
constructorArgumentValues.addIndexedArgumentValue(1, Binding.DestinationType.QUEUE);
constructorArgumentValues.addIndexedArgumentValue(2, "staticCache");
constructorArgumentValues.addIndexedArgumentValue(3, "");
runtimeBinding.setConstructorArgumentValues(constructorArgumentValues);
this.context.registerBeanDefinition("bajnding", runtimeBinding);
GenericBeanDefinition runtimeMessageListenerAdapter = new GenericBeanDefinition();
runtimeMessageListenerAdapter.setBeanClass(MessageListenerAdapter.class);
runtimeMessageListenerAdapter.setLazyInit(false);
runtimeMessageListenerAdapter.setAbstract(false);
runtimeMessageListenerAdapter.setAutowireCandidate(true);
constructorArgumentValues = new ConstructorArgumentValues();
constructorArgumentValues.addIndexedArgumentValue(0, this);
constructorArgumentValues.addIndexedArgumentValue(1, new RuntimeBeanReference("jackson2JsonMessageConverter"));
runtimeMessageListenerAdapter.setConstructorArgumentValues(constructorArgumentValues);
this.context.registerBeanDefinition("mla2", runtimeMessageListenerAdapter);
GenericBeanDefinition runtimeContainerExchange = new GenericBeanDefinition();
runtimeContainerExchange.setBeanClass(SimpleMessageListenerContainer.class);
runtimeContainerExchange.setLazyInit(false);
runtimeContainerExchange.setAbstract(false);
runtimeContainerExchange.setAutowireCandidate(true);
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue("connectionFactory", new RuntimeBeanReference("connectionFactory"));
propertyValues.addPropertyValue("queues", new RuntimeBeanReference("nejm"));
propertyValues.addPropertyValue("messageListener", new RuntimeBeanReference("mla2"));
runtimeContainerExchange.setPropertyValues(propertyValues);
this.context.registerBeanDefinition("defqueue", runtimeContainerExchange);
The problem is that queue/exchange is not created at the runtime, and I have to manually start the listener (unless I call this.context.start() - but I don't know if this is correct approach).
My question - is there some way to magically start all generated beans in runtime (something like this.context.refresh() - this exists but doesn't work or similar)?
UPDATE:
This is how I do it currently (this approach works, but don't know if correct one)
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-message-ttl", 600000L);
Queue queue = new Queue(queueName, true, false, false, arguments);
FanoutExchange exchange = new FanoutExchange("staticCache");
Binding binding = new Binding(queueName, Binding.DestinationType.QUEUE, "staticCache", "", null);
rabbitAdmin.declareQueue(queue);
rabbitAdmin.declareExchange(exchange);
rabbitAdmin.declareBinding(binding);
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(this.connectionFactory);
container.setQueues(queue);
container.setMessageListener(new MessageListenerAdapter(this, this.converter));
container.start();

You can't do that way. BeanDefinition and this.context.registerBeanDefinition are for parsing phase of your application context lifecycle.
If you app is already there, the application context won't accepts any BeanDefinition.
Yes, you can declare Queue and its Binding to the exchange manually at runtime. And also you even can create SimpleMessageListenerContainer manually and make it worked.
And what is good for you that you just need to use their classes manually to instantiate. There is just need to supply container environment (e.g. inject this.applicationContext to the listenerContainer object).
For the declaration on the Broker you must use RabbitAdmin bean from your applicationContext.
From other side there is no reason to start a new listenerContainer manually. The existing one can supplied with your new Queue at runtime.

Related

Send MessageProperties [priority=anyInteger] while publishing message in RabbitMQ

we are using rabbit MQ and Spring Integration in our project. Every Message has a deliver mode, header, properties, and payload part.
We want to add properties i.e) priority with value 2(any integer) , payload with "test message 3" and publish the message to the queue named OES. please see screen shot.
How to add the messageproperties i.e) priority =2(or any value) in the below outbound-channel-adapter(Spring Integration). I know we can add "headers" by adding into "mapped-request-headers" but i would like to add the properties. There are no properties defined for the MessageProperties in "outbound-channel-adapter". Is there a way to overcome this issue.
We have no issues with payload, it is going already. we want to add only the MessageProperties with priority=2(any value). how to add that in the outbound-channel-adapter(no need of hardcoding, should be generic)?
<!-- the mapped-request-headers should be symmetric with
the list on the consumer side defined in consumerbeans.consumerHeaderMapper() -->
<int-amqp:outbound-channel-adapter id="publishingAmqpAdapter"
channel="producer-processed-event-channel"
amqp-template="amqpPublishingTemplate"
exchange-name="events_forwarding_exchange"
routing-key-expression="headers['routing-path']"
mapped-request-headers="X-CallerIdentity,routing-path,content-type,route_to*,event-type,compression-state,STANDARD_REQUEST_HEADERS"
/>
Other configuration:
<!-- chain routes and transforms the ApplicationEvent into a json string -->
<int:chain id="routingAndTransforming"
input-channel="producer-inbound-event-channel"
output-channel="producer-routed-event-channel">
<int:transformer ref="outboundMessageTracker"/>
<int:transformer ref="messagePropertiesTransformer"/>
<int:transformer ref="eventRouter"/>
<int:transformer ref="eventToJsonTransformer"/>
</int:chain>
<int:transformer id="messagePayloadCompressor"
input-channel="compress-message-payload"
output-channel="producer-processed-event-channel"
ref="payloadCompressor"/>
#Configuration("amqpProducerBeans")
#ImportResource(value = "classpath:com/apple/store/platform/events/si/event-producer-flow.xml")
public class AmqpProducerBeans {
#Bean(name = { "amqpPublishingTemplate" })
public AmqpTemplate amqpTemplate() {
logger.debug("creating amqp publishing template");
RabbitTemplate rabbitTemplate = new RabbitTemplate(producerConnectionFactory());
SimpleMessageConverter converter = new SimpleMessageConverter();
// following needed for retry logic
converter.setCreateMessageIds(true);
rabbitTemplate.setMessageConverter(converter);
return rabbitTemplate;
}
/*Other code commented */
}
Other Code:
import org.springframework.integration.Message;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.message.GenericMessage;
public class PayloadCompressor {
#Transformer
public Message<byte[]> compress(Message<String> message){
/* some code commented */
Map<String, Object> headers = new HashMap<String, Object>();
headers.putAll(message.getHeaders());
headers.remove("compression-state");
headers.put("compression-state", CompressionState.COMPRESSED);
Message<byte[]> compressedMessage = new GenericMessage<byte[]>(compressedPayload, headers);
return compressedMessage;
}
If we are not using spring integration, then we can use channel.basicPublish below way and send the MessageProperties.
ConnectionFactory factory = new ConnectionFactory();
factory.setVirtualHost("/");
factory.setHost("10.102.175.30");
factory.setUsername("rahul");
factory.setPassword("rahul");
factory.setPort(5672);
Connection connection = factory.newConnection();
System.out.println("got connection "+connection);
Channel channel = connection.createChannel();
MessageProperties msgproperties= new MessageProperties() ;
MessageProperties.BASIC.setPriority(3);
// set Messageproperties with priority
    String exchangeName = "HeaderExchange";
      String routingKey = "testkey";
      //routingkey
      byte[] messageBodyBytes = "Message having priority value 3".getBytes();
      channel.basicPublish(exchangeName,
                           routingKey,
                           true,
                           msgproperties.BASIC,
                           messageBodyBytes);
Please let me know if you need more details.
Properties are already mapped automatically - see the header mapper.
Simply use a <header-enricher/> to set the appropriate header and it will be mapped to the correct property. In the case of priority, the constant is here for the amqp-specific header constants, see here.

Creating flow or model programmatically

I want to create a flow or model dynamically without using mule-config.xml for tcp with remote machines.
It should be something like this:
MuleContext context = new DefaultMuleContextFactory().createMuleContext();
MuleRegistry registry = context.getRegistry();
EndpointBuilder testEndpointBuilder = new EndpointURIEndpointBuilder("vm://testFlow.in",
context);
testEndpointBuilder.setExchangePattern(MessageExchangePattern.REQUEST_RESPONSE);
registry.registerEndpointBuilder("testFlow.in", testEndpointBuilder);
InboundEndpoint vmInboundEndpoint = testEndpointBuilder.buildInboundEndpoint();
registry.registerEndpoint(vmInboundEndpoint);
StringAppendTransformer stringAppendTransformer = new StringAppendTransformer(" world");
stringAppendTransformer.setMuleContext(context);
Flow testFlow = new Flow("testFlow", context);
testFlow.setMessageSource(vmInboundEndpoint);
testFlow.setMessageProcessors(Arrays.asList((MessageProcessor) stringAppendTransformer));
registry.registerFlowConstruct(testFlow);
context.start();
MuleClient muleClient = new MuleClient(context);
MuleMessage response = muleClient.send("vm://testFlow.in", "hello", null);
Validate.isTrue(response.getPayloadAsString().equals("hello world"));
muleClient.dispose();
context.stop();
Not sure if I understand your problem, but if you need a tcp outbound endpoint in your flow, you just create it similarly like the inbound vm endpoint in the example, but you then add it to a certain point in the flow in a list with all the processors with setMessageProcessors, like in the example where stringAppendTransformer is wrapped inside a list and added to the flow.
The code to create your tcp outbound would be something like this:
String address = "tcp://localhost:1234";
EndpointURIEndpointBuilder builder = new
EndpointURIEndpointBuilder(new URIBuilder(address), context);
builder.setExchangePattern(MessageExchangePattern.REQUEST_RESPONSE);
registry.registerEndpointBuilder("testFlow.out", builder);
OutboundEndpoint tcpOutboundEndpoint = builder.buildOutboundEndpoint();
registry.registerEndpoint(tcpOutboundEndpoint);
UPDATE regarding your new comment:
using a Java component:
//object factory for your Java class
PrototypeObjectFactory objectFactory = new PrototypeObjectFactory(MyClass.class);
objectFactory.initialise();
//the actual component
DefaultJavaComponent component = new DefaultJavaComponent(objectFactory);
//entry point resolver to determine the called method
EntryPointResolver resolver = new ExplicitMethodEntryPointResolver();
((ExplicitMethodEntryPointResolver)resolver).addMethod("myMethod");
component.setEntryPointResolvers(Arrays.asList(resolver));
Then add the component in the list like you add all the other processors

Ninject ActivationException on ChildKernel

I'm getting an ActivationException saying there was an error activating IEventBroker. MyDataSource takes an IEventBroker has a parameter. If I don't use the child kernel, there is no issue. What is going on?
var kernel = new StandardKernel();
var childKernel = new ChildKernel(kernel);
var eventBroker = new EventBroker();
childKernel.Bind<IEventBroker>().ToConstant(eventBroker);
var myDS = childKernel.Get<MyDataSource>();
From the ChildKernel readme:
The default behavior of Ninject that classes are bound to themself if
not explicit still exists. But in this case this will be done by the
top most parent. This means that this class can not have any
dependency defined on a child kernel. I strongly suggest to have a
binding for all objects that are resolved by ninject and not to use
this default behavior.
So you need to explicitly bind MyDataSource to self to make it work:
var kernel = new StandardKernel();
var childKernel = new ChildKernel(kernel);
var eventBroker = new EventBroker();
childKernel.Bind<IEventBroker>().ToConstant(eventBroker);
childKernel.Bind<MyDataSource>().ToSelf();
var myDS = childKernel.Get<MyDataSource>();

WCF: Why does passing in a remote endpoint fail?

The problem I am having connecting a wcf client application to a host running on a separate machine is documented in a question previously asked:
WCF: Why does passing in a remote endpoint fail?
However, the solution provided here says you need to use a SpnEndpointIdentity with an empty string. Since my code doesn't look anything like the case in the example I have referenced, I need to know what to do with the SpnEndpointIdentity object I have created.
I have a ChannelFactory upon which I call Create channel, passing in an EndpointAddress:
public override void InitialiseChannel()
{
SpnEndpointIdentity spnEndpointIdentity = new SpnEndpointIdentity("");
var address = new EndpointAddress(EndpointName);
Proxy = ChannelFactory.CreateChannel(address);
}
(NB: ChannelFactory is of type IChannelFactory, where T is the service contract interface)
So what do I do with spnEndpointIdentity? I can't pass it to CreateChannel.
Or perhaps I can use it somehow when I create the channel factory:
private ChannelFactory<T> CreateChannelFactory()
{
var binding = new NetTcpBinding
{
ReaderQuotas = { MaxArrayLength = 2147483647 },
MaxReceivedMessageSize = 2147483647
};
SpnEndpointIdentity spnEndpointIdentity = new SpnEndpointIdentity("");
var channelFactory = new ChannelFactory<T>(binding);
return channelFactory;
}
Again, I can't pass it into the constructor, so what do I do with it?
Thanks.
You almiost got it.
What you're missing is that you associate the EndpointIdentity with the EndpointAddress, and then provide that to CreateChannel():
SpnEndpointIdentity spnEndpointIdentity = new SpnEndpointIdentity("");
var address = new EndpointAddress(EndpointName, spnEndpointIdentity);

WCF OutgoingMessageHeaders not saving values

I am trying to write a MessageHeader to the OutgoingMessageHeaders but the value isn't sticking.
BasicHttpBinding basicHttpBinding = new BasicHttpBinding();
EndpointAddress endpointAddress = new EndpointAddress("http://localhost:1003/Client.svc");
IClientService serviceClient = new ChannelFactory<IClientService>(basicHttpBinding, endpointAddress).CreateChannel();
// attempt 1
using (OperationContextScope scope = new OperationContextScope(serviceClient as IContextChannel))
{
OperationContext.Current.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader("SessionId","","ABC"));
}
// attempt 2
using (OperationContextScope scope = new OperationContextScope(serviceClient as IContextChannel))
{
OperationContext.Current.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader("SessionId","","ABC"));
}
OK, you can see that I am setting OutgoingMessageHeaders twice but that is simply to prove a point. In the second attempt, before I do the actual add, I inspect the OperationContext.Current.OutgoingMessageHeaders. I would expect this to have one entry. But it is zero. As soon as it gets out of the using scope the value is lost.
When this flows through to the server, it says it can't find the message header, indicating that as far as its concerned it hasn't been saved either.
Why isn't my MessageHeader sticking?
Jeff
The end of the using block calls the dispose and resets the previous OperationContext.
So you want something like this with the service call inside of the OperationContextScope.
using (OperationContextScope scope = new OperationContextScope(serviceClient as IContextChannel))
{
OperationContext.Current.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader("SessionId", "", "ABC"));
serviceClient.CallOperation();
}