Consuming SOAP Web Services with ActiveMQ in Spring Boot - soap

I am new to Web Service development, currently building a SOAP Web Service with Spring Boot 2.7.0, Java 17.
As well as a client application that communicates with this soap service via JMS.
But I do not know the procedure of the process.
The way I see it -> The client application (Producer) sends a message to a queue that lives on the server side (Consumer), the queue pops the message when ready to consume and redirects it to the endpoint handler method and then sends a response in the response queue back to the client side.
However, I don't know how to redirect the JMS message to the endpoint. Nor do I know how to send it back. I have read all of the documentations related to "SOAP over JMS", CXF-SOAP-JMS", "ActiveMQ with Spring", etc... None of them helped me fix this problem.
Using SOAP with http is pretty easy by exploiting the "WebServiceTemplate", provided by Spring-WS API. But when I tried using it over JMS I encountered several problems, including the following:
What to do with the JMS Message once in the destination object?
How do I send it specifically to my endpoint handler method?
What and how do I send back to the response destination?
Sample code of what I've tried latest
CLIENT APP
Client Configuration
#Configuration
public class SoapClientConfiguration {
#Value("${spring.activemq.broker-url}")
private String activeMqUrl;
#Value("${spring.activemq.user}")
private String userName;
#Value("${spring.activemq.password}")
private String password;
#Bean
Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("com.mile.soap.client.app.quiz");
return marshaller;
}
#Bean
WebServiceTemplate template() {
return new WebServiceTemplate(jaxb2Marshaller());
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(mqConnectionFactory());
return factory;
}
#Bean
public SingleConnectionFactory mqConnectionFactory(){
SingleConnectionFactory factory = new SingleConnectionFactory();
ActiveMQConnectionFactory mqConnectionFactory = new ActiveMQConnectionFactory();
mqConnectionFactory.setBrokerURL(activeMqUrl);
mqConnectionFactory.setUserName(userName);
mqConnectionFactory.setPassword(password);
factory.setTargetConnectionFactory(mqConnectionFactory);
return factory;
}
#Bean
public JmsTemplate jmsTemplate(){
JmsTemplate template = new JmsTemplate();
template.setConnectionFactory(mqConnectionFactory());
return template;
}
Client Service
#Service
public class SoapClient extends WebServiceGatewaySupport{
#Autowired WebServiceTemplate template;
#Autowired JmsTemplate jmsTemplate;
public CategoriesResponse getCategories() {
CategoriesResponse response = new CategoriesResponse();
try {
SAAJResult soapRequest = new SAAJResult();
template.getMarshaller().marshal(new GetCategoriesRequest(), soapRequest);
Message m = jmsTemplate.sendAndReceive("example.queue", new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
return session.createObjectMessage(soapRequest.toString());
}
});
response = m.getBody(CategoriesResponse.class);
}
catch (Exception e) {
e.printStackTrace();
}
return response;
}
SERVER SIDE APP
ActiveMQ Configuration
#Configuration #EnableJms
public class ActiveMqConfig {
#Value("${spring.activemq.broker-url}")
private String activeMqUrl;
#Value("${spring.activemq.user}")
private String userName;
#Value("${spring.activemq.password}")
private String password;
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(mqConnectionFactory());
return factory;
}
#Bean
public SingleConnectionFactory mqConnectionFactory(){
SingleConnectionFactory factory = new SingleConnectionFactory();
ActiveMQConnectionFactory mqConnectionFactory = new ActiveMQConnectionFactory();
mqConnectionFactory.setBrokerURL(activeMqUrl);
mqConnectionFactory.setUserName(userName);
mqConnectionFactory.setPassword(password);
factory.setTargetConnectionFactory(mqConnectionFactory);
return factory;
}
}
Main Configuration (WSDL/SERVLET)
#Configuration
#EnableWs
public class SoapConfiguration extends WsConfigurerAdapter{
#Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus(){
SpringBus bus = new SpringBus();
return bus;
}
#Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(
ApplicationContext applicationContext, SpringBus springBus){
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<>(servlet, "/*");
}
//wsdl
#Bean(name = "quiz") #SneakyThrows
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema schema) {
DefaultWsdl11Definition defaultWsdl11Definition = new DefaultWsdl11Definition();
defaultWsdl11Definition.setPortTypeName("QuizMainEndPoint");
defaultWsdl11Definition.setLocationUri("/");
defaultWsdl11Definition.setTargetNamespace("http://www.mile.com/collection/management/soap/Quiz");
defaultWsdl11Definition.setTransportUri("http://www.openuri.org/2002/04/soap/jms/");
defaultWsdl11Definition.setSchema(schema);
return defaultWsdl11Definition;
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
EndpointInterceptor endpointInterceptor = new PayloadRootSmartSoapEndpointInterceptor(
new QuizMainEndpointInterceptor(), "http://www.mile.com/collection/management/soap/Quiz", "GetCategoriesRequest");
interceptors.add(endpointInterceptor);
}
#Bean
public XsdSchema schema() {
return new SimpleXsdSchema(new ClassPathResource("/schemas/QuizSchema/quiz.xsd"));
}
}
Listener
#Component
public class Listener {
#JmsListener(destination = "example.queue")
public void listenRequests(Message message) {
System.out.println(message.toString());
/*I RECEIVE THE MESSAGE BUT I HAVE NO IDEA WHAT TO DO WITH IT.
* HOW DO I CONSUME IT?
*/
}
}
Method in a class annotated with #Endpoint
#ResponsePayload
#PayloadRoot(namespace = NAMESPACE, localPart = "GetCategoriesRequest")
public CategoriesResponse getCategories( #RequestPayload GetCategoriesRequest request) {
CategoriesResponse response = new CategoriesResponse(service.getCategories());
/*
* How to CONVERT my JMS Message living in the Destination Object - "example.queue" To a SOAP Message
* and be RECOGNISED by this exact method??
Do i send a JMS response here or somewhere else?
Is it sent by default?
*/
return response;
}
Thank you for reading thoroughly. I'd appreciate any kind of help.

