How to connect SOAP server and JSON client through Spring Integration? - java

Good day. I am new to spring integration. I wrote a simple SOAP server, and I need to connect a client that communicates through JSON and a server that communicates via SOAP, but I’ve got confused in the technology that this framework provides. As I understand it there are JsonToObjectTransformer and ObjectToMapTransformer transformers. As I understand it is necessary to transform the data before transmitting it to the controller. Is it possible to do this with the help of transformers, or I can use other technologies in the spring integration. And can this be done only with the help of DSL?
Controller:
#Endpoint
public class CityEndpoint {
private static final String NAMESPACE_URI = "http://weather.com/senchenko";
private CityRepository cityRepository;
#Autowired
public CityEndpoint(CityRepository cityRepository) {
this.cityRepository = cityRepository;
}
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCityRequest")
#ResponsePayload
public GetCityResponse getCityResponse(#RequestPayload GetCityRequest request){
GetCityResponse response = new GetCityResponse();
response.setCity(cityRepository.findCity(request.getName()));
return response;
}
}
Config:
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
#Bean(name = "city")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema citySchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("CityPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://weather.com/senchenko");
wsdl11Definition.setSchema(citySchema);
return wsdl11Definition;
}
#Bean
public XsdSchema citySchema() {
return new SimpleXsdSchema(new ClassPathResource("xsd/weather.xsd"));
}
#Bean
#Transformer()
JsonToObjectTransformer jsonToObjectTransformer() {
return new JsonToObjectTransformer();
}
#Bean
#Transformer()
ObjectToMapTransformer objectToMapTransformer(){
return new ObjectToMapTransformer();
}
}
Addition
I solved the problem with redirection to SOAP, but still do not know the best way to convert JSON into an SOAP Envelope and back.
#Bean
public IntegrationFlow httpProxyFlow() {
return IntegrationFlows
.from(Http.inboundGateway("/service"))
.transform(t -> TEST_ENVOLOPE)
.enrichHeaders(h -> h.header("Content-Type", "text/xml; charset=utf-8"))
.handle(Http.outboundGateway("http://localhost:8080/ws")
.expectedResponseType(String.class))
.transform(t -> TEST_RESPONSE)
.get();
}

Your question isn't clear or you are not fully familiar with technologies you need to work.
The SOAP is fully about XML messages exchange. On the server side you have a specific MessageDispatcherServlet which converts an incoming HTTP request to the SOAP envelop fully in XML. There is just nothing about JSON at all.
Your CityEndpoint.getCityResponse() is triggered by the Spring WS Framework when an incoming SOAP request is unmarshalled from the XML into the domain model via JaxB according your XSD definition and generated model. There is just nothing about Spring Integration at all.
Your JsonToObjectTransformer and ObjectToMapTransformer just don't make any sense in this scenario. They are not involved in the SOAP request process.
Sorry to disappoint you in my answer, but it even not clear by your question how that JSON client is going to call SOAP service when JSON and XML are fully different and not compatible protocols.

Related

Update: Spring Boot JMS static reply queue on IBM MQ Series

