Apache Camel dataFormat - spring

Im using Camel to handle requests from a webservice and route those request to somewhere else (i will send them to RabbitMQ, but this is not relevant for my question).
Im using the standard Dataformat in camel-cxf endpoint which is POJO, but when i receive the packet in the camel route, im getting something that im not expecting.
Let me show you what i have right now, and i hope someone can point me in the right direction :)
Im using SOAPUI to send the request to the webservice:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:es:tf:mm:types">
<soapenv:Header/>
<soapenv:Body>
<urn:PromoEvent>
<urn:id>601234567</urn:id>
<urn:text>qwerty</urn:text>
<urn:date>01012001</urn:date>
</urn:PromoEvent>
</soapenv:Body>
</soapenv:Envelope>
Now, I have the following cxf endpoint bean definition in my camel-context.xml file, which by default, uses the POJO dataformat:
<cxf:cxfEndpoint id="Promo"
address="/Promo"
serviceClass="es.tf.mm.mmPortType"/>
And finally, i defined a camel route where im printing some logs and following you can see the results:
<route id="fromMySOAPws" autoStartup="true">
<from uri="cxf:bean:Promo"/>
<log message="Body: ${body}"/>
<log message="Body in: ${in.body}"/>
<log message="Body 0: ${in.body[0]}"/>
<log message="Body 1: ${in.body[1}"/>
<log message="Body 2: ${in.body[2]}"/>
<log message="Body 3: ${in.body[3]}"/>
<transform>
<simple>${in.body[0]}</simple>
</transform>
<unmarshal ref="jaxbWsDf"/>
<process ref="PromoProcessRequest"/>
<to uri="rabbitmq://rabbitmqhostname:5672/exchange_name?connectionFactory=#customConnectionFactory&autoDelete=false"/>
<bean ref="PromoResponse" method="generateResponse(0,"event published in rabbitmq")"/>
</route>
Here you can see the PromoProcessRequest Java class:
public class PromoProcessRequest implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
EventPromoType request = (EventPromoType)exchange.getIn().getBody();
Map<String, Object> eventFields = new LinkedHashMap<String, Object>();
eventFields.put("id", request.getId());
eventFields.put("text", request.getText());
eventFields.put("date", request.getDate());
exchange.getOut().setBody(eventFields);
}
}
Results:
Body: qwerty
Body in: qwerty
Body 0: javax.xml.ws.Holder#6ecf43c8
Body 1: qwerty
Body 2: 1012001
Body 3:
Thank you all in advance :)
#EDIT:
As Souciance correctly asked, what is unexpected for me, is to have just one of the fields (text:'qwerty') as the whole {body} of the request.
Since im using the standard POJO dataFormat, i need to call a process before Camel sends the message to rabbit and create a JSON with all the fields.
Im updating the code to show you the whole Camel route and also to include the process method which is where im having the issues, since im trying to cast the body of the request.
Hope this clarifies everything a little bit, but ofc, feel free to ask for more details if needed.
#EDIT2:
As fiw asked, here is the jaxbWsDf definition which is declared inside the camelContext tag:
<dataFormats>
<jaxb id="jaxbWsDf" prettyPrint="true" contextPath="es.tf.mm.types"/>
</dataFormats>
Also i would like to replicate here out my answer to the fiw second comment. When i compile the wsdl, in the auto-generated Java class, i cant see the #XmlRootElement and i think this is because the wsdl schema is declared in a external .xsd file. Am i right? Any other idea of why that XmlRootElement does not appear after compiling my webservice?
Thanks again!

Related

Testing apache camel routes