Related

call to authenticated rest web service fails in case of post method spring boot

I'm trying to consume a rest web service which is authenticated in spring boot application where the Httpmethod is POST,
Below I would like to show how all set up work to consume authenticated web service for HttpMethod.GET and then what changes I try to consume same authenticated web service for HttpMethod.POST and throws 401 Unauthorized ERROR,
RestTemplateFactory to get restTemplte,
public class RestTemplateFactory implements FactoryBean<RestTemplate>, InitializingBean {
#Autowired
private RestTemplate restTemplate;
public RestTemplate getObject() {
return restTemplate;
}
public Class<RestTemplate> getObjectType() {
return RestTemplate.class;
}
public boolean isSingleton() {
return true;
}
public void afterPropertiesSet() {
HttpHost host = new HttpHost("localhost", 9090, "http");
restTemplate = new RestTemplate(
new HttpComponentsClientHttpRequestFactoryBasicAuth(host));
}
}
For basic authentication,
public class HttpComponentsClientHttpRequestFactoryBasicAuth extends HttpComponentsClientHttpRequestFactory {
HttpHost host;
public HttpComponentsClientHttpRequestFactoryBasicAuth(HttpHost host) {
super();
this.host = host;
}
protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
return createHttpContext();
}
private HttpContext createHttpContext() {
AuthCache authCache = new BasicAuthCache();
BasicScheme basicAuth = new BasicScheme();
authCache.put(host, basicAuth);
BasicHttpContext localcontext = new BasicHttpContext();
localcontext.setAttribute(HttpClientContext.AUTH_CACHE, authCache);
return localcontext;
}
}
Calling a authenticated web service for HttpMethod.Get method,
public ResponseEntity<SomeResponse> consumeRestApi(SomeRequest request) throws InvalidDataException {
ResponseEntity<SomeResponse> responseEntity = null;
try {
RestTemplate restTemplate = restTemplateFactory.getRestTemplate();
restTemplate
.getInterceptors()
.add(new BasicAuthorizationInterceptor(username, pwd));
responseEntity = restTemplate.exchange("http://localhost:9090/sendMail?phone=60598745&email=abc#gmail.com", HttpMethod.GET, null, SomeResponse.class);
} catch (Exception e) {
// Exception handing...
}
return responseEntity;
}
And I do have dummy server running at localhost, with HttpMethod.Get and this is the authenticated service I'm trying to consume in above set up,
#RequestMapping(value = "/sendMail", method = RequestMethod.GET)
public ResponseEntity<SomeResponse> sendmail(#RequestParam String phone, #RequestParam String email){
SomeResponse response = SomeResponse.builder()
.id("101")
.type("formdata")
.fieldValues(getFieldValues(phone,email))
.build();
return new ResponseEntity<>(response,HttpStatus.CREATED);
}
When its a HttpMethod.GET method it works perfectly fine with all the set up mentioned above,
Now, I want to change the same web service to be consumed, to accept a HttpMethod.POST
So below are the changes I tried out but it throw back an error of 401 i.e. Unauthorized error
The changes I try for post method,
By keeping the RestTemplateFactory and HttpComponentsClientHttpRequestFactoryBasicAuth same
I first change, the rest api on dummy server to accept request with POST so,
#RequestMapping(value = "/sendMail", method = RequestMethod.POST)
public ResponseEntity<SomeResponse> sendmail(#RequestParam String phone, #RequestParam String email){
// Same as above
}
Next change is calling method with method post,
public ResponseEntity<SomeResponse> consumeRestApi(SomeRequest request) throws InvalidDataException {
ResponseEntity<SomeResponse> responseEntity = null;
try {
RestTemplate restTemplate = restTemplateFactory.getRestTemplate();
restTemplate
.getInterceptors()
.add(new BasicAuthorizationInterceptor(username, pwd));
SomeResponse response = restTemplate.postForObject("http://localhost:9090/sendMail?phone=60598745&email=abc#gmail.com",request, SomeResponse.class);
} catch (Exception e) {
// Exception handling....
}
}
return new ResponseEntity<SomeResponse>(HttpStatus.CREATED);
}
Does anyone has any suggestion where I'm going wrong with this,
Thanks in advance.
I guess the issue lies in your way of creating and configuring the RestTemplate. Spring Boot provides the RestTemplateBuilder to construct a RestTemplate and it has builder methods to do additional configuration.
In addition the RestTemplate is thread safe so instead of re-recreating it to use it you can reuse the created instance. That being said your calling class can be refactored to something like this
public class EndpointTester {
private final RestTemplate rest;
public EndpointTester(RestTemplateBuilder rtb, String username, String pwd) {
this.rest = rtb.basicAuthorization(username, pwd).build();
}
public ResponseEntity<SomeResponse> consumeRestApi(SomeRequest request) throws InvalidDataException {
ResponseEntity<SomeResponse> responseEntity = null;
try {
responseEntity = rest.postForEntity("http://localhost:9090/sendMail?phone=60598745&email=abc#gmail.com", null, SomeResponse.class);
} catch (Exception e) {
// Exception handing...
}
return responseEntity;
}
}
This way you don't need your RestTemplateFactory and HttpComponentsClientHttpRequestFactoryBasicAuth which simplifies your configuration and code.

