Camel way: JSON to Array of Object - json

Need to deserialize stringls like "[100,200]" into array of Integer to later use from:
<to uri="class:java.awt.Robot?method=mouseMove&multiParameterArray=true"/>
multiParameterArray=true is a key.
<unmarshal ref="json"/>
with
<dataFormats>
<json id="json" library="Jackson" allowJmsType="true"/>
</dataFormats>
and stuck. Working in restricted environment, can't create custom beans that perform the stuff. Need some standard camel way.
UPDATE 1
Seems this construct does exactly what I need. But I'd better use <unmarshal/> rather then it
<setBody>
<constant>[100,200]</constant>
</setBody>
<setBody>
<jsonpath>$.*</jsonpath>
</setBody>

Related

Deserializing single-attribute JSON payload in Spring MVC controller

I want to create controller methods that semantically look like the following
public HttpEntity<?> deleteUser(String userId){
...
}
The client is going to pass the user ID as part of the JSON payload. If I try to annotate #RequestBody the string parameter and issue a {"userId":"foo"} payload, then I get an exception
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: java.io.PushbackInputStream#7311a203; line: 1, column: 1]
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148) ~[jackson-databind-2.6.1.jar:2.6.1]
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:854) ~[jackson-databind-2.6.1.jar:2.6.1]
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:62) ~[jackson-databind-2.6.1.jar:2.6.1]
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:11) ~[jackson-databind-2.6.1.jar:2.6.1]
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3702) ~[jackson-databind-2.6.1.jar:2.6.1]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2798) ~[jackson-databind-2.6.1.jar:2.6.1]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:221) ~[spring-web-4.2.0.RELEASE.jar:4.2.0.RELEASE]
And that is reasonable because JSON wants to deserialize a complex object (with namely one attribute) into a String.
I also know that "foo" is not valid JSON. And I know that I can use a Map<String,Object> or even better a ModelMap, and as a last resort I could use query string and #RequestParam, but today I have been clearly asked by my boss to find a way to use a plain string instead of an object, in order for code to look more readable.
How can I force Jackson/MVC to deserialize only the "username" property into a plain old String?
You will usually see this type of error when Spring MVC finds a request mapping that matches the URL path but the parameters (or headers or something) don't match what the handler method is expecting.
If you use the #RequestBody annotation then Spring MVC is expecting to map the entire body of the POST request to an Object,it dont work with String by default.
There are different way to do this as listed below:
1) Change method type of deleteUser() method type to GET instead of Post and use userId as String.
2) You could simply inject the HttpServletRequest into your method and read the body:
public void deleteUser(HttpServletRequest request) {
String userID = IOUtils.toString( request.getInputStream());
// do stuff
}
3) Use a wrapper (java model of the JSON object) that could replace the String parameter,and also this will work fine with the json coming in your post.
public class UserWrapper {
private String userId;
//getter setters
and then use in your controller as:
public void deleteUser(#RequestBody UserWrapper user) {
//do your stuff
}
4) Spring provides a way to configure multiple message converters as shown below:
Note: Then, requests to the various methods must specify the "content-type" header with an appropriate value. For those methods where the request body is mapped to a JAXB bean, specify "application/xml". And for those where the request body is a String, use "text/plain".
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jsonConverter" />
<ref bean="marshallingConverter" />
<ref bean="stringHttpMessageConverter" />
</list>
</property>
</bean>
<bean id="jsonConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json" />
</bean>
<bean id="marshallingConverter"
class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<constructor-arg ref="jaxb2Marshaller" />
<property name="supportedMediaTypes" value="application/xml"/>
</bean>
<bean id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/plain"/>
</bean>
Hope this help you!

Camel JSON serialization of joda DateTime using Jackson in Spring DSL

I have been using the json, dataFormats, and marshall/unmarshall tags in the Camel Spring DSL as described in the following url and snippet from applicationContext.xml.
http://camel.apache.org/json.html
...
<dataFormats>
<json id="json" library="Jackson" unmarshalTypeName="com.example.Foo" />
</dataFormats>
...
<route>
<from uri="direct:inPojo"/>
<marshal ref="json"/>
</route>
<route>
<from uri="direct:backPojo"/>
<unmarshal ref="json"/>
</route>
...
I want to serialize my joda DateTime objects as longs, as mentioned here:
http://wiki.fasterxml.com/JacksonFAQDateHandling
I understand that I need to register the separate Jackson-datatype-Joda module, and how to do that when I have an instance of an ObjectMapper, as mentioned in How to serialize Joda DateTime with Jackson JSON processer?, but is there a way to do it using the Camel/Spring XML tags?
Pass in ObjectMapper to JacksonDataFormat. I'm not using Spring DSL, but you should be able to do the same thing like what I do in Java DSL:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule( ... );
JacksonDataFormat df = new JacksonDataFormat(mapper, Pojo.class);
from("direct:source").unmarshal(df);

