Camel: forward message to dynamic destinations (from database) - spring

I am using camel 2.8.4 in my app. my app will receive request from a queue, then the request will be validated by a Validator. Base on the content of the message, the Validator will forward the request to different destinations. Validator will be a POJO bean. Destinations will be get from database (this is a MUST). I prefer to use spring dsl for camelContext.
1. I dont know how to write the validator to forward req to destinations.
2. Can we use something similar like this
<to uri='method=getURI() bean='Validator''> in camelContext
<camelContext>
<route id="route-1">
<from uri="mq:queue:QUEUE"/>
<bean ref="Validator" method="validate"/>
<!--i would be great if we can use <to uri="dynamicURI-from-database"> here -->
</route>
</camelContext>
Class Validator{
public void validate(String req){
if (...)
//get uri1 from database
String uri1=getURI(..);
//forward req to uri1
...........
else
//get uri2 from database
String uri2=getURI(...);
//forward req to uri2
...........
}
public String getURI(..){
......
return uri;
}
}

Use the dynamic URI feature to generate a URI at runtime. You can invoke a processor which sets the URI in exchange and then use that in the to clause.
Something like :
process(new Procesor()
public void process(Exchange exchange){
exchange.setHeader("myURI",someURI);
});
and in the to clause
<to uri="${header.myURI}"/>

Did you have a look at the dynamic recipient list pattern: http://camel.apache.org/recipient-list.html ?

You can use toD provided the destination endpoints are Http APIs. Please refer the link : https://camel.apache.org/components/latest/eips/toD-eip.html

Related

Set dynamic custom headers in http-outbound gateway

I have http outbound gateway :
<int-http:outbound-gateway encode-uri="true" http-method="POST" header-mapper="headerMappper"
charset="UTF-8" url="{url}" expected-response-type="java.lang.String">
<int-http:uri-variable name="url" expression="headers.uri"/>
</int-http:outbound-gateway>
Header Mapper bean configuration :
<bean class="com.cc.gateway.HeaderMapper"/>
public class HeaderMapper extends org.springframework.integration.http.support.DefaultHttpHeaderMapper{
#Bean("headerMappper")
public HeaderMapper mapHeader()
{
this.setOutboundHeaderNames(getHeaderMapper());
this.setUserDefinedHeaderPrefix("");
return this;
}
public String[] getHeaderMapper()
{
Object [] headersArray =new HeadersConfig().getHeaders().keySet().toArray();
return Arrays.copyOf(headersArray,headersArray.length,String[].class);
}
}
How I can set header mapper configuration on every request ?
My configuration reads only once at deployment time.
The DefaultHttpHeaderMapper isn't structured for that kind of use; it is not thread-safe to change the mapped headers for each request.
You would have to override so many methods to make it thread-safe, it would probably be easier to just implement your own custom HeaderMapper<HttpHeaders>.
If you only have one message being sent at a time, though, simply override fromHeaders() and update the headers to map before calling super.fromHeaders().
That said, it's rather unusual to want to dynamically change mapped headers.

How to test a custom processor - Apache Camel Spring Test

I have the following route:
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="activemq:topic:inbox" />
<log message="To: ${in.header.recipients}" />
<to uri="bean:myLogger" />
</route>
</camelContext>
bean:myLogger is my custom processor for formatting the log messages I am getting. The process method in my custom processor simply calls a private method that appends the type of message (e.g. CC) and the recipients' email. I am struggling to see how to test the actual resulting logs. I am using CamelSpringTestSupportand I am OK when it comes to testing the myLogger endpoint:
#Produce(uri = "activemq:topic:inbox")
protected ProducerTemplate template;
#Override
protected AbstractApplicationContext createApplicationContext() {
return new ClassPathXmlApplicationContext("file:src/main/resources/my-camel-context.xml");
}
#Test
public void testLogEndpoint() throws Exception {
String body = "Hello World";
template.sendBody("activemq:topic:inbox", body);
LOG.info("This is the message body we sent {} ", body);
}
However, I am not really sure how to test the format of the returned logs. Do I send the email of the recipients in a similar fashion as the example above? But then how do I check whether the format is the correct one? I am really looking more for the approach than the actual solution.
Thank you so much for your help,
I.
Make the log formatting in the bean independent on the logger such as another public/package method and then write a plain unit test that tests this method. Then you don't need to use Camel in that test at all.
If you can't do that, then maybe configure the logger to log to a file, and then read that file afterwards and test its logged as you want.