Spring Boot : Apache CXF SOAP with #RestController

I am making Spring Boot rest service using #RestController, in same project I am also exposing the Apache CXF SOAP service like
#RestController Code
#RestController
#RequestMapping(value = "/mobileTopUpService")
public class TopUpRestService {
#RequestMapping(value="/processTopUpRequest", method=RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE, produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<TopUpRequestDTO> processTopUpRequest(HttpServletRequest httpServletRequest, #Valid RequestEntity<TopUpRequestDTO> _requestEntity) {
return new ResponseEntity<>(new exampleDTO("hi"), HttpStatus.OK);
}
}
Apache CXF SOAP
#Configuration
#Import(ApplicationConfiguration.class)
public class WebServiceConfig
{
public static final String SERVLET_MAPPING_URL_PATH = "/*";
public static final String SERVICE_NAME_URL_PATH = "/services";
#Autowired
private ApplicationConfiguration applicationConfiguration;
#Bean
public ServletRegistrationBean dispatcherServlet()
{
return new ServletRegistrationBean(new CXFServlet(), SERVLET_MAPPING_URL_PATH);
}
#Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus()
{
return new SpringBus();
}
#Bean
public ERSBackendService ersBackendServiceImpl()
{
return new ERSBackendServiceImpl();
}
#Bean
public Endpoint endpoint()
{
EndpointImpl endpoint = new EndpointImpl(springBus(), ersBackendServiceImpl());
endpoint.publish(SERVICE_NAME_URL_PATH);
AutomaticWorkQueue executorQueue = createThreadPoolExecutorQueue();
endpoint.setExecutor(executorQueue);
return endpoint;
}
#Bean
public EmbeddedServletContainerFactory embeddedServletContainerFactory()
{
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory("/backend-service", Integer.valueOf(applicationConfiguration.getPort()));
return factory;
}
}
SOAP Service are running fine after change but REST (#RestController) stop working, but if I disables the methods
// #Bean
// public ServletRegistrationBean dispatcherServlet()
// {
// return new ServletRegistrationBean(new CXFServlet(), SERVLET_MAPPING_URL_PATH);
// }
and
#Bean
// public EmbeddedServletContainerFactory embeddedServletContainerFactory()
// {
// TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory("/backend-service", Integer.valueOf("8007"));
// return factory;
// }
//}
and run
http://localhost:8007/mobileTopUpService/processTopUpRequest/
the #RestController runs fine but not soap.
I need to run both #RestController and CXF SOAP, kindly suggest.
thanks
I've just working with SOAP and REST servicies together. Here's my configuration: (At the end of the answer, I included a sample project)
application.properties
cxf.path=/services
cxf.servlet.load-on-startup=-1
WebServiceConfig
#Configuration
#ConditionalOnWebApplication
public class WebServiceConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(WsEndpointsConfiguration.class);
#Autowired
private Bus bus;
#Value("${cxf.path}")
private String cxfServletPath;
#Autowired
private YourServiceInterface yourService;
public Logger getLOGGER() {
return LOGGER;
}
public Bus getBus() {
return bus;
}
public String getCxfServletPath() {
return cxfServletPath;
}
public void setCxfServletPath(String cxfServletPath) {
this.cxfServletPath = cxfServletPath;
}
public YourServiceInterface getYourServiceInterface() {
return yourService;
}
#Bean
public Endpoint yourWebServiceEndpoint() {
EndpointImpl endpoint = new EndpointImpl(getBus(), new YourWebServiceEndpoint(getYourServiceInterface()));
endpoint.publish("/YourWebService");
return endpoint;
}
#Bean
public FilterRegistrationBean openEntityManagerInViewFilter() {
FilterRegistrationBean filterRegBean = new FilterRegistrationBean();
filterRegBean.setFilter(new OpenEntityManagerInViewFilter());
List<String> urlPatterns = new ArrayList<String>();
urlPatterns.add(getCxfServletPath() + "/*");
filterRegBean.setUrlPatterns(urlPatterns);
if (getLOGGER().isDebugEnabled()) {
getLOGGER().debug("Registering the 'OpenEntityManagerInViewFilter' filter for the '"
.concat(getCxfServletPath() + "/*").concat("' URL."));
}
return filterRegBean;
}
}
Replace the #Autowired service with your own service interface.
You could check a full example here:
https://github.com/jcagarcia/proofs/tree/master/spring-security-and-formatters
Related classes from the sample provided above:
Configuration class
WebService API
WebService Implementation
application.properties
Hope it helps,
I resolved it as #EnableWebMvc in class where starting boot app
i.e SpringApplication.run(ApplicationStartup.class, args);
Moved ServletRegistrationBean in spring boot class too,
disbaled method
#Bean
public EmbeddedServletContainerFactory embeddedServletContainerFactory() {...}

