Response to REST client from Camel CXFRS route? - apache-camel

I'm trying to create a response to a REST web service call in a CXFRS Camel route, but no matter what I do the response to the client is always the same 200, not 201. Here's my route:
<route id="front-end">
<from uri="cxfrs:bean:myService" />
<setBody>
<constant>Will do...</constant>
</setBody>
<setHeader headerName="CamelHttpResponseCode">
<constant>201</constant>
</setHeader>
<setHeader headerName="Content-Type">
<constant>more/blah</constant>
</setHeader>
</route>
The body is returned but the response code and content type are ignored. What am I doing wrong?
Thanks,
Matt

Basically camel-cxfrs overwrites any headers set in exchange when it converts the exchange to actual HTTP response
See here:
exchange.getOut().setHeaders(binding.bindResponseHeadersToCamelHeaders(response, exchange));
And this happens because DefaultCxfRsBinding expects a jaxrs Response as a parameter.
So to fix the issue you either override DefaultCxfRsBinding with custom one in order to copy headers from exchange.getIn().
<cxf:rsServer id="MyService" address="/myAddress">
<cxf:binding><bean class="MyCustomCxfRsBinding" /></cxf:binding>
<cxf:serviceBeans>
<ref bean="myResourceWithJSR311Annotations" />
</cxf:serviceBeans>
</cxf:rsServer>
Or make your camel route to return a jaxrs Response with headers instead of setting headers in the rout or in camel processors. Something
class HttpHeaderProcessor implements Processor
{
#Override
public void process(Exchange exchange) throws Exception
{
Message message = exchange.getIn();
Response response = convertToJaxRs(message);
exchange.getIn().setBody(response);
exchange.getIn().setHeader("Test", "Won't work unless DefaultCxfRsBinding is not replaced with a custom one");
}
private Response convertToJaxRs(Message message)
{
ResponseBuilder jaxrsResponseBuilder = Response.ok(message.getBody(), MediaType.APPLICATION_XML);
jaxrsResponseBuilder.header("header1", "you'll see this");
Response response = jaxrsResponseBuilder.build();
return response;
}
}
For your sample:
<route id="front-end">
<from uri="cxfrs:bean:myService" />
<setBody>
set it to Response.ok(your message).header(x, y).build()
</setBody>
You can also use a Service bean returning a jaxrs.Response with headders
<route id="front-end">
<from uri="cxfrs:bean:myService" />
<bean ref="myServiceImpl">

Related

Camel - ControlBus - Activate timer route

I have a timer route that is initially not started. I would like to activate it from another route. I am attempting to use the camel's controlbus EIP pattern.
// From my other route
.to("controlbus:route?routeId=fileConsumerRoute&action=start&async=true")
from("timer://camel-fileConsumerRoute?fixedRate=true&period=5s")
.routeId("fileConsumerRoute").noAutoStartup()
.log("Route is running");
I see in the logs aft the controlbus line is run that the route is being resumed
Context action: [resume]
but the timer still does not fire. I dont see the log line "Route is running"
How do I activate the timer endpoint with the controlbus?
Is there another EIP pattern or diffrent way to achieve activating the timer endpoint?
I realize I'm late to the party, but, you could use controlbus or something like this start stop routes. if there is some predicate some other boolean test.
example:
we test if some condition exists every period and perform operation on result of condition...
<!-- Token Database -->
<route autoStartup="true" id="core.predix.tkndependencyCheckerRoute">
<from id="_from2" uri="timer://tkndbAvailable?fixedRate=true&period=30000"/>
<process id="_process2" ref="tkndbAvailableProcessor"/>
</route>
public class TknDbAvailableProcessor implements Processor {
private static final Logger LOG = LoggerFactory.getLogger(TknDbAvailableProcessor.class);
private TKNDataService tknDataService = null;
#Override
public void process(Exchange exchange) throws Exception {
ServiceStatus status = exchange.getContext().getRouteStatus("core.fleet.EnrichmentRoute");
if (null == tknDataService) {
if (status.isStarted()) {
exchange.getContext().stopRoute("core.fleet.EnrichmentRoute");
}
} else {
if (tknDataService.isTKNDataServiceAvailable()) {
if (status.isStopped()) {
exchange.getContext().startRoute("core.fleet.EnrichmentRoute");
}
} else {
if (status.isStarted()) {
exchange.getContext().stopRoute("core.fleet.EnrichmentRoute");
}
}
}
}
public void setDataService(TKNDataService dataService) {
this.tknDataService = dataService;
}
}
If needing a master singleton route if needing redundancy - on diff nodes use a JGroups Cluster with ControlBus
<bean class="org.apache.camel.component.jgroups.JGroupsFilters"
factory-method="dropNonCoordinatorViews"
id="dropNonCoordinatorViews" scope="prototype"/>
<bean class="org.apache.camel.component.jgroups.JGroupsExpressions"
factory-method="delayIfContextNotStarted"
id="delayIfContextNotStarted" scope="prototype">
<argument value="5000"/>
</bean>
<route autoStartup="true" id="FleetMainClusterControlRoute">
<from id="_from2" uri="jgroups:mainCluster?enableViewMessages=true&channelProperties=etc/jgroups.xml"/>
<filter id="filterNonCoordinatorViews">
<method ref="dropNonCoordinatorViews"/>
<threads id="threads1" threadName="ControlThreads">
<delay id="_delay1">
<ref>delayIfContextNotStarted</ref>
<log id="logCR" loggingLevel="INFO" message="Starting Main Cluster consumer!!!!!!!!!!!!!!!!!!!!!"/>
<to id="_toCR" uri="controlbus:route?routeId=core.predix.tkndependencyCheckerRoute&action=start&async=true"/>
</delay>
</threads>
</filter>
</route>