No message body reader has been found [duplicate]

I have a jaxrs client configured like this:
<jaxrs:client id="opaRestProxy" name="opaRestProxy"
address="${endpoint}" serviceClass="com.test.RestProxy"
inheritHeaders="true" threadSafe="true">
<jaxrs:headers>
<entry key="Accept" value="application/json" />
<entry key="Content-Type" value="application/json" />
</jaxrs:headers>
</jaxrs:client>
But when I send a request I get the following exception:
Caused by: org.apache.cxf.interceptor.Fault: .No message body writer has been found for class : class com.test.RequestObject, ContentType : application/json.
at org.apache.cxf.jaxrs.client.ClientProxyImpl$BodyWriter.handleMessage(ClientProxyImpl.java:646)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.jaxrs.client.ClientProxyImpl.doChainedInvocation(ClientProxyImpl.java:527)
... 47 more
My RestProxy class looks like this:
#Component
public interface RestProxy {
#POST
#Path("/getSomething")
String getSomething(RequestObject RequestObject);
}
If you are using Jackson JSON library you need to add these xml tags to your application context.
<jaxrs:providers>
<bean id="jacksonProvider" class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
</jaxrs:providers>
If you are using any other library add that bean to the providers tag. Hope that helps!
If you are consuming using javax.ws.rs.client.Client, please register the provider using client.register(new JacksonJsonProvider());
This answers point me in the right direction, yet i had to add on two parts to make it work on web.xml
<init-param>
<param-name>jaxrs.providers</param-name>
<param-value>
org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider
(writeXsiType=false)
</param-value>
</init-param>
And on the client call:
List<Object> providers = new ArrayList<>();
// add custom providers if any
providers.add(new JacksonJaxbJsonProvider());
WebClient client = WebClient.create(ENDPOINT_ADDRESS,providers);

spring-mvc return raw json string

I want the convenience of automatically serializing objects into JSON and ability to return raw JSON string. I am using Gson instead of Jackson, since Gson has been in my app for a while and I have existing tweaks, converters, and annotations peppered throughout my app.
<mvc:annotation-driven >
<mvc:message-converters register-defaults="true">
<bean class="com.test.GSONHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
I can automatically serialize pojo's:
#RequestMapping(value="foo/{name}", method = RequestMethod.GET)
public #ResponseBody Shop getShopInJSON(#PathVariable String name) {
return new Shop();
}
I want this to work also:
#RequestMapping(value="rawJsonTest/{name}", method = RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody String rawJsonTest(#PathVariable String name) {
return "{\"test\":5}";
}
Result right now is an escaped value:
"{\"test\":5}"
instead of:
{"test":5}
The problem is that your custom converter takes precedence over the default ones. It's thus called, considers the String as a raw String that must be converted to JSON, and thus escapes the double quotes.
I'm not sure if and how it's possible with XML to register a converter after (and not before) the default ones, but you could set register-defaults to false and provide an explicit list of all the converters you want to apply. If org.springframework.http.converter.StringHttpMessageConverter is registered before your custom one, it will be called first and will send the returned String as is.
Thanks for the correct answer, #JB Nizet
Order matters:
<mvc:annotation-driven >
<mvc:message-converters register-defaults="true">
<bean class = "org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json; charset=UTF-8" />
</bean>
<bean class="com.test.GSONHttpMessageConverter" />
</mvc:message-converters>

Binary File To SQL Database Apache Camel

