I am new to Spring Integration, and want to know the best way to call a service using both SOAP and JSON payloads. My configuration appears below. I have the SOAP flow working, we support multiple namespaces, so I use SI to handle the incoming message, determine the version (from the package name) and route it to the correct endpoint. As I mentioned, this works.
<int-ws:inbound-gateway id="ContactServiceGateway"
request-channel="ContactServiceRequestChannel" marshaller="marshaller"
unmarshaller="marshaller" />
<bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="packagesToScan">
<list>
<value>com.predictivesolutions.schema.v1</value>
<value>com.predictivesolutions.schema.v2</value>
<value>com.predictivesolutions.schema.v3</value>
</list>
</property>
</bean>
<!-- map the ContactService endpoint to the Gateway -->
<bean
class="org.springframework.ws.server.endpoint.mapping.UriEndpointMapping">
<property name="mappings">
<props>
<prop key="http://localhost:8088/ws-inbound-gateway/ContactService">ContactServiceGateway</prop>
</props>
</property>
</bean>
<int:channel id="ContactServiceRequestChannel" />
<int:channel id="ContactServiceRequestChannelWithHeaders">
<int:interceptors>
<ref bean="SecurityInterceptor" />
</int:interceptors>
</int:channel>
<!-- REST Configuration -->
<int-http:inbound-gateway id="ContactRestGateway"
path="*" request-channel="ContactRestRequestChannel" reply-channel="ContactRestResponseChannel"
supported-methods="GET, POST, PUT, DELETE" reply-timeout="5000"
header-mapper="headerMapper" request-payload-type="java.lang.String" />
<bean id="headerMapper" class="com.ps.snt.integration.HeaderMapper">
</bean>
<!-- this routes different versions of the SOAP messages to the appropriate
channels - we could use annotations of a custom router method instead -->
<int:header-value-router input-channel="ContactServiceRequestChannelWithHeaders"
header-name="version">
<int:mapping value="com.predictivesolutions.schema.v1"
channel="ContactServiceRequestChannel_v1" />
<int:mapping value="com.predictivesolutions.schema.v2"
channel="ContactServiceRequestChannel_v2" />
<int:mapping value="com.predictivesolutions.schema.v3"
channel="ContactServiceRequestChannel_v3" />
</int:header-value-router>
<!-- create beans all of the different versions -->
<bean id="ContactServiceEndpoint_v1" class="com.ps.snt.integration.ContactServiceEndpoint_v1" />
<bean id="ContactServiceEndpoint_v2" class="com.ps.snt.integration.ContactServiceEndpoint_v2" />
<bean id="ContactServiceEndpoint_v3" class="com.ps.snt.integration.ContactServiceEndpoint_v3" />
<int:service-activator input-channel="ContactServiceRequestChannel_v1"
ref="ContactServiceEndpoint_v1" />
<int:service-activator input-channel="ContactServiceRequestChannel_v2"
ref="ContactServiceEndpoint_v2" />
<int:service-activator input-channel="ContactServiceRequestChannel_v3"
ref="ContactServiceEndpoint_v3" />
I would like to support JSON, and reuse as many components as possible. The flow would be to handle all incoming requests to /rest/*, determine the version and other header values from the HTTP headers, and then route the message to the same service endpoint as the SOAP calls. I do this by using a transformer and basically converting the JSON string to the versioned JAXB object. This works as well. That configuration appears below:
<int-http:inbound-gateway id="ContactRestGateway"
path="*" request-channel="ContactRestRequestChannel" reply-channel="ContactRestResponseChannel"
supported-methods="GET, POST, PUT, DELETE" reply-timeout="5000"
header-mapper="headerMapper" request-payload-type="java.lang.String" />
<bean id="headerMapper" class="com.ps.snt.integration.HeaderMapper">
</bean>
<int:transformer id="ContactRestResponseChannelTransformer"
ref="transformerBean" input-channel="ContactRestResponseChannel"
method="toObject" output-channel="ContactRestRequestChannelTransformed" />
<bean id="transformerBean" class="com.ps.snt.integration.Transformer" />
<int:channel id="ContactRestRequestChannel" />
<int:channel id="ContactRestResponseChannel" />
<int:channel id="ContactRestRequestChannelTransformed" />
<int:header-value-router input-channel="ContactRestRequestChannelTransformed"
header-name="version">
<int:mapping value="com.predictivesolutions.schema.v1"
channel="ContactServiceRequestChannel_v1" />
<int:mapping value="com.predictivesolutions.schema.v2"
channel="ContactServiceRequestChannel_v2" />
<int:mapping value="com.predictivesolutions.schema.v3"
channel="ContactServiceRequestChannel_v3" />
</int:header-value-router>
So I can get this to work, but I have other services I will eventually need to integrate, so I would like the least amount of configuration as possible. Some questions:
can I just use one inbound-gateway for both JSON and SOAP? I need
both /ContactService and /rest/ContactService to be processed by this
gateway. I could add a router element that uses the content-type to
route to the SOAP flow or the JSON flow.
how do I use SPeL in a transformer element, as in the following
example. Basically, from the headers I can determine the version of the payload, and was hoping to use the built in jackson transformer:
<int:json-to-object-transformer input-channel="ContactRestRequestChannel" type="headers['packageName'].concat('.').concat(headers['version'])"/>
You can't use SpEL in the type attribute of the transformer. Since version 3.0, you can set the json__TypeId__ header (upstream of the transformer) to either a class or fully qualified class name - which will be used if there is no type attribute on the transformer.
As I said in my comment; the two gateways are using different technology, so it's not clear why you might think you can combine them into one.
Of course, you can consume SOAP over normal HTTP but you will need to parse out the soap headers etc.
Related
I'm trying to upgrade a current project from Jackson 1.9 to 2.5. Everything was going well until I tried to startup my WAS 7 server and receive this error:
org.springframework.beans.factory.CannotLoadBeanClassException: Error
loading class
[com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider] for bean
with name 'jaxbProvider' defined in ServletContext resource
[/WEB-INF/spring/applicationContext-configuration.xml]: problem with
class file or dependent class; nested exception is
java.lang.NoClassDefFoundError:
com.fasterxml.jackson.jaxrs.base.ProviderBase
This appears to be in relation to trying to register the Jackson Provider in my web.xml below:
<!-- Jackson Provider -->
<bean id="jaxbProvider" class="com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider" >
<property name="mapper" ref="jacksonObjectMapper"/>
</bean>
<bean id="jacksonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" >
<property name="annotationIntrospector" ref="jacksonAnnotationIntrospector"></property>
</bean>
<bean id="jacksonAnnotationIntrospector" class="com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair" >
<constructor-arg ref="primaryAnnotationIntrospector" />
<constructor-arg ref="secondaryAnnotationIntrospector" />
</bean>
<bean id="primaryAnnotationIntrospector" class="com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector" />
<bean id="secondaryAnnotationIntrospector" class="com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector" />
I have the following jars on my classpath:
jackson-annotations-2.5.0.jar
jackson-core-2.5.0.jar
jackson-databind-2.5.0.jar
jackson-jaxrs-json-provider-2.5.0.jar
jackson-module-jaxb-annotations.2.5.0.jar
Now from my understanding its looking for this fellow:
com.fasterxml.jackson.jaxrs.base.ProviderBase
but can't find it. A google search reveals that class as belonging to a
jackson-jaxrs-provider project, but I can't find a specific jar for that. I think that's because that is just a base for the jackson-jaxrs-json-provider.2.5.0.jar that I already included. So shouldn't it inherently be able to see that base class through the jackson-jaxrs-json-provider.2.5.0.jar??
If anyone has an idea of what could be wrong I would be very appreciative!
Thanks.
If you used Maven, adding jackson-jaxrs-json-provider as a dependency, you will see all the following pulled in
(I had an image from another post with v2.2.3- disregard the version)
As you can see, it does depend on a jackson-jaxrs-base, which is where the ProviderBase is located.
You can download it here (just click the 2.5.0, then the Download Bundle)
There is no error caused by the configuration below. However it is not taken efect in Json converter. I am using MethodInvokingBean instead of MethodInvokingFactoryBean because I read that the second one give me a new instance and I am interested in changing the current instance used by RestTemplate. Anyway, I tried the factory one and it doesn't take effect as well.
Honestly, after some weeks searching for, I am wondering if it is really possible to change the default configuration of MapperObject when it is used by RestTemplate. The default configuration is to ignore the nanoseconds and I am really asking myself how someone else has been using RestTemplate when there is the requirement of nanoseconds. I can fix this by changing from sql.TimeStamp to String but it doesn't seem the best approach. If someone else has faced similiar issue and has been able to use sql.TimeStamp with nanoseconds either by changing Jackson Mapper configuration or other way I will appreciate a lot some tips.
//everything start here
ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
_myCllientOfRestService = context.getBean(MyCllientOfRestService.class, request, response);
_myCllientOfRestService.myMethod();
// myCllientOfRestService
Public void myMethod(){
//it is not returning the nano seconds and I am sure the nano seconds is available in rest service return side. MyReturnType object has the sql.TimeStamp variable filled in with nanoseconds in the rest service side but it is lost in client side
_myReturnType = restTemplate.postForObject(urlRestService, myParameters,MyReturnType.class);
}
//applicationContext.xml
<bean id="myCllientOfRestService" class="com.someCompany.mhe.log.handler.MyCllientOfRestService" scope="prototype" lazy-init="true">
<property name="restTemplate" ref="restTemplate2" />
</bean>
<bean id="myMIB"
class="org.springframework.beans.factory.config.MethodInvokingBean">
<property name="targetObject" ref="jacksonObjectMapper" />
<property name="targetMethod" value="configure" />
<property name="arguments">
<list>
<value type="com.fasterxml.jackson.databind.SerializationFeature">WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS</value>
<value>true</value>
</list>
</property>
</bean>
<bean id="myMIB2"
class="org.springframework.beans.factory.config.MethodInvokingBean">
<property name="targetObject" ref="jacksonObjectMapper" />
<property name="targetMethod" value="configure" />
<property name="arguments">
<list>
<value type="com.fasterxml.jackson.databind.DeserializationFeature">READ_DATE_TIMESTAMPS_AS_NANOSECONDS</value>
<value>true</value>
</list>
</property>
</bean>
<bean id="restTemplate2" class="org.springframework.web.client.RestTemplate" >
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
</list>
</property>
</bean>
I have this working now now, but am lost as to why this problem occurred..
I followed the following
http://pfelitti87.blogspot.co.uk/2012/07/rest-services-with-spring-3-xml-json.html
but i changed the controller method and added #ResponseBody...
#ResponseBody
#ResponseStatus(HttpStatus.OK)
#RequestMapping(value="/names", method=RequestMethod.GET)
public List<Book> getNames() {
return returnData();
}
By adding this i noticed that the output would appear as json, regardless of the extension i specified?...
Any ideas why #RepsonseBody would cause this issue?
The post only works for resolving different views based on different types. It does not work on your case.
If you are using Spring 3.2.x, the configuration below would solve your problem.
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="true"/>
<property name="mediaTypes">
<value>
json=application/json
xml=application/xml
</value>
</property>
<property name="defaultContentType" value="application/json"/>
</bean>
However, if you are using 3.1.x, there are approaches like http://tedyoung.me/2011/07/28/spring-mvc-responsebody and http://springinpractice.com/2012/02/22/supporting-xml-and-json-web-service-endpoints-in-spring-3-1-using-responsebody that might help you.
I have a custom MessageConverter registered in spring with the following configuration:
<bean id="jsonHttpMessageConverter" class="com.eventwiz.web.util.ServiceResponseHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json"/>
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<util:list id="beanList">
<ref bean="jsonHttpMessageConverter"/>
</util:list>
</property>
</bean>
However, it's not being called as I confirmed that with a breakpoint in my code. ServiceResponseHttpMessageConverter subclasses MappingJacksonHttpMessageConverter and overrides writeInternal() method. I've even tried overriding MessageConverter.supports() just to see if that was being called and it wasn't. Any ideas what's going on?
The issue, as was found based on an answer from the author was the <mvc:annotation-driven/> tag which registers its own handlerAdapter, so if another handlerAdapter is added to the Spring MVC configuration file with converters added to this adapter, the custom adapters will not take effect. The fix is to either register the httpMessageConverters through <mvc:message-converters... tag under <mvc:annotation-driven or removing <mvc:annotation-driven and having the custom handleradapter with the httpmessageconverter registered under it.
I cant get Spring's JSON support working. In my spring-servlet.xml file i have included following lines:
<mvc:annotation-driven/>
<context:component-scan base-package="my.packagename.here" />
<context:annotation-config />
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />
<bean id="jacksonMessageConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
I have also downloaded jackson libraries and added them to my eclipse project and also to WEB-INF/lib folder. When sending request to controller with jQuery getJSON method i get following errors:
javax.servlet.ServletException: Servlet.init() for servlet dispatcher threw exception
java.lang.NoClassDefFoundError: org/codehaus/jackson/JsonProcessingException
java.lang.ClassNotFoundException: org.codehaus.jackson.JsonProcessingException
What do you think is the problem. I'm guessing it has something to do with my spring-servlet.xml file. I can paste entire error log, if you need.
For Jackson v2 jars, class to be used for bean should be
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
For older jackson version, org.springframework.http.converter.json.MappingJacksonHttpMessageConverter is ok. Make sure the jar files are added to the project library.
The answer of anshul tiwari partially captures the problem. Here is a more complete answer ...
When Jackson made it to version 2.0, the core library got changed from jackson-core-asl-x.x.x.jar to jackson-core-x.x.x.jar. With that, the paths changed. In version 1, org.codehaus.jackson was the path. In version 2, it is in com.fasterxml.jackson.core if you were to open up the jar file.
Now if you have the libraries of version 2 and you are seeing the org.codehaus.jackson ClassNotFoundException, it means that there is a mixing of versions. Some code is expecting v1 but you have provided v2. This is certainly possible when using Spring so you have to be careful to choose the correct jar file for your code.
EDIT
In fact, looking at 3.2 Spring source code, org.springframework.http.converter.json.MappingJacksonHttpMessageConverter still references the org.codehaus stuff so this is a case where Spring source code needs to import the correct path and there is nothing you the developer can do to use jackson 2.
JsonProcessingException is part of the jackson-core-asl-x.x.x.jar. Make sure that it's part of your classpath.
Just to complement anshul tiwari answer, the bean tag should go inside mvc:annotation-driver:
<mvc:annotation-driven>
<mvc:message-converters>
<bean
class="org.springframework.http.converter.ResourceHttpMessageConverter" />
<!-- <bean -->
<!-- class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"
/> -->
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
<!-- <bean -->
<!-- class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"
/> -->
</mvc:message-converters>
</mvc:annotation-driven>
Use it Like below : Hope it will work..
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd"
default-lazy-init="true">
<context:component-scan base-package="com.vc.bmp.resource" />
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<!-- property name="prefixJson" value="true" />
<property name="supportedMediaTypes" value="application/json" /-->
<property name="objectMapper">
<bean class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="serializationInclusion">
<value type="com.fasterxml.jackson.annotation.JsonInclude.Include">NON_NULL</value>
</property>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>