Apache Camel Aggegation Strategy

While trying to use the Apache camel aggregation strategy, I am running into an issue as described below:
My application : Exposed a simple soap service that takes in the names of the departments in an organisation (finance, sales...). Inside my camel route, I route the request accordingly to the department specific routes. In these routes, I query a table, and get the employees for that department.
As a response for the soap service, I want to aggregate all the employees together and send.
Below is my camel Route:
<camelContext id="camel"
xmlns="http://camel.apache.org/schema/spring">
<camel:dataFormats>
<camel:jaxb contextPath="org.example.departments" id="jaxb"/>
</camel:dataFormats>
<route id="simple-route">
<!-- <camel:to id="unmarshallDeps" uri="bean:departmentProcessor"></camel:to> -->
<from id="_from1" uri="cxf:bean:departmentsEndpoint?dataFormat=PAYLOAD&loggingFeatureEnabled=true"/>
<camel:unmarshal id="_unmarshal1" ref="jaxb"/>
<camel:setHeader headerName="departments" id="_setHeader1">
<camel:method method="getDepartmentRoute" ref="depRouter"/>
</camel:setHeader>
<camel:recipientList id="_recipientList1">
<camel:header>departments</camel:header>
</camel:recipientList>
<camel:log id="_log1" message="Body in original cxf after aggregation ******** ${body} and exchange id is ${exchangeId}"/>
</route>
<camel:route id="_route1">
<camel:from id="_from2" uri="direct:finance"/>
<camel:to id="_to1" uri="mySqlComponent:select id,Location,Head,email,create_date from finance"/>
<camel:to id="_to2" pattern="InOut" uri="seda:departmentAggregator"/>
</camel:route>
<camel:route id="_route2">
<camel:from id="_from3" uri="direct:sales"/>
<camel:to id="_to3" uri="mySqlComponent:select id,Location,Head,email,create_date from sales"/>
<camel:to id="_to4" pattern="InOut" uri="seda:departmentAggregator"/>
</camel:route>
<camel:route id="_route3">
<camel:from id="_from4" uri="direct:hr"/>
<camel:to id="_to5" uri="mySqlComponent:select id,Location,Head,email,create_date from hr"/>
<camel:to id="_to6" pattern="InOut" uri="seda:departmentAggregator"/>
</camel:route>
<camel:route id="_route4">
<camel:from id="_from5" uri="seda:departmentAggregator"/>
<camel:aggregate completionSize="2" id="_aggregate1" strategyRef="myAggregator">
<camel:correlationExpression>
<camel:constant>Constant</camel:constant>
</camel:correlationExpression>
<camel:log message="Aggregated Body : ${body} and exchange id is ${exchangeId}"></camel:log>
<!-- <camel:marshal id="_marshal1" ref="jaxb"/> -->
<!-- <camel:setBody id="_setBody1">
<camel:simple>${body}</camel:simple>
</camel:setBody> -->
</camel:aggregate>
<camel:log id="_log4" message="Body outside aggregate : ${body} and exchange id is ${exchangeId}"/>
</camel:route>
</camelContext>
Now, what I notice is that the body when printed inside the aggregate is indeed an aggregated body, containing all the employees, but when I print the body outside the aggregate, it prints the latest exchange and not the aggregated exchange. Below is my aggregation strategy:
public Exchange aggregate(Exchange oldExchange, Exchange newExchange)
{
ArrayList<Map<String, Object>> depList = newExchange.getIn().getBody(ArrayList.class);
int depCount = depList.size();
System.out.println("Department Count is : " + depCount);
if (oldExchange == null) {
for (int i = 0; i < depCount; i++) {
Map<String, Object> row = (Map) depList.get(i);
Department newDepartment = new Department();
newDepartment.setLocation((String) row.get("Location"));
newDepartment.setHead((String) row.get("Head"));
newDepartment.setEmail((String) row.get("email"));
departments.getDepartment().add(newDepartment);
}
newExchange.getIn().setBody(departments);
return newExchange;
} else {
System.out.println("New Exchange: Department Count is : " + depCount);
departments = oldExchange.getIn().getBody(Departments.class);
System.out.println("Aggregate Department count : " + departments.getDepartment().size());
for (int j = 0; j < depCount; j++) {
Map<String, Object> row = (Map) newExchange.getIn().getBody(ArrayList.class).get(j);
Department newDepartment = new Department();
newDepartment.setLocation((String) row.get("Location"));
newDepartment.setHead((String) row.get("Head"));
newDepartment.setEmail((String) row.get("email"));
departments.getDepartment().add(newDepartment);
}
newExchange.getIn().setBody(departments);
oldExchange.getIn().setBody(departments);
}
// System.out.println("exchange is out capable ? : " +
// newExchange.getPattern().isOutCapable());
return oldExchange;
}
Aggregation Strategy is usually something that gets seamlessly embedded into EIPs; you don't have to call it manually, Camel does it for you.
recipientList is precisely one of those EIPs that accepts an aggregation strategy as a parameter.
See example in Camel doc.
Thus your "seda:departmentAggregator" route is in fact not needed (along with all the calls towards it in your "direct:[department]" routes). Instead, its content should be +/- included into the recipientList definition.