Spring Integration Email Redelivery on Exception

I have a web service that via a GET Http method, the user requests for a person object. This person is sent to a JMS Queue and then with the help of Spring Integration, I send it to a fake email address (https://papercut.codeplex.com/). I have written the code with Spring Integration Java DSL. I would like to ask:
Is there a more flexible way to send the email message?
If an exception is thrown, how can the mail be redelivered with the help of Spring Integration? (e.g. for 5 times and if it is not sent then the exception gets handled and the program stops)
Here is my code:
Web Service
public Person findById(Integer id) {
Person person = jpaPersonRepository.findOne(id);
jmsTemplate.convertAndSend("testQueue", person);
return jpaPersonRepository.findOne(id);
}
Java Confiuration
#Configuration
#EnableIntegration
#ComponentScan
public class JavaConfig {
private static final String DEFAULT_BROKER_URL = "tcp://localhost:61616";
private static final String DEFAULT_QUEUE = "testQueue";
#Bean
public ActiveMQConnectionFactory connectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(DEFAULT_BROKER_URL);
return connectionFactory;
}
#Bean
public JmsTemplate jmsTemplate() {
JmsTemplate template = new JmsTemplate();
template.setConnectionFactory(this.connectionFactory());
template.setDefaultDestinationName(DEFAULT_QUEUE);
return template;
}
#Bean
public DefaultMessageListenerContainer defaultMessageListenerContainer() {
DefaultMessageListenerContainer defaultMessageListenerContainer = new DefaultMessageListenerContainer();
defaultMessageListenerContainer.setDestinationName(DEFAULT_QUEUE);
defaultMessageListenerContainer.setConnectionFactory(this.connectionFactory());
return defaultMessageListenerContainer;
}
#Bean(name="inputChannel")
public DirectChannel directChannel() {
return new DirectChannel();
}
#Bean
public IntegrationFlow orders() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(defaultMessageListenerContainer()))
.transform(new ObjectToStringTransformer())
.enrichHeaders(p -> p.header(MailHeaders.TO, "Papercut0#test.com"))
.handle(Mail.outboundAdapter("127.0.0.1")
.credentials("test","test").port(25)
.javaMailProperties(p -> p.put("mail.debug", "true")),
e -> e.id("sendMailEndpoint"))
.get();
}
}
Is there a more flexible way to send the email message?
Sorry, the question isn't clear. You have enough short code to do that. Mail.outboundAdapter() and all its fluent API. What should be more flexible?
If an exception is thrown, how can the mail be redelivered with the help of Spring Integration?
For this purpose Spring Integration suggests RequestHandlerRetryAdvice. And Mail.outboundAdapter() can be configured with that as:
#Bean
public Advice retryAdvice() {
RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice();
RetryTemplate retryTemplate = new RetryTemplate();
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(5);
retryTemplate.setRetryPolicy(retryPolicy);
advice.setRetryTemplate(retryTemplate);
advice.setRecoveryCallback(new ErrorMessageSendingRecoverer(emailErrorChannel()));
return advice;
}
...
.handle(Mail.outboundAdapter("127.0.0.1")
.credentials("test","test").port(25)
.javaMailProperties(p -> p.put("mail.debug", "true")),
e -> e.id("sendMailEndpoint")
.advice(retryAdvice())) // HERE IS THE TRICK!
See its JavaDocs and Reference Manual on the matter.