I need some guidance around which approach to use to load binary files from a folder into a MySQL Database using Camel. Basically I want to store voice logs from our PBX system into a database. The directory with the voice logs will be a remote directory
I have designed a prototype but I am not sure if this is really efficient, it works but I am not happy with the design. Let me explain what I am doing. Camel route as follows:
<camelContext xmlns="http://camel.apache.org/schema/spring">
<package>com.hia.camelone</package>
<route>
<from uri="file://c:/CTest/Inbox?noop=true&recursive=true&delay=3000"/>
<to uri="bean://fileToSQL"/>
<to uri="jdbc://timlogdb"/>
</route>
</camelContext>
<bean id="timlogdb" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value=" com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/TimLog" />
<property name="username" value="root" />
<property name="password" value="blahblah" />
</bean>
<bean id="fileToSQL" class="com.hia.camelone.fileToSQL"/>
And the code to fileToSQL bean is:
public class fileToSQL {
public String toString(#Headers Map<String,Object> header, #Body Object body){
StringBuilder sb = new StringBuilder();
String filename =(String)header.get("CamelFileNameOnly");
String escapedFileName = StringEscapeUtils.escapeJava(filename).replace("\'", "");
String filePath = StringEscapeUtils.escapeJava((String)header.get("CamelFilePath"));
sb.append("insert into FileLog ");
sb.append("(FileName,FileData) values (");
sb.append("'").append(escapedFileName).append("',").append("LOAD_FILE(\"").append(filePath).append("\")");
sb.append(")");
System.out.println(sb.toString());
System.out.println(body);
System.out.println(header.toString());
return sb.toString();
}
}
Ok short explanation I get the file component to consume the files then I build a SQL string using the MySQL LOAD_FILE() function to load the file.
My thoughts around this:
The LOAD_FILE function only works on the local machine and thus this route will only with the files being on the local machine. I could use a file producer to copy the files from some remote directory to a local directory and then use the route. My route would be something like this then:
<route>
<from uri="file://c:/CTest/Inbox?noop=true&recursive=true&delay=3000"/>
<to uri="file://c:/outbox"/>
<to uri="bean://fileToSQL"/>
<to uri="jdbc://timlogdb"/>
</route>
However since I have access to the files content in the message from the files consumer I should be able to theoretically be able to access the body/content of the string and build a SQL command that does NOT use the LOAD_FILE() function.
The only way I know how to build such a string is by using the prepared statement of JDBC. This would be first prize if I could somehow build a insert statement with the content from the file consumer.
Can I create a prepared statement in my fileToSQL bean and pass it to my jdbc component?
Or how do I build a INSERT statement without the LOAD_FILE() function?
Since I have to use the LOAD_FILE() function I would now have to cater for both unix and windows filepaths. While this should not be difficult I just dont like the idea of putting OS specific code into my applications(feels like a work around).
Anybody here ever uploaded binary files to a MySQL database using Camel who can give me some guidance on the points above. While I could work around the problems I just want to make sure I dont miss a obvious way of doing things.
I had a look around here and only found people working with mostly text files. Guys please don't even go down the route of me storing the file on the files system and linking it to the database. We have some very specific disaster recovery requirements and legal requirements that enforce the need for me to store it in a database.
Right so I managed to find a way and it was not that difficult. What I essentially did was get rid of the JDBC Camel Component in the route. I then injected the data source bean into my fileToSQL bean. I then used a simple prepared statement to insert the file and its name into MySQL.
As always code is much more explicit than my english.
<camelContext xmlns="http://camel.apache.org/schema/spring">
<package>com.hia.camelone</package>
<route>
<from uri="file://c:/CTest/Inbox?noop=true&recursive=true&delay=3000"/>
<to uri="bean://fileToSQL"/>
<!--<to uri="jdbc://timlogdb"/>-->
</route>
</camelContext>
<bean id="timlogdb" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value=" com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/TimLog" />
<property name="username" value="root" />
<property name="password" value="lalala" />
</bean>
<bean id="fileToSQL" class="com.hia.camelone.fileToSQL">
<property name="dataSource" ref="timlogdb"/>
</bean>
As you can see I inject my timlogdb bean into my fileToSQL bean. Spring ROCKS!
So here is my fileToSQL bean.
public class fileToSQL {
private DriverManagerDataSource dataSource;
private static final String SQL_INSERT="insert into FileLog(FileName,FileData)values(?,?)";
#Handler
public void toString(#Headers Map<String,Object> header,Exchange exchange){
Connection conn = null;
PreparedStatement stmt=null;
String filename =StringEscapeUtils.escapeJava(((String)header.get("CamelFileNameOnly")).replace("\'", ""));
try {
conn= dataSource.getConnection();
stmt =conn.prepareStatement(SQL_INSERT);
stmt.setString(1, filename);
byte[] filedata = exchange.getIn().getBody(byte[].class);
stmt.setBytes(2,filedata );
int s = stmt.executeUpdate();
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
finally{
try
{
if (stmt!=null)
{
stmt.close();
}
if (conn!=null)
{
conn.close();
}
}
catch(SQLException e)
{
System.out.println(e.getMessage());
}
}
}
/**
* #param dataSource the dataSource to set
*/
public void setDataSource(DriverManagerDataSource dataSource) {
this.dataSource = dataSource;
}
}
The guys from Camel did a great job. Camel is truly flexible especially when you combine it with Spring.
What a ride!