I need an xml to be sent into a queue and be aggregated by one of it's fields using xpath.
That's the code of my RouteBuilder class implementation:
public class SimpleRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("activemq:queue:test.input").aggregate(new MyAggregationStrategy()).
xpath("login/(login)='manager", String.class).completionPredicate(header("aggregated").isEqualTo(5))
.to("activemq:queue:test.output").end()
;
}
}
class MyAggregationStrategy implements AggregationStrategy {
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
Message newIn = newExchange.getIn();
String oldBody = oldExchange.getIn().getBody(String.class);
String newBody = newIn.getBody(String.class);
newIn.setBody(oldBody + newBody);
return newExchange;
}
}
The xml that is being sent looks like this:
<person>
<login>login</login>
<password>pass</password>
</person>
When I copy this jar into the activemq lib folder and start the activemq, such exception appears:
ERROR: java.lang.IllegalArgumentException: Invalid broker URI, no scheme specified: start
What could be the problem here?
Your code extract doesn't lead to the reported exception. And I am not sure, what your use case is.
Anyway, following route matches the login content of the input messages. When 5 messages with the same login have been received, then the aggregation is sent to the output target. Please note the null check for oldExchange in the aggregate method:
public class SimpleRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("activemq:queue:test.input")
.aggregate(new MyAggregationStrategy()).xpath("/person/login", String.class)
.completionSize(5)
.to("activemq:queue:test.output");
}
}
public class MyAggregationStrategy implements AggregationStrategy {
#Override
public Exchange aggregate(final Exchange oldExchange, final Exchange newExchange) {
Message newIn = newExchange.getIn();
String newBody = newIn.getBody(String.class);
if (oldExchange == null) {
newIn.setBody(newBody);
} else {
String oldBody = oldExchange.getIn().getBody(String.class);
newIn.setBody(oldBody + newBody);
}
return newExchange;
}
}
Related
I have an aggregation Strategy in my camel split() route.
from("direct:split")
.split()
.method(new SplitBean(), "splitMessage")
.aggregationStrategy(AggregationStrategies.groupedExchange())
.stopOnException()
.to("direct:destination")
.end();
The splitMessage method has split the data into 3 request data. So I am hitting the http destination endpoint 3 times.
Using the aggregation Strategy my http response got aggregated for the first 2 times.
Third time when the http call failed with an exception. The exchange returned to the caller does not contain the first two grouped exchanges.
How can I get the grouped exchanges with (success, exception) this case.
Please tell me if the question is not clear.
Change from .stopOnException() to .stopOnAggregateException()
create an AggregationStrategy strategy class and handle the exception from there
public void configure() throws Exception {
from("direct:split")
.split()
.method(new SplitBean(), "splitMessage")
.aggregationStrategy(new ErrorStrategy())
.stopOnAggregateException()
.to("direct:destination")
.end();
}
public class ErrorStrategy implements CompletionAwareAggregationStrategy {
#Override
public void onCompletion(Exchange exchange) {
}
#Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
if (newExchange.getException() != null) {
return oldExchange;
}
if (oldExchange == null) {
....
return newExchange;
}
.....
return oldExchange;
}
}
I have Spring boot app
It is receiving Json formatted messages from ActiveMQ
#Component class Receiver {
#JmsListener(destination = "queue")
public void receiveMessage(BusMessage message) {
System.out.println("Received <" + message + ">");
}
The problem, is what sometimes Json can arrive without proper header
In this case i get exception
org.springframework.messaging.converter.MessageConversionException: Cannot convert from [java.lang.String] to [cam.melexis.minipcs2socketioserver.BusMessage] for org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage#38bd4ff9, failedMessage=org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage#38bd4ff9
One solution can be is to get message as string and deserialise it
#Component class Receiver {
#JmsListener(destination = "queue")
public void receiveScannerMessage(Message message, Session session) {
System.out.println("Received <" + message + ">");
}
But i want to do something more graceful
To simplify all #JmsListener
And to make my learning curve in Spring more curved :)
Knowing, what all messages will arrive in Json format, can i add missing header to all incoming messages?
Or it is another "better" way exist?
This made a job
#EnableJms
#Configuration class JmsListenerConfig implements JmsListenerConfigurer {
Logger logger = LoggerFactory.getLogger(Receiver.class);
#Bean
public DefaultMessageHandlerMethodFactory handlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setMessageConverter(messageConverter());
return factory;
}
#Bean
public MessageConverter messageConverter() {
return new MappingJackson2MessageConverter();
}
#Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(handlerMethodFactory());
}
}
But i have no idea how
Maybe anybody can help me with this?
The Spring framework support tcp connection as well , i wrote code below to setup a simple socket server , i am confused about adding below futures to my socket server :
authorizing clients based on a unique identifier ( for example a client secret received from client, maybe using TCP Connection Events )
send a message directly to specific client (based on identifier)
broadcast a message
UPDATE :
Config.sendMessage added to send message to single client
Config.broadCast added to broadcast message
authorizeIncomingConnection to authorize clients , accept or reject connections
tcpConnections static filed added to keep tcpEvent sources
Questions !
is using tcpConnections HashMap good idea ?!
is the authorization method i implemented a good one ?!
Main.java
#SpringBootApplication
public class Main {
public static void main(final String[] args) {
SpringApplication.run(Main.class, args);
}
}
Config.java
#EnableIntegration
#IntegrationComponentScan
#Configuration
public class Config implements ApplicationListener<TcpConnectionEvent> {
private static final Logger LOGGER = Logger.getLogger(Config.class.getName());
#Bean
public AbstractServerConnectionFactory AbstractServerConnectionFactory() {
return new TcpNetServerConnectionFactory(8181);
}
#Bean
public TcpInboundGateway TcpInboundGateway(AbstractServerConnectionFactory connectionFactory) {
TcpInboundGateway inGate = new TcpInboundGateway();
inGate.setConnectionFactory(connectionFactory);
inGate.setRequestChannel(getMessageChannel());
return inGate;
}
#Bean
public MessageChannel getMessageChannel() {
return new DirectChannel();
}
#MessageEndpoint
public class Echo {
#Transformer(inputChannel = "getMessageChannel")
public String convert(byte[] bytes) throws Exception {
return new String(bytes);
}
}
private static ConcurrentHashMap<String, TcpConnection> tcpConnections = new ConcurrentHashMap<>();
#Override
public void onApplicationEvent(TcpConnectionEvent tcpEvent) {
TcpConnection source = (TcpConnection) tcpEvent.getSource();
if (tcpEvent instanceof TcpConnectionOpenEvent) {
LOGGER.info("Socket Opened " + source.getConnectionId());
tcpConnections.put(tcpEvent.getConnectionId(), source);
if (!authorizeIncomingConnection(source.getSocketInfo())) {
LOGGER.warn("Socket Rejected " + source.getConnectionId());
source.close();
}
} else if (tcpEvent instanceof TcpConnectionCloseEvent) {
LOGGER.info("Socket Closed " + source.getConnectionId());
tcpConnections.remove(source.getConnectionId());
}
}
private boolean authorizeIncomingConnection(SocketInfo socketInfo) {
//Authorization Logic , Like Ip,Mac Address WhiteList or anyThing else !
return (System.currentTimeMillis() / 1000) % 2 == 0;
}
public static String broadCast(String message) {
Set<String> connectionIds = tcpConnections.keySet();
int successCounter = 0;
int FailureCounter = 0;
for (String connectionId : connectionIds) {
try {
sendMessage(connectionId, message);
successCounter++;
} catch (Exception e) {
FailureCounter++;
}
}
return "BroadCast Result , Success : " + successCounter + " Failure : " + FailureCounter;
}
public static void sendMessage(String connectionId, final String message) throws Exception {
tcpConnections.get(connectionId).send(new Message<String>() {
#Override
public String getPayload() {
return message;
}
#Override
public MessageHeaders getHeaders() {
return null;
}
});
}
}
MainController.java
#Controller
public class MainController {
#RequestMapping("/notify/{connectionId}/{message}")
#ResponseBody
public String home(#PathVariable String connectionId, #PathVariable String message) {
try {
Config.sendMessage(connectionId, message);
return "Client Notified !";
} catch (Exception e) {
return "Failed To Notify Client , cause : \n " + e.toString();
}
}
#RequestMapping("/broadCast/{message}")
#ResponseBody
public String home(#PathVariable String message) {
return Config.broadCast(message);
}
}
Usage :
Socket Request/Response Mode
notify single client
http://localhost:8080/notify/{connectionId}/{message}
broadCast
http://localhost:8080/broadCast/{message}
The TcpConnectionOpenEvent contains a connectionId property. Each message coming from that client will have the same property in the IpHeaders.CONNECTION_ID message header.
Add a custom router that keeps track of the logged-on state of each connection.
Lookup the connection id and if not authenticated, route to a challenge/response subflow.
When authenticated, route to the normal flow.
To use arbitrary messaging (rather than request/response) use a TcpReceivingChannelAdapter and TcpSendingMessageHandler instead of an inbound gateway. Both configured to use the same connection factory. For each message sent to the message handler, add the IpHeaders.CONNECTION_ID header to target the specific client.
To broadcast, send a message for each connection id.
I'm learning Apache camel from the "Camel in Action" book and currently I'm on data transformation. More particularly Content Enricher EIP. I noticed that when I run the code below from the book Camel creates fileName + .camelLock file but it doesn't remove it after finishing route.
Is there something wrong from the code side ? Or it should work like that ?
import java.io.File;
import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.processor.aggregate.AggregationStrategy;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;
public class OrderToCsvProcessorTest extends CamelTestSupport {
#Test
public void testOrderToCsvProcessor() throws Exception {
// this is the inhouse format we want to transform to CSV
String inhouse = "0000004444000001212320091208 1217#1478#2132";
template.sendBodyAndHeader("direct:start", inhouse, "Date", "20091208");
File file = new File("target/orders/received/report-20091208.csv");
assertTrue("File should exist", file.exists());
// compare the expected file content
String body = context.getTypeConverter().convertTo(String.class, file);
assertEquals("000000444,20091208,000001212,1217,1478,2132\nthis,is,sample,string", body);
}
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start")
.process(new OrderToCsvProcessor())
.pollEnrich("file://target/input?noop=true",
new AggregationStrategy() {
#Override
public Exchange aggregate( Exchange oldExchange, Exchange newExchange) {
if (newExchange == null) {
return oldExchange;
}
String http = oldExchange.getIn().getBody(String.class);
String ftp = newExchange.getIn().getBody(String.class);
String body = http + "\n" + ftp;
oldExchange.getIn().setBody(body);
return oldExchange;
}
})
.to("file://target/orders/received?fileName=report-${header.Date}.csv");
}
};
}
}
Processor which is used in code:
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
public class OrderToCsvProcessor implements Processor {
public void process(Exchange exchange) throws Exception {
String custom = exchange.getIn().getBody(String.class);
String id = custom.substring(0, 9);
String customerId = custom.substring(10, 19);
String date = custom.substring(20, 29);
String items = custom.substring(30);
String[] itemIds = items.split("#");
StringBuilder csv = new StringBuilder();
csv.append(id.trim());
csv.append(",").append(date.trim());
csv.append(",").append(customerId.trim());
for (String item : itemIds) {
csv.append(",").append(item.trim());
}
exchange.getIn().setBody(csv.toString());
}
}
GenericFileOnCompletion is in charge of deleting the lock file. You need to handoverCompletions in AggregationStrategy just like this.
new AggregationStrategy() {
#Override
public Exchange aggregate( Exchange oldExchange, Exchange newExchange) { if (newExchange == null) {
return oldExchange; }
String http = oldExchange.getIn().getBody(String.class); String ftp = newExchange.getIn().getBody(String.class);
String body = http + "\n" + ftp;
oldExchange.getIn().setBody(body);
newExchange.handoverCompletions(oldExchange);
return oldExchange; } })
The issue is due to the fact that you are pulling a file using pollEnrich with a custom AggregationStrategy.
When using a custom AggregationStrategy in this use case, then certain properties of the aggregated Exchange need to be copied over to the original Exchange for the Camel markerFile to be deleted correctly
So, at the end of your AggregationStrategy you can do :
oldExchange.getProperties().putAll(newExchange.getProperties());
Source : https://access.redhat.com/solutions/2189891
I have an JMS listener, and I have to take the message, manipulate it and then redirect it to an page using websocket.
Well, I’m just confused about the configuration, I have configured the WebSocketConfig:
#Configuration
#EnableWebSocketMessageBroker
#EnableScheduling
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws/firstep/").withSockJS();
registry.addEndpoint("/ws/secondep/").withSockJS();
}
And this it should be correct, Then my webpage is:
var socket = new SockJS("/myapp-web/api/ws/secondep/",undefined,options);
var stompClient = Stomp.over(socket);
stompClient.connect({
company : "xxx"
}, function(frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/register', function(message){
console.log('message: ' + message);
});
stompClient.subscribe('/topic/update', function(message){
console.log('message: ' + message);
});
And the connection works.
Now On my jms listener I tried to send a message in this way:
public class ImporterListener implements MessageListener {
Logger logger = LoggerFactory.getLogger(ImporterListner.class);
#SendTo("/topic/register")
private String TestMessage() {
return "TestMessage";
}
#Override
public void onMessage(Message message) {
logger.info("Request on message");
if (message instanceof MapMessage) {
MapMessage t = (MapMessage) message;
TestMessage(); //<--- have to send the message here
But it doesn’t work.
The questions are:
How to send a message?
Where do I have to specify the end point (secondep) when I send a message?
Thank you! any help is appreciated!
TestMessage(); //<--- have to send the message here
No, it has't to send, because you use method from the same class, but #SendTo makes your ImporterListener as proxy and Advice will work on method only from another component.
You should inject this:
#Autowired
#Qualifier("brokerMessagingTemplate")
private MessageSendingOperations brokerMessagingTemplate;
And send a message using that:
brokerMessagingTemplate.convertAndSend("/topic/register", "TestMessage");
Where do I have to specify the end point (secondep) when I send a message?
It is for the #MessageMapping("/ws/secondep") on some POJO method to receive message from clients. It isn't for the sending part.