In my use case I need to do request-reply call to a remote system via managed queues. Using Spring Boot and IBM's MQ starter I have the problem that the application wants to create dynamic/temporary reply queues instead of using the already existing managed queue.
Configuration is set up here
#EnableJms
#Configuration
public class QueueConfiguration {
#Bean
public MQQueueConnectionFactory connectionFactory() throws JMSException {
MQQueueConnectionFactory factory = new MQQueueConnectionFactory();
factory.setTransportType(CT_WMQ); // is 1
factory.setHostName(queueProperties.getHost());
factory.setPort(queueProperties.getPort());
factory.setChannel(queueProperties.getChannel()); // combo of ${queueManager}%${channel}
return factory;
}
#Bean
public JmsMessagingTemplate messagingTemplate(ConnectionFactory connectionFactory) {
JmsMessagingTemplate jmt = new JmsMessagingTemplate(connectionFactory);
jmt.setDefaultDestinationName(queueProperties.getQueueName());
return jmt;
}
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("com.foo.model");
return marshaller;
}
#Bean
public MessageConverter messageConverter(Jaxb2Marshaller marshaller) {
MarshallingMessageConverter converter = new MarshallingMessageConverter();
converter.setMarshaller(marshaller);
converter.setUnmarshaller(marshaller);
return converter;
}
}
Usage is pretty straight forward: Take the object convert and send it. Wait for response receive
and convert it.
#Component
public class ExampleSenderReceiver {
#Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
#Override
#SneakyThrows
public ResponseExample sendAndReceive(RequestExample request, String correlationId) {
MessagePostProcessor mpp = message -> {
message = MessageBuilder.fromMessage(message)
.setHeader(JmsHeaders.CORRELATION_ID, correlationId)
// .setHeader(JmsHeaders.REPLY_TO, "DEV.QUEUE.3") this triggers queue creation
.build();
return message;
};
String destination = Objects.requireNonNull(jmsMessagingTemplate.getDefaultDestinationName());
return jmsMessagingTemplate.convertSendAndReceive(destination, request, ResponseExample.class, mpp);
}
I read already a lot of IBM documentation and think, I need to set the message type to "MQMT_REQUEST" but I do not find the right spot to do so.
Update
Added Spring Integration as Gary proposed and added a configuration for JmsOutboundGateway
#Bean
public MessageChannel requestChannel() {
return new DirectChannel();
}
#Bean
public QueueChannel responseChannel() {
return new QueueChannel();
}
#Bean
#ServiceActivator(inputChannel = "requestChannel" )
public JmsOutboundGateway jmsOutboundGateway( ConnectionFactory connectionFactory) {
JmsOutboundGateway gateway = new JmsOutboundGateway();
gateway.setConnectionFactory(connectionFactory);
gateway.setRequestDestinationName("REQUEST");
gateway.setReplyDestinationName("RESPONSE");
gateway.setReplyChannel(responseChannel());
gateway.setCorrelationKey("JMSCorrelationID*");
gateway.setIdleReplyContainerTimeout(2, TimeUnit.SECONDS);
return gateway;
}
And adapted my ExampleSenderReceiver class
#Autowired
#Qualifier("requestChannel")
private MessageChannel requestChannel;
#Autowired
#Qualifier("responseChannel")
private QueueChannel responseChannel;
#Override
#SneakyThrows
public ResponseExample sendAndReceive(RequestExample request, String correlationId) {
String xmlContent = "the marshalled request object";
Map<String, Object> header = new HashMap<>();
header.put(JmsHeaders.CORRELATION_ID, correlationId);
GenericMessage<String> message1 = new GenericMessage<>(xmlContent, header);
requestChannel.send(message1);
log.info("send done" );
Message<?> receive = responseChannel.receive(1500);
if(null != receive){
log.info("incoming: {}", receive.toString());
}
}
The important part is gateway.setCorrelationKey("JMSCorrelationID*");
Without that line the correlationId was not propagated correct.
Next step is re-adding MessageConverters and make it nice again.
Thank you.
The default JmsTemplate (used by the JmsMessagingTemplate) always uses a temporary reply queue. You can subclass it and override doSendAndReceive(Session session, Destination destination, MessageCreator messageCreator) to use your managed queue instead.
However, it will only work if you have one request outstanding at a time (e.g. all run on a single thread). You will also have to add code for discarding "late" arrivals by checking the correlation id.
You can use async sends instead and handle replies on a listener container and correlate the replies to the requests.
Consider using spring-integration-jms and its outbound gateway instead - it has much more flexibility in reply queue handling (and does all the correlation for you).
https://docs.spring.io/spring-integration/reference/html/jms.html#jms-outbound-gateway
You are missing the queue manager.
ibm:
mq:
queueManager: QM1
channel: chanel
connName: localhost(1414)
user: admin
password: admin

Feign Registration - Spring Cloud - Change Target without ribbon over-ride

Introduction
I would like to be able to have two different spring profiles, and depending on the profile to change to a hardcoded address for our feign builders.
Currently was have the following:
return builder.target(cls, "http://" + serviceName);
But I would actually like to do the following and over-ride the address:
return builder.target(cls, "http://our-server:8009/" + serviceName);
Why
Sometimes we don't want to run all the services within our development environment. Additionally, some of the services are only available through a zuul gateway sometimes.
So we run the same code in different situations and conditions.
Technical Details
We have the following code that we use for building our Feign Clients.
We had been using the #FeignClient annotation in the past, but lately we decided to start building our feignClients manually.
Example below:
#FeignClient(name = "ab-document-store", configuration = MultiPartSupportConfiguration.class, fallback = DocumentStoreFallback.class)
We call the feignRegistrar class with the following command:
return registerFeignClient(DocumentStoreClient.class, true);
#RequiredArgsConstructor
//#Component
#Slf4j
public class FeignRegistrar {
#Autowired
private Decoder decoder;
#Autowired
private Encoder encoder;
#Autowired
private Client client;
#Autowired
private Contract feignContract;
#Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
#Autowired
private List<RequestInterceptor> interceptors;
public <T> T register(Class<T> cls, String serviceName, boolean isDocumentStore) {
if(isDocumentStore){
encoder = new MultipartFormEncoder(new SpringEncoder(messageConverters));
}
//Client trustSSLSockets = new Client.Default(getSSLSocketFactory(), new NoopHostnameVerifier());
Feign.Builder builder = Feign.builder()
.client(client)
.encoder(encoder)
.decoder(decoder)
.contract(feignContract)
.logger(new Slf4Logger())
.logLevel(Logger.Level.HEADERS);
builder.requestInterceptor(new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("X-Service-Name", serviceName);
}
});
for(RequestInterceptor interceptor : interceptors) {
builder.requestInterceptor(interceptor);
}
log.debug("Registering {} - as feign proxy ", serviceName);
return builder.target(cls, "http://" + serviceName);
}
public static class Slf4Logger extends Logger {
#Override
protected void log(String configKey, String format, Object... args) {
log.info("{} - {}", configKey, args);
}
}
}
Spring Cloud Property Over-ride
We have also been using property files such as application-ENV.property with entries such as the following:
ab-document-store.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
ab-document-store.ribbon.listOfServers: localhost:8025
Unfortunately, listOfServers is not enough for us. We would like to be able to assign a directory/path as well. Something like:
ab-document-store.ribbon.listOfServers: localhost:8025/ab-document-store
Otherworkaround
I have thought about sneaking in a header into all requests such as X-SERVICE-NAME using a feign interceptor. Then we could point all services to an address (e.g. localhost:9001) , and forward/proxy those requests to localhost:9001/X-SERVICE-NAME.
However, I would prefer a much easier solution such as:
ab-document-store.ribbon.listOfServers: localhost:8025/ab-document-store
But this doesn't work :(
Introduction
I found a solution for this using a proxy that detects a header.
So, I have a feign interceptor on the java-side that attaches a header x-service-name to every feign-request.
I also have a NodeJS proxy, that analyzes requests, finds x-service-name, and re-writes the requests to become: x-service-name/originalRequestPath.
This allows me to have all the microservices behind a zuul gateway but also access them using a eureka-over-ride.
Java-Feign-Interceptor
Feign.Builder builder = Feign.builder()
.client(client)
.encoder(usedEncoder)
.decoder(decoder)
.contract(feignContract)
.logger(new Slf4Logger())
.logLevel(Logger.Level.HEADERS);
builder.requestInterceptor(new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("X-Service-Name", serviceName);
}
});
NodeJS proxy
In the example, my zuul gateway ( or another proxy ) is on localhost:9001.
I'm listening on localhost:1200 .
let enableProxyForJava = process.env.ENABLE_PROXY_FOR_JAVA;
if (enableProxyForJava != undefined && enableProxyForJava.toLowerCase() === 'true') {
var httpProxyJava = require('http-proxy');
var proxyJava = httpProxyJava.createProxy();
gutil.log( gutil.colors.green('Enabling Proxy for Java. Set your Eureka overrides to localhost:1200.') );
require('http').createServer(function(req, res) {
console.log("req.headers['x-service-name'] = " + req.headers['x-service-name']);
console.log("Before req.url:"+ req.url);
if( req.headers['x-service-name'] != undefined){
let change = req.headers['x-service-name'] +req.url;
console.log("After req.url:"+ change);
req.url = change;
}
proxyJava.web(req, res, {
target: 'http://localhost:9001/'
});
}).listen(1200);
}
Property file inside Java Application that has feign clients
mbak-microservice1.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-microservice1.ribbon.listOfServers: localhost:1200
mbak-microservice2.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-microservice2.ribbon.listOfServers: localhost:1200
mbak-document-store.ribbon.NIWSServerListClassName:com.netflix.loadbalancer.ConfigurationBasedServerList
mbak-document-store.ribbon.listOfServers: localhost:1200