How to map the response for the REST request by Apache camel HTTP component

I have implemented following route with Apache camel XML mode.
<restConfiguration component="servlet" bindingMode="json" contextPath="/abc-esb/rest" port="8181">
<dataFormatProperty key="prettyPrint" value="true" />
<dataFormatProperty key="json.in.disableFeatures" value="FAIL_ON_EMPTY_BEANS,FAIL_ON_UNKNOWN_PROPERTIES" />
</restConfiguration>
<rest path="/service-http" consumes="application/json" produces="application/json">
<put type="com.abc.abcd.esb.models.UserServiceMapping" uri="/create">
<route>
<setHeader headerName="Authorization">
<simple>Basic YWRtaW46YWRtaW4=</simple>
</setHeader>
<setHeader headerName="CamelHttpMethod">
<constant>POST</constant>
</setHeader>
<setBody>
<simple>
{"SystemUserDetails":{"userName":"${body.objectDetails.userName}",
"password": "${body.objectDetails.password}"}}
</simple>
</setBody>
<to uri="http://192.168.1.20:8081/abcd/services/UserServiceRest/V1.0/users/create?bridgeEndpoint=true" />
</route>
</put>
</rest>
Functionality happens as expected. But camel returns HTTP 500 mentioning following error.
com.fasterxml.jackson.databind.JsonMappingException: No serializer
found for class
org.apache.camel.converter.stream.CachedOutputStream$WrappedInputStream
and no properties discovered to create BeanSerializer (to avoid
exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) at
com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:69)
at
com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:32)
at
com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:130)
When I analyze a tcp dump related to this scenario, I found this HTTP 500 occurred when trying to mapping the {"status" : "Success"} response from the external webservice.
Content of the UserServiceMapping class
private String serviceName;
private String serviceMethod;
private String mappedServiceObject;
private SystemUserDetails objectDetails;
// private StatusDetails statusDetails;
public String getMappedServiceObject() {
return mappedServiceObject;
}
public void setMappedServiceObject(String mappedServiceObject) {
this.mappedServiceObject = mappedServiceObject;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getServiceMethod() {
return serviceMethod;
}
public void setServiceMethod(String serviceMethod) {
this.serviceMethod = serviceMethod;
}
public SystemUserDetails getObjectDetails() {
return objectDetails;
}
public void setObjectDetails(SystemUserDetails objectDetails) {
this.objectDetails = objectDetails;
}
/*
* public StatusDetails getStatusDetails() { return statusDetails; }
*
* public void setStatusDetails(StatusDetails statusDetails) {
* this.statusDetails = statusDetails; }
*/
Request response flow of the scenario
How ever up to now I couldn't find an exact reason for this issue but I could by-pass the problem by setting Accept: application/xml other than Accept: application/json in my external REST client which makes the initial REST request. (REST service endpoint in other end configured to produce both json and xml)

How do I properly use Mocks when testing Camel routes?

I am trying to write a Camel test that checks to ensure a content based router is routing XML files correctly. Here are the enpoints and the route in my blueprint.xml:
<endpoint uri="activemq:queue:INPUTQUEUE" id="jms.queue.input" />
<endpoint uri="activemq:queue:QUEUE1" id="jms.queue.1" />
<endpoint uri="activemq:queue:QUEUE2" id="jms.queue.2" />
<route id="general-jms.to.specific-jms">
<from ref="jms.queue.input" />
<choice>
<when>
<xpath>//object-type = '1'</xpath>
<log message="Sending message to queue: QUEUE1" />
<to ref="jms.queue.1" />
</when>
<when>
<xpath>//object-type = '2'</xpath>
<log message="Sending message to queue: QUEUE2" />
<to ref="jms.queue.2" />
</when>
<otherwise>
<log message="No output was able to be determined based on the input." />
</otherwise>
</choice>
</route>
Right now, all I am trying to do is send in a sample source file that has an <object-type> of 1 and verify that is it routed to the correct queue (QUEUE1) and is the correct data (should just send the entire XML file to QUEUE1). Here is my test code:
public class RouteTest extends CamelBlueprintTestSupport {
#Override
protected String getBlueprintDescriptor() {
return "/OSGI-INF/blueprint/blueprint.xml";
}
#Override
public String isMockEndpointsAndSkip() {
return "activemq:queue:QUEUE1";
}
#Test
public void testQueue1Route() throws Exception {
getMockEndpoint("mock:activemq:queue:QUEUE1").expectedBodiesReceived(context.getTypeConverter().convertTo(String.class, new File("src/test/resources/queue1-test.xml")));
template.sendBody("activemq:queue:INPUTQUEUE", context.getTypeConverter().convertTo(String.class, new File("src/test/resources/queue1-test.xml")));
assertMockEndpointsSatisfied();
}
}
When I run this test, I see the log message that I put in the route definition that says it is sending it to QUEUE1, but the JUnit test fails with this error message: java.lang.AssertionError: mock://activemq:queue:QUEUE1 Received message count. Expected: <1> but was: <0>.
Can someone help me understand what I am doing wrong?
My understanding is that Camel will automatically mock the QUEUE1 endpoint since I overrode the isMockEndpointsAndSkip() and provided the QUEUE1 endpoint uri. I thought this meant I should be able to use that endpoint in the getMockEnpoint() method just by appending "mock:" to the beginning of the uri. Then I should have a mocked endpoint of which I can set expections on (i.e. that is has to have the input file).
If I am unclear on something please let me know and any help is greatly appreciated!
The solution is to use CamelTestSupport.replaceRouteFromWith.
This method is completely lacking any documentation, but it works for me when invoking it like this:
public class FooTest extends CamelTestSupport {
#Override
public void setUp() throws Exception {
replaceRouteFromWith("route-id", "direct:route-input-replaced");
super.setUp();
}
// other stuff ...
}
This will also prevent the starting of the original consumer of the from destination of the route. For example that means it isn't necessary anymore to have a activemq instance running when a route with an activemq consumer is to be tested.
After working on this for quite some time, the only solution I came up with that actaully worked for me was to use the createRouteBuilder() method in my test class to add a route to a mocked endpoint at the end of the route defined in my blueprint.xml file. Then I can check that mocked endpoint for my expectations. Below is my final code for the test class. The blueprint XML remained the same.
public class RouteTest extends CamelBlueprintTestSupport {
#Override
protected String getBlueprintDescriptor() {
return "/OSGI-INF/blueprint/blueprint.xml";
}
#Test
public void testQueue1Route() throws Exception {
getMockEndpoint("mock:QUEUE1").expectedBodiesReceived(context.getTypeConverter().convertTo(String.class, new File("src/test/resources/queue1-test.xml")));
template.sendBody("activemq:queue:INPUTQUEUE", context.getTypeConverter().convertTo(String.class, new File("src/test/resources/queue1-test.xml")));
assertMockEndpointsSatisfied();
}
#Test
public void testQueue2Route() throws Exception {
getMockEndpoint("mock:QUEUE2").expectedBodiesReceived(context.getTypeConverter().convertTo(String.class, new File("src/test/resources/queue2-test.xml")));
template.sendBody("activemq:queue:INPUTQUEUE", context.getTypeConverter().convertTo(String.class, new File("src/test/resources/queue2-test.xml")));
assertMockEndpointsSatisfied();
}
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
public void configure() throws Exception {
from("activemq:queue:QUEUE1").to("mock:QUEUE1");
from("activemq:queue:QUEUE2").to("mock:QUEUE2");
}
};
}
}
While this solution works, I don't fully understand why I can't just use isMockEndpointsAndSkip() instead of having to manually define a new route at the end of my existing blueprint.xml route. It is my understanding that defining isMockEndpointsAndSkip() with return "*"; will inject mocked endpoints for all of your endpoints defined in your blueprint.xml file. Then you can check for your expections on those mocked endpoints. But for some reason, this does not work for me.

