How to use MappingJackson2HttpMessageConverter to convert Maps and List correctly? - spring

In my Spring MVC project MappingJackson2HttpMessageConverter configured as follows:
<bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"></bean>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jsonMessageConverter" />
</list>
</property>
</bean>
For plain simple Java POJO Beans, serialization works well. All good. But, i also have Beans that include Maps that needed to be serialized as well, and it's fails (JsonMappingException).
From Jackson instructions, I know that to solve this what needed is to indicate the actual type for the object mapper. It looks like that:
Map<String, ResultValue> results = mapper.readValue(jsonSource,
new TypeReference<Map<String, ResultValue>>() { } );
How can be the same configuration be done to MappingJackson2HttpMessageConverter (with based on Jackson2 object mapper)?

It's not clear from your question what doesn't work, but I'm guessing that you have a bean that contains java.util.Map or java.util.List property and you're getting com.fasterxml.jackson.databind.JsonMappingException when deserializing that bean.
In that case you can give hints on fields with #JsonDeserialize annotation. So for instance if you have a java.util.Map<String, ResultValue> field, you can annotate it like:
#JsonDeserialize(keyAs = String.class, contentAs = ResultValue.class)
public Map<String, ResultValue> map;

Related

Convert XML bean definition with parent to Java with annotations

I have an application using a framework that provides certain Spring beans via XML files in the framework. The configuration of my application is currently done partly in XML but mostly with Spring annotations.
Some of the XML bean definitions have parents referring to beans supplied by the framework, e.g.
<bean id="MyBean" parent="FrameworkBean">
<property name="context">
<map merge="true">
<entry key="SomeKey" value-ref="SomeValue" />
</map>
</property>
</bean>
FramwworkBean is defined in an XML file in the framework. There is a chain of bean inheritance. At each step some entries are added to the context:
<bean id="FrameworkBean" parent="AbstractBean">
<map merge="true">...
<bean id="AbstractBean" abstract="true" class="ClassWithContext"/>
I understand the result of all this is construction of a ClassWithContext
instance with a map containing all the entries up the chain.
Is it possible to write Java code to do the same, without duplicating code from the framework XML files?
#Bean("MyBean") ClassWithContext myBean() {
return ??? // code that uses "FrameworkBean" somehow
}
The XML bean definition has no dependency on the type of AbstractBean.
If MyBean can be created by Java code, can that code be written to be equally type-agnostic? Or should I just leave this in XML?
If your "FrameworkBean" is not abstract bean you can try the following:
#Bean
public SomeType myBean(#Qualifier("FrameworkBean") FrameworkBeanType frameworkBean) {
SomeType type = getSomeType();
type.setFrameworkBean(frameworkBean);
return type;
}

spring bean creation for java builder pattern objects

I'm using Google Gson(gson) library form reading/writing json files and spring mvc 3 in my web application side.
So In controller, I want to create a singleton instance of Gson with pretty printing. In java the code would be,
Gson gson = new GsonBuilder().setPrettyPrinting().create();
In Controller, I created an autowired entry as below,
#Autowired
private Gson gson;
and the xml bean configuration is as below,
<bean id="gsonBuilder" class="com.google.gson.GsonBuilder">
<property name="prettyPrinting" value="true"/>
</bean>
<bean id="gson" factory-bean="gsonBuilder" factory-method="create"/>
It throws the following exception in catalina logs,
Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'prettyPrinting' of bean class [com.google.gson.GsonBuilder]: Bean property 'prettyPrinting' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:1024)
at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:900)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:76)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:58)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1358)
I knew that the setter signature of setPrettyPrinting() is different than spring expects, that's why spring is throwing the exception.
public GsonBuilder setPrettyPrinting() {
prettyPrinting = true;
return this;
}
But I'm unable to find a way to wire the builder pattern beans. I'm pretty new to spring. Can any one let me know, whether it is possible to solve this problem in xml bean approach ?
Simply use a static factory method as described in the documentation and use Java code to create Java objects: it's sooo much easier and safe:
<bean id="gson"
class="com.foo.bar.MyGsonFactory"
factory-method="create"/>
and in MyGsonFactory:
public static Gson create() {
return new GsonBuilder().setPrettyPrinting().create();
}
The setPrettyPrinting method doesn't take a parameter, so it doesn't look like a java bean property setter. That's why your approach didn't work. You can use the factory method mentioned in the other answer or use a method invoking bean in the config file like so:
<bean id="myStarter" class="org.springframework.beans.factory.config.MethodInvokingBean">
<property name="targetObject" ref="gsonBuilder"/>
<property name="targetMethod" value="setPrettyPrinting"/>
</bean>
The factory approach seems more straightforward and idiomatic to me, but I include this approach for the sake of completeness.

Can I combine #controller and XML bean mapping in spring?

I currently have a #Controller declared in spring and have a bunch of mappings done like so:
#RequestMapping(value = "foo", method = RequestMethod.GET)
public ModelAndView foo() {
ModelAndView mav = new ModelAndView(
"myjsp");
return mav;
}
However every time I want to add a simple JSP mapping I need to recompile and build a new war and deploy.
This isnt so bad except sometimes other members of the team have requests and it would be easier if they can just go into the test env and create the mapping themselves without having to recompile.
I know that you can do similar mapping using xml but can I do this at the same time that I have the #Controller defined?
Like in the example above how could I define that mapping in XML rather than in java?
or say I needed foo2 to map to myjsp2.jsp
I am using spring MVC 3.2
Look into BeanNameUrlHandlerMapping which allows you specify url patterns for controllers in your configuration. Documentation
Example
<beans>
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<bean name="/editaccount.form" class="org.springframework.web.servlet.mvc.SimpleFormController">
<property name="formView" value="account"/>
<property name="successView" value="account-created"/>
<property name="commandName" value="account"/>
<property name="commandClass" value="samples.Account"/>
</bean>
<beans>