Spring Webservice Client - Empty soapAction returning in response "Message part Request was not recognized"

I am using spring webservices for consuming a service. I am following this article https://spring.io/guides/gs/consuming-web-service/, I have created a client by extending WebServiceGatewaySupport and using that to invoke the service.
The first doubt I have is that I haven't generated any java classes from wsdl (other than jaxb objects for the types mentioned in wsdl). Don't I have to generate the stub (endpoint) classes on the client side? If not, then how does spring know which operation to be invoked as my wsdl has multiple operations?
Here is my code
public class SOAPConnector extends WebServiceGatewaySupport {
public Object callWebService(String url, Object request){
return getWebServiceTemplate().marshalSendAndReceive(url, request);
}
}
#Configuration
public class Config {
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
// this is the package name specified in the <generatePackage> specified in
// pom.xml
marshaller.setContextPath("com.example.howtodoinjava.schemas.school");
return marshaller;
}
#Bean
public SOAPConnector soapConnector(Jaxb2Marshaller marshaller) {
SOAPConnector client = new SOAPConnector();
client.setDefaultUri("https://www.example.com/ExampleServiceBean");
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}
}
The second issue is that on executing above code I am getting following error message, can someone please help me how to fix it:
org.springframework.ws.soap.client.SoapFaultClientException: Message part Request was not recognized. (Does it exist in service WSDL?)
at org.springframework.ws.soap.client.core.SoapFaultMessageResolver.resolveFault(SoapFaultMessageResolver.java:38)
at org.springframework.ws.client.core.WebServiceTemplate.handleFault(WebServiceTemplate.java:830)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:624)
at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:555)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:506)
at org.springframework.ws.client.core.WebServiceTemplate.sendSourceAndReceiveToResult(WebServiceTemplate.java:446)
at org.springframework.ws.client.core.WebServiceTemplate.sendSourceAndReceiveToResult(WebServiceTemplate.java:436)
at org.springframework.ws.client.core.WebServiceTemplate.sendSourceAndReceiveToResult(WebServiceTemplate.java:424)
I might be a missing a very basic thing as I am using spring webservices for the first time. Thanks in advance.

