I'm using Spring 4 websockets on Tomcat 8 and I have the following configuration:
<websocket:message-broker application-destination-prefix="/app">
<websocket:stomp-endpoint path="/notify">
<websocket:sockjs />
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/topic" />
</websocket:message-broker>
My Spring controller has the following method:
#MessageMapping("/notify/{client}")
public void pushMessage(#DestinationVariable long client, String message) {
System.out.println("Send " + message + " to " + client);
template.convertAndSend("/topic/push/" + client, message);
}
So what I'm trying to do here is that if client 1 wants to send a message to client 2, he uses /app/notify/2. The Spring controller will then push the message to topic /topic/push/2.
I wrote the following code in my client:
var id = 1;
var sock = new SockJS('/project/notify');
var client = Stomp.over(sock);
client.connect({}, function() {
client.subscribe('/topic/push/' + id, function(message) {
console.log(message);
});
});
The connection works perfectly, /project is just the context root of my application.
I also have the following code in my client to send a message:
client.send('/app/notify/' + id, {}, "test");
Both variables (client and id) are accessible, I'm not getting any errors from this part of the code and I can see in my console that the message is actually sent:
>>> SEND
destination:/app/notify/1
content-length:4
test
However, the System.out.println() statement in my controller is never executed, so I assume there is something wrong with my controller mappings or I'm not using the destination endpoints correctly (I don't understand why I have to specify the application prefix here, but not when connecting to that endpoint).
It seems it's unable to map when using a simple String message as payload. When I wrap the message in an object, then it works just fine.
EDIT: As stated in the comments, Spring already comes with a message wrapper called the TextMessage class.
Related
I created an application that calls a gateway asynchronously using Splitter / Aggregator. In my concfiguration file, I invoke the process via InvestmentMessagingGateway that proceeds on calling the splitter. Every splitted message calls a service activator in parallel and pass it inn aggregator.
I placed an error channel in the InvestmentMessagingGateway and transform every failed message to pass to the aggregator as well.
I collect every successful and failed message in aggregator as a compilation for the response.
But when I tried to place an exception in one or more of the messages, I get an error in my aggregator,
Reply message received but the receiving thread has already received a reply.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="......."">
<context:component-scan base-package="com.api.investments"/>
<!--The gateway to be called in parallel-->
<gateway id="InvestmentGateway" service-interface="com.api.investments.gateways.InvestmentGateway"/>
<channel id="investmentDetailChannel"/>
<service-activator input-channel="investmentDetailChannel" ref="investmentService" method="getAccountPortfolio"/>
<!--Inbound gateway to invoke Splitter / Aggregator-->
<gateway id="InvestmentMessageGateway" service-interface="com.api.investments.gateways.InvestmentMessageGateway"
default-reply-channel="investmentAsyncReceiver" error-channel="investmentAsyncException"/>
<channel id="investmentAsyncSender"/>
<channel id="investmentAsyncReceiver"/>
<!-- Splitter for Invesment Details-->
<splitter input-channel="investmentAsyncSender" output-channel="investmentSplitChannel" id="investmentDetailsSplitter" ref="investmentComponentsSplitter" />
<channel id="investmentSplitChannel">
<queue />
</channel>
<!--Calls the Investment Gateway asynchronously using split messages ad send the response in aggregator-->
<service-activator input-channel="investmentSplitChannel" output-channel="investmentAggregateChannel" ref="investmentAsyncActivator" method="retrieveInvestmentDetailsAsync" requires-reply="true">
<poller receive-timeout="5000" task-executor="investmentExecutor" fixed-rate="50"/>
</service-activator>
<channel id="investmentAsyncException"/>
<!--Handles failed messages and pass it in aggregator-->
<transformer input-channel="investmentAsyncException" output-channel="investmentAggregateChannel" ref="invesmentErrorLogger" method="logError"/>
<!--Aggreggates successfull and failed messaged-->
<publish-subscribe-channel id="investmentAggregateChannel"/>
<aggregator input-channel="investmentAggregateChannel" output-channel="investmentAsyncReceiver" id="investmentAggregator"
ref="investmentComponentsAggregator" correlation-strategy="investmentComponentsCorrelationStrategy"
expire-groups-upon-completion="true"
send-partial-result-on-expiry="true" />
<task:executor id="investmentExecutor" pool-size="10-1000"
queue-capacity="5000"/>
</beans:beans>
I tried putting my error channel in the poller of the service activator and but the error is still the same but this time it didn't went to the aggregator.
I also tried putting a mid-gateway for the service activator like this
but the error became null.
<gateway id="InvestmentAsyncActivatorGateway" service-interface="com.api.investments.gateways.InvestmentAsyncActivatorGateway"
default-reply-channel="investmentAggregateChannel" error-channel="investmentAsyncException"/>
----UPDATE------
This is the transformer that handles every error message
#Component("invesmentErrorLogger")
public class InvesmentErrorLoggerImpl implements InvestmentErrorLogger {
private final Logger logger = LoggerFactory.getLogger(Application.class.getName());
/**
* handles all error messages in InvestmentMessageGateway
* Creates an error message and pass it in the aggregator channel
* #param invesmentMessageError
* #return errorMessage
*/
#Override
public Message<ErrorDetails> logError(Message<?> invesmentMessageError) {
if(invesmentMessageError.getPayload().getClass().equals(MessagingException.class)) {
MessagingException messageException = (MessagingException) invesmentMessageError.getPayload();
AccountPortfolioRequest failedMsgPayload = (AccountPortfolioRequest) messageException.getFailedMessage().getPayload();
String logError = "Exception occured in Account Number: " + failedMsgPayload.getiAccNo();
logger.error(logError);
ErrorDetails productErrorDetail = new ErrorDetails();
productErrorDetail.setCode(InvestmentAPIErrorMessages.SVC_ERR_INQACCNTPORTFOLIO);
productErrorDetail.setMessage(InvestmentAPIErrorMessages.SVC_ERR_INQACCNTPORTFOLIO_DESC + ". Problem occured in Account Number: " + failedMsgPayload.getiAccNo());
Message<ErrorDetails> errorMessage = MessageBuilder.withPayload(productErrorDetail)
.setHeaderIfAbsent(InvestmentInquiryConstants.INV_CORRELATION_STRATEGY, InvestmentInquiryConstants.INV_CORRELATION_STRATEGY_VALUE)
.build();
return errorMessage;
}
else if(invesmentMessageError.getPayload().getClass().equals(MessageDeliveryException.class)) {
MessageDeliveryException messageException = (MessageDeliveryException) invesmentMessageError.getPayload();
AccountPortfolioRequest failedMsgPayload = (AccountPortfolioRequest) messageException.getFailedMessage().getPayload();
String logError = "Exception occured in Account Number: " + failedMsgPayload.getiAccNo();
logger.error(logError);
ErrorDetails productErrorDetail = new ErrorDetails();
productErrorDetail.setCode(InvestmentAPIErrorMessages.SVC_ERR_INQACCNTPORTFOLIO);
productErrorDetail.setMessage(InvestmentAPIErrorMessages.SVC_ERR_INQACCNTPORTFOLIO_DESC + ". Problem occured in Account Number: " + failedMsgPayload.getiAccNo());
Message<ErrorDetails> errorMessage = MessageBuilder.withPayload(productErrorDetail)
.setHeaderIfAbsent(InvestmentInquiryConstants.INV_CORRELATION_STRATEGY, InvestmentInquiryConstants.INV_CORRELATION_STRATEGY_VALUE)
.build();
return errorMessage;
}
else {
Exception messageException = (Exception) invesmentMessageError.getPayload();
String logError = "Exception occured in Investment Gateway ";
logger.error(logError);
logger.equals(messageException.getMessage());
ErrorDetails productErrorDetail = new ErrorDetails();
productErrorDetail.setCode(InvestmentAPIErrorMessages.SVC_ERR_INQACCNTPORTFOLIO);
productErrorDetail.setMessage(InvestmentAPIErrorMessages.SVC_ERR_INQACCNTPORTFOLIO_DESC + " " + messageException.getMessage());
Message<ErrorDetails> errorMessage = MessageBuilder.withPayload(productErrorDetail)
.setHeaderIfAbsent(InvestmentInquiryConstants.INV_CORRELATION_STRATEGY, InvestmentInquiryConstants.INV_CORRELATION_STRATEGY_VALUE)
.build();
return errorMessage;
}
}
}
Reply message received but the receiving thread has already received a reply.
As the error suggests, you can't send multiple replies (or errors) for a single request; it is strictly one reply per request.
You need another gateway between the splitter and the service.
The mid-flow gateway should have no service-interface so it uses the RequestReplyExchanger.
I am trying to implement a Camel Route that Reads a request message from a Remote systems queue (System.A.out) The route looks at the message body and dynamically routes it to another systems in queue (System.B.in) This Route is then complete, and waits for the next message on its from queue (Currently it blocks and waits for a response on a temp queue)
System.B Reads its in queue (System.B.in, not always a camel route) processes the message and drops a response on its out queue (System.B.out) System.B uses the JMSMessageID from the Request message as the JMSCorrelationID on its response, that is all it keeps from the request.
A Camel Route (Similar to the System.A.out, but listening on System.B.out) picks up the response message and using the JMSCorrelationID (The request would not have had a JMSCorrelationID and thus would be routed by message body) finds the request's JMSReplyTo Queue (System.A.in) and drops the response on System.A's in queue for System.A to process.
I am using SpringBoot and Camel 2.18.3, the message queue is IMB MQ version 8
My route looks like this:
#Override
public void configure() throws Exception {
//#formatter:off
Predicate validRoute = header("route-valid").isEqualTo(true);
Predicate inValidRoute = header("route-valid").isEqualTo(false);
Predicate splitRoute = header("route-split").isEqualTo(true);
Predicate singleRoute = header("route-split").isEqualTo(false);
Predicate validSplitRoute = PredicateBuilder.and(validRoute, splitRoute);
Predicate validSingelRoute = PredicateBuilder.and(validRoute, singleRoute);
from(endpoint(incomingURI)).routeId(routeId)
.process(exchange -> {
exchange.getIn().setHeader("route-source", format("%s-%s", incomingURI, routeId));
})
.to(endpoint(format("bean:evaluateIncomingMessageService?method=routeMessage(*, %s)", replyToURI)))
.choice()
.when(validSingelRoute)
.log(DEBUG, "Creating a Single route")
.to(endpoint("bean:messageCoalitionService?method=saveInstruction(*)"))
.setExchangePattern(ExchangePattern.InOut)
.toD("${header.route-recipients}")
.when(inValidRoute)
.log(DEBUG, "a.b.test", format("Incoming message [%s] failed evaluation: %s", incomingURI, body()))
.to(endpoint(deadLetterURI))
.routeId(format("%s-%s", incomingURI, routeId))
.when(validSplitRoute)
.log(DEBUG, "Creating a Split route")
.to(endpoint("bean:messageCoalitionService?method=saveInstructions(*)"))
.setExchangePattern(ExchangePattern.InOut)
.multicast()
.toD("${header.route-recipients}").endChoice()
.otherwise()
.log(DEBUG, "a.b.test", format("Incoming message [%s] failed evaluation: %s", incomingURI, body()))
.to(endpoint(deadLetterURI))
.routeId(format("%s-%s", incomingURI, routeId));
The Spring Bean evaluateIncomingMessageService decides if the message is a Request (No Correlation ID) or a Response and sets routing headers for the Request. I hoped Camel would automatically route responses to the Request.JMSReplyTo Queue, if not how can one do this?
replyToURI is configured in the Camel Route builder, if the route Listens on System.A.out its replyToURI will always be System.A.in.
evaluateIncomingMessageService.routeMessage looks like this:
public void routeMessage(final Exchange exchange, final String replyToURI) {
String correlationId = exchange.getIn().getHeader("JMSCorrelationID", String.class);
if (correlationId != null) {
log.debug("Processing Message Response with JMSCorrelationID [{}]", correlationId);
exchange.getIn().setHeader("JMSReplyTo", replyToURI);
} else {
// Request Messages have nave NO correlationId
log.debug("Processing Message Request with MessageID [{}] and JMSMessageID: [{}]",
exchange.getIn().getMessageId(),
exchange.getIn().getHeader("JMSMessageID") != null ? exchange.getIn().getHeader("JMSMessageID").toString() : exchange.getIn().getMessageId());
String message = exchange.getIn().getBody(String.class);
Set<ContentBasedRoute> validRoutes = contentBasedRouting
.stream().filter(
routeEntity -> Pattern.compile(
routeEntity.getRegularExpression(), DOTALL).matcher(message).matches()).collect(Collectors.toSet());
if (validRoutes.isEmpty()) {
log.warn("No valid routes found for message: [{}] ", message);
exchange.getIn().setHeader("route-valid", false);
} else {
HashMap<String, ContentBasedRoute> uniqueRoutes = new HashMap<>();
validRoutes.stream().forEach(route -> uniqueRoutes.putIfAbsent(route.getDestination(), route));
exchange.getIn().setHeader("route-valid", true);
exchange.getIn().setHeader("route-count", uniqueRoutes.size());
exchange.getIn().setHeader("JMSReplyTo", replyToURI);
//if (exchange.getIn().getHeader("JMSMessageID") == null) {
// exchange.getIn().setHeader("JMSMessageID", exchange.getIn().getMessageId());
//}
if (uniqueRoutes.size() > 1) {
log.debug("Building a split route");
StringBuilder routes = new StringBuilder();
StringBuilder routeIds = new StringBuilder();
StringBuilder routeRegex = new StringBuilder();
uniqueRoutes.keySet().stream().forEach(i -> routes.append(i).append(","));
uniqueRoutes.values().stream().forEach(j -> routeIds.append(j.getRouteId()).append(","));
uniqueRoutes.values().stream().forEach(k -> routeRegex.append(k.getRegularExpression()).append(","));
routes.deleteCharAt(routes.length() - 1);
routeIds.deleteCharAt(routeIds.length() - 1);
routeRegex.deleteCharAt(routeRegex.length() - 1);
exchange.getIn().setHeader("route-split", true);
exchange.getIn().setHeader("route-uuid", routeIds.toString());
exchange.getIn().setHeader("route-regex", routeRegex.toString());
exchange.getIn().setHeader("route-recipients", routes.toString());
} else {
exchange.getIn().setHeader("route-split", false);
exchange.getIn().setHeader("route-uuid", uniqueRoutes.values().iterator().next().getRouteId());
exchange.getIn().setHeader("route-regex", uniqueRoutes.values().iterator().next().getRegularExpression());
exchange.getIn().setHeader("route-recipients", uniqueRoutes.values().iterator().next().getDestination());
}
}
}
}
The Bean messageCoalitionService simply saves the message body and headers so the messages can be reproduced and for auditing of the system.
I am not sure if I have gone about this incorrectly, Should I be using the Camel Async API or do I need pipes to implement this? This pattern looks close to what I need http://camel.apache.org/async.html (Asynchronous Request Reply) Any Help would be great thanks.
In the end I implemented the above using Spring Integration. I was not able to find a way to retrieve the Message ID of the sent Message once the Camel Route had sent the message on which meant I had no way of tracking the Correlation ID when a response was sent back. Using the Camel InOut caused Camel to block and wait for a response which is also not what I wanted.
Thanks to lutalex for this solution:
http://forum.spring.io/forum/other-spring-related/remoting/30397-jmsmessageid-after-message-is-sent?p=745127#post745127
I am trying to get notified when an user subscribes to a stomp user destination using #SubscribeMapping annotation. My ideia is to send some initialization data to when it joins.
Although i am not being able to get this working:
Javascript:
stompClient.subscribe('/user/monitor', function(msg) {
console.log(msg);
});
Java side:
#SubscribeMapping("/monitor-user{clientId}")
public void init(#DestinationVariable("clientId") String clientId) {
messagingTemplate.convertAndSend("/user/" + clientId + "/monitor", getOps());
}
I tried many mapping combinations such as "/user/monitor-{clientId}", "/user/monitor" (Removing clientId), no success at all.
What is the expected mapping value so this get called?
Thank you!
Since the client subscribes to the "/user/monitor" queue, the server #SubscribeMapping annotation must be to "/user/monitor" and not to "/user/monitor{something}".
The server can understand what client is depending on your authentication scheme. If you use a sockjs websocket you can use HTTP authentication, and hence leverage Spring Security, and add a "Principal" parameter to your function that will hold user information:
#SubscribeMapping("/monitor")
public void init(Principal p) {
String user = p.getName();
messagingTemplate.convertAndSendToUser(user, "/monitor", getOps());
}
If you don't use http authentication you may send the server the user info, for example adding a custom STOMP header that can be accessed from the server using a SimpMessageHeaderAccessor:
#SubscribeMapping("/monitor")
public void init(SimpMessageHeaderAccessor accessor) {
String user = accessor.getFirstNativeHeader("userid");
messagingTemplate.convertAndSendToUser(user, "/monitor", getOps());
}
Another way could be to subscribe to a different queue name which contains the user information (and this maybe was your proposal). The client must use a code similar to:
stompClient.subscribe('/topic/byUser/'+userId, function(msg) {
console.log(msg);
});
so that the server can access it this way:
#SubscribeMapping("/byUser/{userId}")
public void init(#DestinationVariable String userId) {
messagingTemplate.convertAndSendToUser(userId, "/monitor", getOps());
}
but in this case keep in mind that that queue is public and hence other clients can access their messages if they knows other client names.
I'm trying to call a https web service using cxf generated client proxies within Mule. Almost 99% of the time, I get
Caused by: org.apache.commons.httpclient.ProtocolException: Unbuffered entity enclosing request can not be repeated.
at org.apache.commons.httpclient.methods.EntityEnclosingMethod.writeRequestBody(EntityEnclosingMethod.java:487)
at org.apache.commons.httpclient.HttpMethodBase.writeRequest(HttpMethodBase.java:2114)
at org.apache.commons.httpclient.HttpMethodBase.execute(HttpMethodBase.java:1096)*
The app has http inbound end point. The Mule Java transformer tries to call a webservice using https using cxf generated client proxies. I'm running into above said exception.
I've provided screenshot the mule flow [http://i.stack.imgur.com/7X9Wg.jpg]. Much appreciated!!
Mule config xml
<cxf:jaxws-service serviceClass="test.service.https.TestService" doc:name="SOAP" configuration-ref="CXF_Configuration" enableMuleSoapHeaders="false"/>
<custom-transformer class="test.service.https.CallLicenseService" doc:name="Calls HTTPS WS using CXF generated client proxies" encoding="UTF-8" mimeType="text/plain"/>
<logger message="Success" level="INFO" doc:name="Logger"/>
<set-payload value="#['HELLO SUCCESS']" doc:name="Set Payload"/> </flow>
Transformer
URL wsdlURL = null;
String serviceUrl = "TARGET_HTTPS_WSDL"; //This would be the target https URL
try {
wsdlURL = new URL(serviceUrl);
} catch (MalformedURLException e) {
Logger.getLogger(getClass()).info("", e);
}
AuditLogServiceService ss = new AuditLogServiceService(wsdlURL);
AuditLoggingService port = ss.getAuditLoggingServicePort();
((BindingProvider) port).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
serviceUrl.substring(0, serviceUrl.length() - 5));
AuditLogServiceRequest request = new AuditLogServiceRequest();
request.setClientId("4");
request.setUserId("101");
request.setEventSubType("1");
request.setEventType("1");
AuditLogMessage msg = new AuditLogMessage();
msg.setMessage("Hello Test");
request.getLogMessages().add(msg);
AuditLogServiceResponse response = port.logEvent(request);
System.out.println(response.getMessage());
return response.getMessage();
First of all if you need to consume a webservice You need to put <cxf:jaxws-client serviceClass instead of cxf:jaxws-client ...next step is you need to use an http outbound endpoint to post to the external webservice ... pls refer the following link :- http://www.mulesoft.org/documentation/display/current/Consuming+Web+Services+with+CXF
One more thing .. you need to use java component instead of <custom-transformer class ..you need to set the payload just before the component ... I mean you need to set the payload before posting it to external webservice
Ok here i'm , i'm right now following the guides on spring site but i'm having problem on how to deliver a notification to only one user using WebSocket, i'm following this guide https://spring.io/guides/gs/messaging-stomp-websocket/ .
What I want is: i have 2 users, both of them subscribe to process1... User1 need to let the server process his pass...now i want that the server will deliver the notification only to User1...
#Controller
public class ProcessController {
#MessageMapping("/ProcessOwner/approve/{pass}")
#SendTo("")
public String notifica(#DestinationVariable String pass)throws Exception{
return "ok"+pass;
}
}
Now what should i write in the #SendTo field to deliver the answer only to user1 ? if ill write /Process/process1 both user1 and user2 will receive the message....
You ca use the sufix. It's send to every unique client.
When you subscribe in the js code:
client.connect('guest', 'guest', function(frame) {
var suffix = frame.headers['queue-suffix'];
client.subscribe("/queue/error" + suffix, function(msg) {
// handle error
});
client.subscribe("/queue/position-updates" + suffix, function(msg) {
// handle position update
});
});
On the server side you can use #ReplyToUser or the message template
String user = "fabrice";
String queue = "/queue/position-updates";
this.messagingTemplate.convertAndSendToUser(user, queue, position);
See more here: http://assets.spring.io/wp/WebSocketBlogPost.html (section: Sending Messages To a Single User)