Spring Configuration of Custom Apache Camel Data Format - spring

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.

Related

Unable to post message to IBM MQ Queue

I am trying to connect to IBM MQ and post message. getting below exception but the same code works in development environment.
org.springframework.jms.connection.SingleConnectionFactory.createSession(SingleConnectionFactory.java:437)
com.ibm.mq.jms.MQQueueConnection.createSession(MQQueueConnection.java:154) ~[com.ibm.mq.allclient-9.0.4.0.jar:9.0.4.0 - p904-L171030.1
com.ibm.mq.jms.MQQueueConnection.createQueueSession(MQQueueConnection.java:130) ~[com.ibm.mq.allclient-9.0.4.0.jar:9.0.4.0 - p904-L171030.1]
com.ibm.mq.jms.MQQueueSession.<init>(MQQueueSession.java:58) ~[com.ibm.mq.allclient-9.0.4.0.jar:9.0.4.0 - p904-L171030.1]
com.ibm.mq.jms.MQSession.<init>(MQSession.java:262) ~[com.ibm.mq.allclient-9.0.4.0.jar:9.0.4.0 - p904-L171030.1]\\n\
com.ibm.mq.jms.MQSession.getTransacted(MQSession.java:876) ~[com.ibm.mq.allclient-9.0.4.0.jar:9.0.4.0 - p904-L171030.1]
Caused by: java.lang.NullPointerException
Basically, the session is becoming null in other regions when we are trying to post a message from Spring JMS Template to IBM MQ.
from java,
public void sendNotificationsMessageToQueue(String jsonMessage)
{
jmsTemplate(Queue, new MessageCreator(){
#Override public Message createMessage(Session session) throws JMSException
{
return session.createTextMessage(jsonMessage);
} });
From XML:
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <property name="connectionFactory" ref="ibmConnectionFactory" />
<property name="defaultDestination" ref="exQueue" />
</bean>
Read this as a comment as opposed to an answer. It's a long comment and needed a little formatting.
Your XML should also have a ibmConnectionFactory bean defined which will point at the IBM MQ Factory classes. What is in there is going to be important. You need to share that, as it looks like that that is where your deployed code is failing.
I am guessing that this is not a Spring-Boot project, and you are not making use of the mq-jms-spring-boot-starter.
If you were then that you are defining jmsTemplate and ibmConnectionFactory beans is a bit strange. If the IBM MQ classes are the only Messaging classes defined in your spring-boot project then spring will automatically bind the IBM MQ connection classes to the default jmsTemplate bean.
If you have other Messaging classes defined as dependencies in your project, then your beans are ignoring them, which indicates that they are not needed and hence shouldn't be there.
If, however, you are using Spring-Boot and mq-jms-spring-boot-starter, then I find it easier to define the beans in Java code than XML, although you only need to if you are changing the defaults.
import com.ibm.mq.jms.MQConnectionFactory;
import com.ibm.mq.samples.jms.spring.globals.handlers.OurDestinationResolver;
import com.ibm.mq.samples.jms.spring.globals.handlers.OurMessageConverter;
import com.ibm.mq.spring.boot.MQConfigurationProperties;
import com.ibm.mq.spring.boot.MQConnectionFactoryFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.core.JmsTemplate;
import javax.jms.JMSException;
#Configuration
public class MQConfiguration114 {
protected final Log logger = LogFactory.getLog(getClass());
#Bean
public MQConnectionFactory mqConnectionFactory() throws JMSException {
MQConfigurationProperties properties = new MQConfigurationProperties();
// Properties will be a mix of defaults, and those found in application.properties
// under ibm.mq
// Here we can override any of the properties should we need to
MQConnectionFactoryFactory mqcff = new MQConnectionFactoryFactory(properties,null);
MQConnectionFactory mqcf = mqcff.createConnectionFactory(MQConnectionFactory.class);
return mqcf;
}
#Bean("myJmsTemplate")
public JmsTemplate myJmsTemplate() throws JMSException {
JmsTemplate jmsTemplate = new JmsTemplate(mqConnectionFactory());
// Any other customisations necessary go here.
// ...
return jmsTemplate;
}
}
It worked after removing spring jms template and written new class for obtaining IBM MQ connection.
We had the same problem.
It was not related to reading configurations but to the dd-java-agent.jar. The version 0.82.0 was causing the issue. We moved to 0.84.0 and problem solved!

what is the usage of new keyword while using java configuration in spring

I have a question around the usage of new keyword being used when using java configuration in spring. What is the need of using new keyword
Refer below mentioned example:
Code implemented using Java Config
#Configuration
public class HelloWorldConfig {
#Bean
public HelloWorld helloWorld(){
return new HelloWorld();
}
}
The above code will be equivalent to the following XML configuration
<beans>
<bean id = "helloWorld" class = "com.test.HelloWorld" />
</beans>
In XML config, we do not use new keyword whereas in java config we are using new keyword. can someone please explain the difference
In the XML configuration, you explain to the system what class should be instanciated (there is a "new" but it is behind the scene) but in the Java Config you actually have to return an instance so that is why we use the 'new' keyword. 'new' simply creates an instance of your class.
The two examples shown in question are not really equivalent.
What the
<beans>
<bean id="helloWorld"
class="com.test.HelloWorld" />
</beans>
really does, is it tells Spring to instantiate class com.test.HelloWorld, and name the resulting bean "helloWorld".
Then the java-config approach is not really doing this. Instead this follows the factory-method pattern, when we tell Spring, that the return value of the method is the bean, and the method name is the name of that bean.
An equivalent of that in XML would be the mentioned factory-method approach, which in this case would look something like this:
<beans>
<bean id="helloWorldConfig"
class="com.test.HelloWorldConfig" />
<bean id="helloWorld"
factory-bean="helloWorldConfig"
factory-method="helloWorld" />
</beans>
Note that there are several approaches to factory-method. In the above, we are assuming, the `helloWorldConfig" is the factory, and we're specifying the method on that bean. Theare are cases with static factory methods too. See here for more examples.
<beans>
<bean id = "helloWorld" class = "com.test.HelloWorld" />
</beans>
This XML configurations tells Spring to "create an instance of com.test.HelloWorld and put it in the bean context with bean id helloWorld".
#Configuration
public class HelloWorldConfig {
#Bean
public HelloWorld helloWorld(){
return new HelloWorld();
}
}
In this Java configuration, we are returning an instance of com.test.HelloWorld. Because of the #Bean annotation, this instance is put into the bean context. As no specific bean id is given, the bean id is derived from the method hellowWorld() and thus becomes helloWorld.
As you can see, both configurations require an instance of com.test.HelloWorld. The XML configuration implicitly creates the instance whereas in the Java configuration you have to explicitly do it yourself.

Apache Camel dataFormat

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!

Why is Mybatis mapper scanner picking up wrong class

I use Spring with Mybatis. I have it configured to scan for mappers in my whole project and I assumed it determined a mapper because it found an XML file which has reference to a java interface.
But this is proven incorrect today because I had to add a new interface which is not a mapper class and Mybatis thinks it is, so it is causing problems in my app due to this error:
Mapped Statements collection does not contain value for com.blah.MyInterface.someMethod
com.blah.MyInterface is just a simple interface which I needed to be included in Spring context so I gave it the #Component tag. Is that the wrong tag to use? Is that where the confusion comes from?
I just needed to create this interface so that I can have a proxy wrap my database calls in one place where I can put a #Transactional tag, since Spring ignores it when it is in my Controller method.
Sample code
package com.blah.something;
#Component public interface MyInterface {
public void someMethod( SomeObject obj) throws Exception;
}
package com.blah.something;
public class MyImplementation implements MyInterface {
#Transactional
public void someMethod( SomeObject obj) throws Exception {
... do a whole bunch of stuff
}
}
I dont want this included in the MyBatis mappers!
Edit: added the mybatis config xml as requested:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="false" />
<setting name="defaultStatementTimeout" value="60"/>
</settings>
<typeAliases>
<typeAlias alias="StripTrailingZerosBigDecimalTypeHandler" type="com.blah.typehandlers.StripTrailingZerosBigDecimalTypeHandler"/>
</typeAliases>
</configuration>
This is the part of my spring xml config which calls the mybatis mapper scanner:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.blah" />
</bean>
So I set it to scan the whole project which includes my interface above but I can't imagine it just grabs every single interface and considers them all mappers!
In my debug log I see mybatis picking up my interface:
12/9/13 11:18:44 904 [org.mybatis.spring.mapper.MapperScannerConfigurer$Scanner.findCandidateComponents:4125] - Scanning file [D:\Weblogic\wls11\domains\ldapdomain\autodeploy\default\WEB-INF\classes\com\blah\MyInterface.class]
12/9/13 11:18:44 904 [org.mybatis.spring.mapper.MapperScannerConfigurer$Scanner.findCandidateComponents:4125] - Identified candidate component class: file [D:\Weblogic\wls11\domains\ldapdomain\autodeploy\default\WEB-INF\classes\com\blah\MyInterface.class]
12/9/13 11:18:44 904 [org.mybatis.spring.mapper.MapperScannerConfigurer$Scanner.findCandidateComponents:4125] - Scanning file [D:\Weblogic\wls11\domains\ldapdomain\autodeploy\default\WEB-INF\classes\com\blah\MyImplementation .class]
12/9/13 11:18:44 904 [org.mybatis.spring.mapper.MapperScannerConfigurer$Scanner.findCandidateComponents:4125] - Ignored because not a concrete top-level class: file [D:\Weblogic\wls11\domains\ldapdomain\autodeploy\default\WEB-INF\classes\com\blah\MyImplementation .class]
There is no XML for this interface, there is no mapper namespace for it, it's just a plain old regular interface and MyBatis should not be thinking it is a mapper service
Ok it looks like MyBAtis scanner does indeed take every interface, it does not have any "smarts" in it to identify mapper interfaces as I thought it would - based on finding matching XML or namespaces. I had to add a filter to the mapper configuration and then introduce a new annotation to annotate my mapper interfaces.

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