SOAP WS-Addressing property with Wss4jSecurityInterceptor with Java

Hi I create code for consume SOAP service,
For Authentication Header I used Wss4jSecurityInterceptor for set Header information.
I am getting fail response like below
Exception in thread "main" org.springframework.ws.soap.client.SoapFaultClientException: Required element {http://www.w3.org/2005/08/addressing}Action is missing
My Configuration code as below
#Configuration
public class SoapClientConfig {
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.xyz.client");
marshaller.setCheckForXmlRootElement(false);
return marshaller;
}
#Bean
public MyClient myClient(Jaxb2Marshaller marshaller) throws Exception {
MyClient client = new MyClient();
client.setDefaultUri("https://localhost:8080/ws/service");
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
ClientInterceptor[] interceptors = new ClientInterceptor[] {securityInterceptor()};
client.setInterceptors(interceptors);
return client;
}
#Bean
public Wss4jSecurityInterceptor securityInterceptor() {
Wss4jSecurityInterceptor wss4jSecurityInterceptor = new Wss4jSecurityInterceptor();
wss4jSecurityInterceptor.setSecurementActions("UsernameToken");
wss4jSecurityInterceptor.setSecurementMustUnderstand(true);
wss4jSecurityInterceptor.setSecurementPasswordType("PasswordText");
wss4jSecurityInterceptor.setSecurementUsername("XXXXXXXXXXX");
wss4jSecurityInterceptor.setSecurementPassword("XXXXXXXX");
return wss4jSecurityInterceptor;
}
}
Can anyone suggest me what I am missing?
If I try from SOAPUI its working fine. If I set WS-Addressing=false from SOAPUI also giving me same error, So Issue with set WS-Addressing property with above code. How can I?
Do you use WebServiceTemplate to send the request? If yes, you can do something like :
ActionCallback callback = new ActionCallback(
new URI("action uri"));
Here you should provide actual uri location of action instead "action uri". Then, do
getWebServiceTemplate().marshalSendAndReceive(request, callback)
Long time before worked on populating SOAP Header with dynamic value, for that you need to work on constructing the xml nodes using callback object...WebServiceMessageCallback
http://docs.spring.io/spring-ws/site/reference/html/client.html#d5e1848
In my scenario I need to construct the node using QName (Java) Node by Node.