Scala, #ResponseBody, and Map

I'm experimenting with Scala in a spring3 project, and I'm not getting what I expect when I return a map with #ResponseBody. I'm including a working example in java along with my attempt in scala
// This is Java
#RequestMapping(value="/pbx.admin", method=RequestMethod.GET)
public #ResponseBody Map<String, Object> getInfo2(HttpServletRequest request, Model model){
Map<String, Object> map = new HashMap<String, Object>();
map.put("pbx", "admin");
map.put("method", "s");
return map;
}
The java returns json with the pbx & method defined, which is what I'm expecting.
// This is Scala
#RequestMapping(value= Array("/pbx.admin"),
method=Array(RequestMethod.GET))
#ResponseBody
def getInfo2() = {
Map("pbx" -> "admin", "method" -> "s")
}
The scala returns something different though:
{
empty: false,
traversableAgain: true
}
What do I need to do in order to get my map keys / values?
And for extra credit, Is there a better 'scala way' to do this? Thanks!
Spring MVC framework wasn't built with Scala in mind and it doesn't recognize Scala's Map. You must convert scala.collection.Map to java.util.Map. You can either use implicit conversion:
import collection.JavaConversions._
def getInfo2(): java.util.Map[String, String] = {
Map("pbx" -> "admin", "method" -> "s")
}
or convert it manually:
import collection.JavaConverters._
def getInfo2() = {
Map("pbx" -> "admin", "method" -> "s").asJava
}
Mind the imports, they are important.
I suspect Spring can be hacked to accept Scala collections, but obviously not out-of-the box.
Spring MVC uses Jackson. You need to register the Jackson scala module as a mapper. This adds support for scala to Jackson allowing it to recognize the collections.
Example:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.ScalaModule
import com.fasterxml.jackson.module.scala.DefaultScalaModule
class ScalaObjectMapper extends ObjectMapper {
{
registerModule(DefaultScalaModule)
}
}
Then you need to add it to you message converters
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.petzfactor.packwall.web.module.ScalaObjectMapper"></bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
The module is on github at https://github.com/FasterXML/jackson-module-scala
Scala module is required to serialize scala objects such as Collections, Option and case classes. Here is a way to register the Jackson Scala module only by modifying the Spring context.
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapperWithScala"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean name="scalaModule" class="com.fasterxml.jackson.module.scala.DefaultScalaModule"/>
<bean name="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper"/>
<bean name="objectMapperWithScala" factory-bean="objectMapper" factory-method="registerModule">
<constructor-arg ref="scalaModule"/>
</bean>

how to configure java.xml.transform.transformer in spring

How can i configure java.xml.transform.Transformer in spring framework ? I need instance of transformer to transform xml to text via xslt. Hence it is critical that the configured transformer should have knowledge of xslt stylesheet. I am not using this in a web project, rather i am using this in a standalone java program.
Well, the Java to configure a Transformer is like this:
Source stylesheetSource = new StreamSource(new File("/path/to/my/stylesheet.xslt"));
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer(stylesheetSource);
If you really want to do this purely in Spring XML, this is the equivalent:
<bean id="stylesheetSource" class="javax.xml.transform.stream.StreamSource">
<property name="systemId" value="/path/to/my/stylesheet.xslt"/>
</bean>
<bean id="transformerFactory" class="javax.xml.transform.TransformerFactory" factory-method="newInstance"/>
<bean id="transformer" factory-bean="transformerFactory" factory-method="newTransformer">
<constructor-arg ref="stylesheetSource"/>
</bean>
You're going to need to get an instance of a Transformer from an appropriate TransformerFactory. You can use the built-in xerces transformer factory or a 3rd party XSLT processor, like saxonica.
Using Spring's IoC you can configure a Xerces XML transformer like this:
<bean id="transformerFactory" class="org.apache.xerces.jaxp.SAXParserFactoryImpl" />
or a saxon XML transformer like this:
<bean id="transformerFactory" class="net.sf.saxon.TransformerFactoryImpl" />
Once you have a TransformerFactory you can use dependency injection to get a new instance of a transformer either inside your bean or using the IoC. Switching to be inside your class you might have some property, say transFact that you need to set:
<bean id="myBean" class="myClass">
<property name="transFact" ref="transformerFactory" />
</bean>
Then in your class:
public class myClass {
// ...
private javax.xml.transformer.TransformerFactory transFact;
public void myMethod(){
StreamSource transformerStream = new StreamSource(getResourceAsStream(pathToXslt));
javax.xml.transformer.Transformer myTrans = transFact.newTransformer(transformerStream);
// now you've got a myTrans
}
// ...
public setTransFact(javax.xml.transformer.TransformerFactory transFact){
this.transFact = transFact;
}
}
Alternatively you can get a new transformer within IoC using the factory-method with a little more effort.
I found this way to provide xsl file from the classpath:
<bean id="errorLogTransformer" factory-bean="transformerFactory" factory-method="newTransformer" >
<constructor-arg>
<bean class="org.springframework.xml.transform.ResourceSource">
<constructor-arg>
<value>classpath:errorLog.xsl</value>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
Pretty ugly, but it works!

Resources