Creating a new Queue using Spring AMQP on web application startup

I am trying to create a new queue in RabbitMQ using Spring AMQP on server startup of my web application. I am not getting the exact configuration code how to achieve it.
Below is my code snippet. Please correct the following.
#Configuration
public class RabbitMQConfiguration {
#Bean
public ConnectionFactory rabbitConnectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("10.165.18.29");
connectionFactory.setUsername("User");
connectionFactory.setPassword("user");
return connectionFactory;
}
#Bean
public SimpleMessageListenerContainer messageListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(rabbitConnectionFactory());
container.addQueueNames("create.queue");
container.setMessageListener(exampleListener());
return container;
}
#Bean
public MessageListener exampleListener() {
return new MessageListener() {
public void onMessage(Message message) {
System.out.println("received: " + message);
}
};
}
}
See the documentation.
Simply add <rabbit:queue ... /> beans and a <rabbit:admin ... /> and the admin will automatically declare the queues when the connection is first established.

how to set queue/message durability to false in Spring AMQP using annotations?

I wrote sample spring amqp producer which is running on RabbitMQ server which sends messages and consuming those messages uisng MessageListener using Spring AMQP. Here, I want to set queue and message durability to false. Could you please any one help me on how to set "durable" flag to false using annotations.
Here is sample code
#Configuration
public class ProducerConfiguration {
protected final String queueName = "hello.queue";
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setRoutingKey(this.queueName);
template.setQueue(this.queueName);
return template;
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
return connectionFactory;
}
}
public class Producer {
public static void main(String[] args) throws Exception {
new Producer().send();
}
public void send() {
ApplicationContext context = new AnnotationConfigApplicationContext(
ProducerConfiguration.class);
RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
for (int i = 1; i <= 10; i++) {
rabbitTemplate.convertAndSend(i);
}
}
}
Thanks in Advance.
#Configuration
public class Config {
#Bean
public ConnectionFactory connectionFactory() {
return new CachingConnectionFactory();
}
#Bean
public Queue foo() {
return new Queue("foo", false);
}
#Bean
public RabbitAdmin rabbitAdmin() {
return new RabbitAdmin(connectionFactory());
}
}
The rabbit admin will declare the queue the first time the connection is opened. Note that you can't change a queue from durable to not; delete it first.