Can anyone point me to a working example Camel route using a cxfrs client/producer?

I am having trouble getting my Camel route to successfully POST a message to an existing RESTful web service. I have tried all the examples in the camel cxf package but none of them produce a web service call (they are consumers). I would love to find a working example for this so I can step through the CxfRsProducer execution to hopefully discover why my route is not posting correctly to the web service.
Here is my RouteBuilder's configuration:
public void configure()
{
//errorHandler(deadLetterChannel(String.format("file:%s/../errors", sourceFolder)).useOriginalMessage().retriesExhaustedLogLevel(LoggingLevel.DEBUG));
errorHandler(loggingErrorHandler());
/*
* JMS to WS route for some of the events broadcast to the jms topic
*/
Endpoint eventTopic = getContext().getEndpoint(String.format("activemq:topic:%s?clientId=%s&durableSubscriptionName=%s", eventTopicName, durableClientId, durableSubscriptionName));
from(eventTopic) // listening on the jms topic
.process(eventProcessor) // translate event into a Notifications object (JAX-RS annotated class)
.choice() // gracefully end the route if there is no translator for the event type
.when(header("hasTranslator").isEqualTo(false)).stop() // no translator stops the route
.otherwise() // send the notification to the web service
.to("cxfrs:bean:rsClient");
}
Here is the rsClientBean:
<cxf:rsClient id="rsClient"
address="http://localhost/ws"
serviceClass="com.foo.notifications.NotificationsResource"
loggingFeatureEnabled="true" />
I'm pretty new to REST and I don't really understand what the serviceClass does for the rsClient because it looks to me like the definition of the exposed web service on the server.
The NotificationsResource class:
#Path("/notifications/")
public class NotificationManagerResource
{
// NOTE: The instance member variables will not be available to the
// Camel Exchange. They must be used as method parameters for them to
// be made available
#Context
private UriInfo uriInfo;
public NotificationManagerResource()
{
}
#POST
public Response postNotification(Notifications notifications)
{
return null;
}
}
The processor creates a Notifications object to put in the exechange message body:
private class EventProcessor implements Processor
{
#Override
public void process(Exchange exchange) throws Exception
{
Message in = exchange.getIn();
IEvent event = (IEvent) in.getBody();
Notifications notifications = null;
in.setHeader("hasTranslator", false);
in.setHeader("Content-Type", "application/xml");
in.setHeader(CxfConstants.CAMEL_CXF_RS_USING_HTTP_API, false);
// I've tried using the HTTP API as 'true', and that results in a 405 error instead of the null ptr.
INotificationTranslator translator = findTranslator(event);
if (translator != null)
{
notifications = translator.build(event);
in.setHeader("hasTranslator", true);
}
// replace the IEvent in the body with the translation
in.setBody(notifications);
exchange.setOut(in);
}
}
The Notifications class is annotated with JAXB for serialization
#XmlRootElement(name = "ArrayOfnotification")
#XmlType
public class Notifications
{
private List<Notification> notifications = new ArrayList<>();
#XmlElement(name="notification")
public List<Notification> getNotifications()
{
return notifications;
}
public void setNotifications(List<Notification> notifications)
{
this.notifications = notifications;
}
public void addNotification(Notification notification)
{
this.notifications.add(notification);
}
}
The error that is returned from the web service:
Exchange
---------------------------------------------------------------------------------------------------------------------------------------
Exchange[
Id ID-PWY-EHANSEN-01-62376-1407805689371-0-50
ExchangePattern InOnly
Headers {breadcrumbId=ID:EHANSEN-01-62388-1407805714469-3:1:1:1:47, CamelCxfRsUsingHttpAPI=false, CamelRedelivered=false, CamelRedeliveryCounter=0, Content-Type=application/xml, hasTranslator=true, JMSCorrelationID=null, JMSDeliveryMode=2, JMSDestination=topic://SysManEvents, JMSExpiration=1407805812574, JMSMessageID=ID:EHANSEN-01-62388-1407805714469-3:1:1:1:47, JMSPriority=4, JMSRedelivered=false, JMSReplyTo=null, JMSTimestamp=1407805782574, JMSType=null, JMSXGroupID=null, JMSXUserID=null}
BodyType com.ehansen.notification.types.v2.Notifications
Body <?xml version="1.0" encoding="UTF-8"?><ArrayOfnotification xmlns="http://schemas.datacontract.org/2004/07/ehansen.Notifications.Dto"> <notification> <causeType>EVENT_NAME</causeType> <causeValue>DeviceEvent</causeValue> <details> <notificationDetail> <name>BUSY</name> <value>false</value> <unit>boolean</unit> </notificationDetail> <notificationDetail> <name>DESCRIPTION</name> <value>Software Computer UPS Unit</value> <unit>name</unit> </notificationDetail> <notificationDetail> <name>DEVICE_NUMBER</name> <value>1</value> <unit>number</unit> </notificationDetail> <notificationDetail> <name>DEVICE_SUB_TYPE</name> <value>1</value> <unit>type</unit> </notificationDetail> <notificationDetail> <name>DEVICE_TYPE</name> <value>UPS</value> <unit>type</unit> </notificationDetail> <notificationDetail> <name>FAULTED</name> <value>false</value> <unit>boolean</unit> </notificationDetail> <notificationDetail> <name>RESPONDING</name> <value>true</value> <unit>boolean</unit> </notificationDetail> <notificationDetail> <name>STORAGE_UNIT_NUMBER</name> <value>1</value> <unit>number</unit> </notificationDetail> </details> <sourceType>DEVICE_ID</sourceType> <sourceValue>1:UPS:1</sourceValue> <time>2014-08-11T18:09:42.571-07:00</time> </notification></ArrayOfnotification>
]
Stacktrace
---------------------------------------------------------------------------------------------------------------------------------------
java.lang.NullPointerException
at java.lang.Class.searchMethods(Class.java:2670)
at java.lang.Class.getMethod0(Class.java:2694)
at java.lang.Class.getMethod(Class.java:1622)
at org.apache.camel.component.cxf.jaxrs.CxfRsProducer.findRightMethod(CxfRsProducer.java:266)
at org.apache.camel.component.cxf.jaxrs.CxfRsProducer.invokeProxyClient(CxfRsProducer.java:222)
at org.apache.camel.component.cxf.jaxrs.CxfRsProducer.process(CxfRsProducer.java:90)
at org.apache.camel.util.AsyncProcessorConverterHelper$ProcessorToAsyncProcessorBridge.process(AsyncProcessorConverterHelper.java:61)
at org.apache.camel.processor.SendProcessor$2.doInAsyncProducer(SendProcessor.java:143)
at org.apache.camel.impl.ProducerCache.doInAsyncProducer(ProducerCache.java:307)
at org.apache.camel.processor.SendProcessor.process(SendProcessor.java:138)
It is the methodName parameter in the following method from CxfRsProducer class that is null... so I assume there is something about my rsClient that is not configured correctly.
private Method findRightMethod(List<Class<?>> resourceClasses, String methodName, Class<?>[] parameterTypes) throws NoSuchMethodException {
Method answer = null;
for (Class<?> clazz : resourceClasses) {
try {
answer = clazz.getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException ex) {
// keep looking
} catch (SecurityException ex) {
// keep looking
}
if (answer != null) {
return answer;
}
}
throw new NoSuchMethodException("Cannot find method with name: " + methodName + " having parameters: " + arrayToString(parameterTypes));
}
Thanks for any help anyone can provide!
The serviceClass is a JAX-RS annotated Java class that defines the operations of a REST web service.
When configuring a CXF REST client, you must specify and address and a serviceClass. By inspecting the annotations found on the serviceClass, the CXF client proxy knows which REST operations are supposed to be available on the REST service published on the specified address.
So in your case, you need to add in.setHeader.setHeader(CxfConstants.OPERATION_NAME, "postNotification"); to the EventProcessor to tell camel which method of the service class you want to call.
Alright then. Here is the camel configuration xml file.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://w3.org/2001/XMLSchema-instance"
xmlns:cxf="http://camel.apache.org/schema/cxf"
xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://camel.apache.org/schema/cxf
http://camel.apache.org/schema/cxf/camel-cxf.xsd
http://cxf.apache.org/jaxrs
http://cxf.apache.org/schemas/jaxrs.xsd
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd
>
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<bean id="helloBean" class="com.examples.camel.cxf.rest.resource.HelloWorldResource" />
<cxf:rsServer id="helloServer" address="/helloapp" loggingFeatureEnabled="true">
<cxf:serviceBeans>
<ref bean="helloBean" />
</cxf:serviceBeans>
<cxf:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
</cxf:providers>
</cxf:rsServer>
<camelContext id="context" xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="cxfrs:bean:helloServer />
<log message="Processing CXF route....http method ${header.CamelHttpMethod}" />
<log message="Processing CXF route....path is ${header.CamelHttpPath}" />
<log message="Processing CXF route....body is ${body}" />
<choice>
<when>
<simple>${header.operationName} == 'sayHello'</simple>
<to uri="direct:invokeSayHello" />
</when>
<when>
<simple>${header.operationName} == 'greet'</simple>
<to uri="direct:invokeGreet" />
</when>
</choice>
</route>
<route id="invokeSayHello">
<from uri="direct:invokeSayHello" />
<bean ref="helloBean" method="sayHello" />
</route>
<route id="invokeGreet">
<from uri="direct:invokeGreet" />
<bean ref="helloBean" method="greet" />
</route>
</camelContext>
</beans>
The actual resource implementation class looks like below.
package com.examples.camel.cxf.rest.resource;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
public class HelloWorldResource implements HelloWorldIntf
{
public Response greet() {
return Response.status(Status.OK).
entity("Hi There!!").
build();
}
public Response sayHello(String input) {
Hello hello = new Hello();
hello.setHello("Hello");
hello.setName("Default User");
if(input != null)
hello.setName(input);
return Response.
status(Status.OK).
entity(hello).
build();
}
}
class Hello {
private String hello;
private String name;
public String getHello() { return hello; }
public void setHello(String hello) { this.hello = hello; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
You don't need , and cxf:rsServer> to be provided.
The tag alone will suffice to handle a web service request and invoke a route.
In case you have both and the then invoking the former will not help you in executing a route. For a route to get invoked, the request must reach to the address published by .
Hope this helps.

Resources