Spring MVC with Spring Integration HTTP: how to pass the queryString avoiding the "?" encoding with "%3F" ?

I have two Spring MVC applications interconneted between each others with Spring Integration HTTP.
I have an "application1" and an "application2".
The application1 receive this HTTP GET request:
http://localhost:8080/application1/api/persons/search/findByName?name=Name
The application1 manage the request with this #Controller:
#Controller
public class ApplicationController {
#RequestMapping(value = "/api/{repository}/search/{methodName}", method = RequestMethod.GET)
public void search(#PathVariable(value="repository") String repository,
#PathVariable(value="methodName") String methodName,
ModelMap model,
HttpServletRequest request,
HttpServletResponse response) {
// handling the request ...
}
}
Here is what I can see in the request properties:
getRequestURI=/application1/api/persons/search/findByName
getRequestedSessionId=null
getContextPath=/application1
getPathTranslated=null
getAuthType=null
getMethod=GET
getQueryString=name=Name
getServletPath=/api/persons/search/findByName
getPathInfo=null
getRemoteUser=null
I want to "transfer" this request to the application2 using Spring Integration HTTP with an <int-http:outbound-gateway>.
The message for the channel used by the outbound-gateway is originated in the #Controller in this way:
MessagingChannel messagingChannel = (MessagingChannel)appContext.getBean("requestChannelBean");
String payload = repository+"/search/"+methodName;
Message<String> message = MessageBuilder.withPayload(payload).build();
MessageChannel requestChannel = messagingChannel.getRequestChannel();
MessagingTemplate messagingTemplate = new MessagingTemplate();
Message<?> response = messagingTemplate.sendAndReceive(requestChannel, message);
This is the <int-http:outbound-gateway> configuration:
<int-http:outbound-gateway id="gateway" rest-template="restTemplate"
url="http://localhost:8080/application2/api/service/{pathToCall}"
http-method="POST" header-mapper="headerMapper" extract-request-payload="true"
expected-response-type="java.lang.String">
<int-http:uri-variable name="pathToCall" expression="payload"/>
</int-http:outbound-gateway>
This gateway produces an HTTP POST request towards the url:
http://localhost:8080/application2/api/service/persons/search/findByName
But in this request I lose the original QueryString received by the application1.
I have tried to add the queryString directly to the payload as follows:
String queryString = "";
if (request.getQueryString()!=null)
queryString = request.getQueryString();
String payload = repository+"/search/"+methodName+"?"+queryString;
But this doesn't work: the produced url is:
http://localhost:8080/application2/api/service/persons/search/findByName%3Fname=Name
The "?" symbol is replaced by "%3F", so the called method is "/service/persons/search/findByName%3Fname=Name", instead of "/service/persons/search/findByName"
I suppose that it depends on the http-method="POST"; I want to use the POST method in any case, because I want to use this "service" for general requests.
So what I have to do in order to transfer the queryString of the original request to the other side in the simplest way as possible?
Thanks in advance.
I have found the answer.
The encoding of the "?" into "%3F" was made by the <int-http:outbound-gateway> beacuse the encode-uri="false" attribute was missing.
The outbound gateway encodes the URI by default, because the encode-uri attribute is setted to true by default.

Camel & CXF & REST: ERROR No message body writer has been found for class java.util.ArrayList, ContentType: application/json