I have an application build using Apache Camel 2.15.3. And I'm wiring the routes using spring-xml for dependency injection. I'm having a hard time understanding how to write automatic test for my routes. For example I might have the routes:
<onException id="Exception">
<exception>java.lang.Exception</exception>
<handled>
<constant>true</constant>
</handled>
<to uri="direct:fear"/>
</onException>
<route id="happyStory">
<from uri="direct:inTheBeginning"/>
<to uri="bean:enchantedKingdom?method=warn" />
<to uri="bean:fluffykins" />
</route>
<route id="scaryStory">
<from uri="direct:fear"/>
<onException>
<exception>java.lang.Exception</exception>
<handled>
<constant>true</constant>
</handled>
</onException>
<to uri="bean:monster"/>
<choice>
<when>
<simple>${header.succesfullywarned}</simple>
<to uri="bean:enchantedKingdom?method=hide"/>
</when>
<otherwise>
<to uri="bean:enchantedKingdom?method=panic" />
</otherwise>
</choice>
</route>
And I wan't to be able to say that when the bean method warn is called then the header "succesfullywarned" should be set in the message and then when the bean fluffykins is called there should be a exception that causes the message to get sent to "scaryStory" and in this case I wan't to assert that the bean method hide is called.
This is roughly my test class setup:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration({"/META-INF/spring/route-
stories.xml","/META-INF/spring/beans.xml"})
#MockEndpointsAndSkip("(bean:fluffykins|bean:monster|bean:enchantedKingdom?method=warn|bean:enchantedKingdom?method=hide|bean:enchantedKingdom?method=panic)")
public class StoryHappyRouteTest extends CamelSpringTestSupport {
private String url = "direct:inTheBeginning";
#Autowired
private ApplicationContext applicationContext;
#Override
protected AbstractApplicationContext createApplicationContext() {
return (AbstractApplicationContext)applicationContext;
}
#Test
public void test(){
MockEndpoint warn = getMockEndpoint("mock:bean:enchantedKingdom?method=warn");
MockEndpoint fluffy = getMockEndpoint("mock:bean:fluffykins");
MockEndpoint monster = getMockEndpoint("mock:bean:monster");
MockEndpoint hide = getMockEndpoint("mock:bean:enchantedKingdom?method=hide");
MockEndpoint panic = getMockEndpoint("mock:bean:enchantedKingdom?method=panic");
fluffy.whenAnyExchangeReceived(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println("Bunny!");
throw new NullPointerException();
}
});
template.sendBody(url,"");
warn.assertExchangeReceived(0);
fluffy.assertExchangeReceived(0);
monster.assertExchangeReceived(0);
panic.assertExchangeReceived(0);
}
}
I've read the chapter on testing in the first edition of Camel in action and look around in the manual (http://camel.apache.org/testing.html) but I don't understand how to apply it in my situation. In the above test everything works except where I have bean with multiple methods so I have an uri that contains "?method=methodname", and for some reason this makes it not work. I don't get an error or but the mock is not used and instead the actual beans are called.
Is it not possible to do what I wan't to do? I can change the test setup in any way, but it is a given that the routes and the beans are defined in the spring-xml files.
I've taught about mocking the beans themselves and not the endpoints but the only way I can think of doing that is creating a "imposter-beans.xml" file where all the beans are defined, that points to stubbclasses that extend every class used in the routes. But that feels like an elaborate and wrong approach.
You can write code what to do when the mock receives a message. This is covered in the book in section 6.2.6, where you can use methods like whenAnyExchangeReceived or whenExchangeReceived, and in those inlined processors you can set the header, or throw an exception etc. See for example listing 6.9.

Apache Camel -- Reference type constants in Spring DSL