Spring SOAP Endpoint - Cache JaxbContext

I wrote a simple SOAP endpoint essentially following the Spring tutorial found here: https://spring.io/guides/gs/producing-web-service/
Below is the class which is used to intercept the requests (assume repository object injected):
#Endpoint
public class SampleEndpoint {
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "SampleRequest")
public
#ResponsePayload
JAXBElement<SampleResponseType> sampleQuery(
#RequestPayload JAXBElement<SampleRequestType> request) {
ObjectFactory factory = new ObjectFactory();
SampleResponseType response = repository.query(request.getValue());
JAXBElement<SampleResponseType> jaxbResponse = factory.createSampleResponse(response);
return jaxbResponse;
}
}
The service performs correctly. One issue I'm running into is performance, particularly with unmarshalling the response. On average it's taking seconds to unmarshall the object into an XML response. Is there a way to cache/inject the JaxbContext Spring is using for this process to improve on this time?
Here's the web service configuration file I'm using for this endpoint. I've tried alternating between Saaj and Axiom message factories but didn't see much performance change:
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
#Bean
public SaajSoapMessageFactory soap12MessageFactory() {
SaajSoapMessageFactory factory = new SaajSoapMessageFactory();
factory.setSoapVersion(SoapVersion.SOAP_12);
return factory;
}
#Bean
public AxiomSoapMessageFactory axiomSoapMessageFactory() {
AxiomSoapMessageFactory factory = new AxiomSoapMessageFactory();
factory.setSoapVersion(SoapVersion.SOAP_12);
factory.setPayloadCaching(false);
return factory;
}
#Bean
public ServletRegistrationBean dispatcherServlet(
ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
servlet.setMessageFactoryBeanName("soap12MessageFactory");
return new ServletRegistrationBean(servlet, "/ws/*");
}
#Bean(name = "wsdlname")
public DefaultWsdl11Definition xcpdDefaultXcpdWsdl11Definition(XsdSchema
schema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setCreateSoap11Binding(false);
wsdl11Definition.setCreateSoap12Binding(true);
wsdl11Definition.setPortTypeName("xcpdPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition
.setTargetNamespace("http://somenamespace.org/");
wsdl11Definition.setSchema(schema);
return wsdl11Definition;
}
#Bean
public XsdSchema schema() {
return new SimpleXsdSchema(
new ClassPathResource(
"schema.xsd"));
}
}
We obtained a solution to the issue two days ago. The first issue we saw was that the client version of the JVM was installed on the server. I installed the server build of the JVM and changed Tomcat to use that instance. The performance of some of the web services improved dramatically. Previously simple requests were taking three seconds, now they are taking 250 ms. However when testing the Spring service I wrote, I didn't see much of an improvement.
To resolve this last issue, I added the following to the Java options for Tomcat:
-Dcom.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.fastBoot=true
After restarting the Tomcat instance, the response times of the services are now under one second each. Previously requests were taking up to 30 seconds to complete. The server JRE we used is the following:
http://www.oracle.com/technetwork/java/javase/downloads/server-jre8-downloads-2133154.html

Categories

Resources