In my Spring configuration file:
<bean id="jacksonJsonProvider" class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
<bean id="restJacksonProviderList" class="java.util.ArrayList">
<constructor-arg>
<list>
<ref bean="jacksonJsonProvider"/>
</list>
</constructor-arg>
</bean>
//......
<route id="RestMyRoute">
<from id="RestRequest" uri="cxfrs:/rest/MyService?resourceClasses=com.myself.services.MyService&bindingStyle=SimpleConsumer&providers=#restJacksonProviderList" />
<to uri="direct:doRoute" />
</route>
The Service interface:
#GET
#Path("/my/something/{id}")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#WebMethod
#WebResult(name = "getSomethingResponse")
public List<MySomething> getSomething(
#PathParam("id") #WebParam(name = "id") String id);
The code above works! I can send the get request to the URl and I get a JSON response.
Now, I do a small change: Instead of defining the web service's URL (and the route) by XML configuration, I define them by Java code:
public class MyRoute extends RouteBuilder {
private String uriRest = "cxfrs:/rest/MyService?resourceClasses=com.myself.services.MyService&bindingStyle=SimpleConsumer&providers=#restJacksonProviderList";
#Override
public void configure() throws Exception {
from(uriRest).
to("log:input").
to("direct:doRoute").
to("log:output");
}
}
When I hit the web service URL, I am getting 500 Internal Server Error and in the logs (Tomcat) I see JAXRSUtils ERROR No message body writer has been found for class java.util.ArrayList, ContentType: application/json
Actually the debugger tells me that defining the URI by Java code is recognized, since I do hit the code inside the route.
I saw this error in many answers here, basically they say to add a Json provider and assign it to the CXF endpoint.
Seems to me like it is what I have done. But it does not work.
Any idea what I am doing wrong here?
As peeskillet said, it's because there isn't a list of providers registered under the name restJacksonProviderList. You can get the JndiContext like this and bind a list to it in the configure method of your routebuilder:
JndiContext registry = (JndiRegistry) context.getRegistry();
registry.bind("restJacksonProviderList", Arrays.asList(new JacksonJsonProvider()));
Edit after comments:
Change & for & in your cxfrs uri definition, & is only needed in xml.

Spring Webflow 2 and bookmarkable URLs

Currently due to the Post/Redirect/Get pattern all flow urls are something like <site_url>/flow_name?execution=? and input GET parameters are not preserved. Thus the users can't copy the url, or bookmark it.
Any suggestions how could this be done neatly ?
We can bookmark a SWF based application's URL by customising FlowHandlerAdapter of SWF API.
Here is a sample:
My SWF configuration file would have:
<bean id="flowController" class="org.springframework.webflow.mvc.servlet.FlowController">
<property name="flowHandlerAdapter" ref="customFlowHandlerAdapter" />
</bean>
<bean id="customFlowHandlerAdapter" class="com.xyz.CustomFlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor" />
<property name="flowUrlHandler" >
<bean class="com.xyz.CustomURLFlowHandler" />
</property>
</bean>
My CustomFlowHandlerAdapter would have:
public class CustomFlowHandlerAdapter extends FlowHandlerAdapter {
...
#Override
public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
FlowHandler flowHandler = (FlowHandler) handler;
checkAndPrepare(request, response, false);
String flowExecutionKey = this.getFlowUrlHandler()
.getFlowExecutionKey(request);
if (flowExecutionKey != null)
try {
ServletExternalContext context = createServletExternalContext(
request, response);
FlowExecutionResult result = this.getFlowExecutor().resumeExecution(
flowExecutionKey, context);
handleFlowExecutionResult(result, context, request, response,
flowHandler);
} catch(org.springframework.webflow.execution.repository.NoSuchFlowExecutionException ex){
response.sendRedirect(request.getRequestURI());
} catch(org.springframework.webflow.execution.repository.BadlyFormattedFlowExecutionKeyException ex){
response.sendRedirect(request.getRequestURI());
} catch (FlowException e) {
handleFlowException(e, request, response, flowHandler);
}
....
Here Iam catching NoSuchFlowExecutionException and am redirecting to the exact flow URL without any parameters. Here you can capture and re-include your parameters
Thus I am able to bookmark my URL from any state(always flow starts from first) also I will be able to send my own parameters if required.
you can always use and bookmark a link to one of your flow's start point.for instance you can do <site_url>/flow_name?personId=123&projectId=456 assuming you have two inputs to your flow personId and projectId. But you need to know the url (you will have to give it to the users), you cannot use the one on your address bar.
even if you want to do that, you won't be able to use and bookmark a link to a specific state in your flow (unless you add some logic to the start of your flow to direct you to a specific event depending on the value of an input).

Resources