I'm trying to define a publish operation to Hazelcast topic using Spring DSL
<from uri="direct:inbound" />
<onCompletion>
<log message="onCompletion:- ${body}" />
<setHeader headerName="${type:org.apache.camel.component.hazelcast.HazelcastConstants.OPERATION}">
<simple>${type:org.apache.camel.component.hazelcast.HazelcastConstants.PUBLISH_OPERATION}</simple>
</setHeader>
<to uri="hazelcast:topic:foo" />
</onCompletion>
<log message="${body}" />
The above route works, but I have to use long SIMPLE scripts like "${type:org.apache.camel.component.hazelcast.HazelcastConstants.OPERATION}" to ref a constant value. Is there any simpler or short form for this?
I tried to define a spring bean for HazelcastConstants class and ref it through SIMPLE scripts as below but it's not working with MethodNotFoundException "Method with name: OPERATION not found on bean"
<bean id="hazelcastConstants" class="org.apache.camel.component.hazelcast.HazelcastConstants" />
... ...
<simple>${bean:hazelcastConstants.OPERATION}</simple>
Your bean workaround would work, if you defined a bean contained a method returning the constant in question, e.g.:
public class ContantRetriever() {
public String getHazelCastOperation() {
return org.apache.camel.component.hazelcast.HazelcastConstants.PUBLISH_OPERATION;
}
}
Your Spring context:
<bean id="hazelcastConstants" class="yourpackage.ContantRetriever"/>
<simple>${bean:hazelcastConstants.getHazelCastOperation}</simple>
If that is no good for you, I am afraid you are stuck with the long form of accessing constants.

Camel setBody using Spring configuration

I have built a Java Camel Timer-JMS route using:
context.addRoutes(new RouteBuilder() {
public void configure() {
from("timer:foo?period=1s").setBody(body().
append("Message at ${date:now:yyyy-MM-dd HH:mm:ss}")).to("jms:queue:activemq/queue/TestQueue");
}
});
Now I need to turn it to Spring.
<camel:route>
<camel:from uri="timer:foo?period=1s" />
<camel:to uri="jms:queue:activemq/queue/TestQueue" />
</camel:route>
I'm missing the equivalent in Spring XML configuration of the expression:
setBody(body().append("Message at ${date:now:yyyy-MM-dd HH:mm:ss}")
In Spring you can use the simple language, to build such messages. In fact you can also do this in Java, it the same.
<setBody>
<simple>${body}Message at ${date:now:yyyy-MM-dd HH:mm:ss}</simple>
</setBody>
Mind that the timer sends an empty/null body. So you may see "null" in the message.
About simple see: http://camel.apache.org/simple

Spring Configuration of Custom Apache Camel Data Format

I am using Apache Camel 2.9.2 and Spring 3.0.6.RELEASE. I am trying to use a custom DataFormat to marshal and unmarshal Camel messages. I want to configure my custom DataFormat into one of my routes using Spring.
Apache Camel's documentation states that in order to hook up my custom Data Format to a route in Spring I simply need to declare my custom DataFormat as a bean and reference it inside of my Spring route like so:
<marshal>
<custom ref="myCustomDataFormat"/>
</marshal>
http://camel.apache.org/custom-dataformat.html
So I have the following setup:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
">
<bean id="myCustomDataFormat" class="com.test.CustomDataFormat"/>
<!-- Camel Context -->
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="file:C:/test?initialDelay=4000&delay=1000"/>
<marshal>
<custom ref="myCustomDataFormat"/>
</marshal>
<to uri="file:C:/test2"/>
</route>
</camelContext>
</beans>
But when I try to start Camel, I get the following nasty error:
org.springframework.beans.ConversionNotSupportedException: Failed to convert value of type 'com.test.CustomDataFormat' to required type 'org.apache.camel.model.DataFormatDefinition'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [com.test.CustomDataFormat] to required type [org.apache.camel.model.DataFormatDefinition]: no matching editors or conversion strategy found
My Data Format is defined as follows:
package com.test;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.camel.Exchange;
import org.apache.camel.spi.DataFormat;
public class CustomDataFormat implements DataFormat {
/* (non-Javadoc)
* #see org.apache.camel.spi.DataFormat#marshal(org.apache.camel.Exchange, java.lang.Object, java.io.OutputStream)
*/
#Override
public void marshal(Exchange exchange, Object graph, OutputStream stream)
throws Exception {
System.out.println("Marshal");
byte[] bytes = exchange.getContext().getTypeConverter().mandatoryConvertTo(byte[].class, graph);
stream.write(bytes);
}
/* (non-Javadoc)
* #see org.apache.camel.spi.DataFormat#unmarshal(org.apache.camel.Exchange, java.io.InputStream)
*/
#Override
public Object unmarshal(Exchange exchange, InputStream stream)
throws Exception {
System.out.println("Unmarshal");
byte[] bytes = exchange.getContext().getTypeConverter().mandatoryConvertTo(byte[].class, stream);
return bytes;
}
}
I know that my CustomDataFormat implementation is correct because I created the following test route in Java and it worked flawlessly
package com.test;
import org.apache.camel.spring.SpringRouteBuilder;
public class TestFormatRoute extends SpringRouteBuilder {
/* (non-Javadoc)
* #see org.apache.camel.builder.RouteBuilder#configure()
*/
#Override
public void configure() throws Exception {
from("file:C:/test?initialDelay=4000&delay=1000").unmarshal(new CustomDataFormat()).to("file:C:/test2");
}
}
What am I missing?
Thanks
Update
After letting Camel completely start up after receiving this error I found to my disbelief that my custom data format actually does work in the route that I created. I'm not sure what process is attempting to parse my custom data format and failing but it is apparently not the same process parsing the data format to put into my route.
This solves the functional requirement of the data format, but it does not explain why I am receiving this error.
I have also confirmed that it was not the name of my data format (CustomDataFormat) that was causing the issue. Renaming my DataFormat to a unique name (MerlinDataFormat) did not fix the error.
I still would like to know why I am receiving this error since large blocks of ugly red errors in my console and log files aren't exactly appealing.
Thanks again.
It turned out to be a pretty simple solution (and one that I admit should have been easy to see). There are actually two ways to go about solving this issue, one of them using only spring and one of them requiring an additional java class.
Solution 1
Create a new class extending DataFormatDefinition which has the same properties as your custom DataFormat. Override the configureDataFormat() method to set all of the properties of the underlying DataFormat. Add constructor(s) to set the underlying DataFormat as an instance of your CustomDataFormat. Now you should be able to create an instance of your DataFormatDefinition in spring and reference it when marshaling or unmarshaling.
Solution 2 (Quick & Dirty)
In spring, create a new DataFormatDefinition bean and set it's dataFormat property as a reference to your DataFormat spring bean. Now you should be able to reference your DataFormatDefinition bean when marshaling or unmarshaling.
Not really sure what's wrong with your example, it seems just fine. Can you post your code for the data format? Are you implementing org.apache.camel.spi.DataFormat correctly?
I just set up this example with Camel 2.9.2 and it works like a charm. The Custom data format is the one from Camel documentation/source code.
<bean id="mySweetDf" class="com.example.MySweetDf"/>
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="file:C:/temp/test?initialDelay=4000&delay=1000"/>
<marshal>
<custom ref="mySweetDf"/>
</marshal>
<convertBodyTo type="java.lang.String"/>
<to uri="file:C:/temp/test2"/>
</route>
</camelContext>
data format java file:
package com.example;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.camel.Exchange;
import org.apache.camel.spi.DataFormat;
public class MySweetDf implements DataFormat {
public void marshal(Exchange exchange, Object graph, OutputStream stream) throws Exception {
byte[] bytes = exchange.getContext().getTypeConverter().mandatoryConvertTo(byte[].class, graph);
String body = reverseBytes(bytes);
stream.write(body.getBytes());
}
public Object unmarshal(Exchange exchange, InputStream stream) throws Exception {
byte[] bytes = exchange.getContext().getTypeConverter().mandatoryConvertTo(byte[].class, stream);
String body = reverseBytes(bytes);
return body;
}
private String reverseBytes(byte[] data) {
StringBuilder sb = new StringBuilder(data.length);
for (int i = data.length - 1; i >= 0; i--) {
char ch = (char) data[i];
sb.append(ch);
}
return sb.toString();
}
}
UPDATE
Just tried you code. Seems to work as well. Created a fresh camel 2.9.2 project via mvn archetype 168: remote -> org.apache.camel.archetypes:camel-archetype-spring (Creates a new Camel project with added Spring DSL support.). This does only include camel-core and camel-spring dependencies, nothing else.
Then replaced camel-context.xml with your xml and added your data format code in the java directory. A run with "mvn camel:run" copied the file and printed "marshal" in the log.
[pache.camel.spring.Main.main()] SpringCamelContext INFO Route: route1 started and consuming from: Endpoint[file://C:/test?delay=1000&initialDelay=4000]
[pache.camel.spring.Main.main()] SpringCamelContext INFO Total 1 routes, of which 1 is started.
[pache.camel.spring.Main.main()] SpringCamelContext INFO Apache Camel 2.9.2 (CamelContext: camel-1) started in 0.808 seconds
Marshal
Are you sure you have all dependencies setup correctly and not some .jar file that messes things up with Data formats?
UPDATE2
Okay, I think I have an idea what it is:
http://camel.apache.org/maven/current/camel-core/apidocs/org/apache/camel/model/dataformat/CustomDataFormat.html
Camel already have a class named as your data format. You should try rename it to something else. CustomDataFormat extends org.apache.camel.model.DataFormatDefinition which is referred to in your error. Java should handle this, since it's two different namespaces, but there might be some issue in your project setup that causes this conflict. Try to rename the data format and see if that solves the problem.
I too was facing the same issue with camel 2.10.0. If you provide the ref with an instance of type org.apache.camel.model.DataFormatDefinition everything works fine!! I can see two classes for xmljson conversion --> XmlJsonDataFormat implementing both DataFormat and DataFormatDefinition.
I solved the same issue that I too was facing.
Implemented a class extending DataFormatDefintion - which in it's configureDataFormat method sets injectable properties for the class that extends DataFormat (in your case this is CustomDataFormat).
I used XmlJson conversion as a template to solve.

Bypassing ViewResolver using #ResponseBody & Method Converters for JSON and XML only works for JSON

I'm creating a RESTful API that returns JSON or XML depending on the Accept header (application/json vs text/xml). I have this working fine for JSON but can't seem to get it working when for XML. I am testing using the Poster plugin client for Firefox.
I was under the impression that I just needed to add the Jackson and JAXB libraries to the app's classpath. Again, it works for JSON but not XML.
Originally I was getting 406 error when sending the Accept "text/xml" header. Then I added #XmlRootElement(name="contact") to my entity and now I'm getting a 500 error. Should I need to put #XmlRootElement on every entity?
Although the response is a 500 error, I don't see any errors reported in the console. I'm testing in Eclipse running Tomcat 7. Shouldn't i see some error in the console when i receive a 500 error?
My "mvc-dispatcher-servlet.xml" has <mvc:annotation-driven />
Here's the relevant code from my controller:
#Controller
#RequestMapping("/contacts")
public class ContactsController {
#Autowired
ContactsService contactsService;
#RequestMapping(value="/{id}",
method=RequestMethod.GET,
headers = {"Accept=application/json, text/xml"})
public #ResponseBody Contact getContact(#PathVariable("id") int id) {
Contact queryContact = new Contact(id);
Contact result = contactsService.getContact(queryContact);
return result;
}
}
The "mvc-dispatcher-servlet.xml" is really simple. Do I need anything other than:
<context:component-scan base-package="contactsapp.web.controller" />
<mvc:annotation-driven />
<mvc:resources mapping="/resources/**" location="/resources/"/>
I'm using Spring 3.1 and the following:
<dependency org="com.sun.xml.bind" name="jaxb-impl" rev="2.2.5-b10" conf="runtime->default"/>
<dependency org="org.codehaus.jackson" name="jackson-mapper-asl" rev="1.7.1" conf="runtime->default"/>
You should put
#XmlRootElement on Contact class to tell jackson how to parse.
It turns out I had it configured correctly. Once I enabled more verbose logging I realized I had circular dependencies in my entity classes and had to add #XmlTransient on those fields

Resources