Retreiving the SOAP message context from inside the service endpoint - java

I have a spring boot application which expose SOAP web service using spring-boot-starter-web-services.
I'am getting the request's messageContext using EndpointInterceptor
#Component
public class GlobalEndpointInterceptor implements EndpointInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
//Here I get the messageContext
}
}
In my service EndPoint I have :
#Endpoint
public class CountryEndpoint {
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "addCountryRequest")
#ResponsePayload
public AddCountryResponse addCountry(#RequestPayload AddCountryRequest request) {
//Insert country and get the autogenerated ID
//Insert the country ID along with the messageContext retreived from the intercepter. I can't get the messageContext here !
}
}
How can I retreive the message context inside my service endpoint

You can do the following to get the SOAP Envelope:
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "addCountryRequest")
#ResponsePayload
public AddCountryResponse addCountry(#RequestPayload AddCountryRequest request, MessageContext context) {
//Insert country and get the autogenerated ID
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
messageContext.getRequest().writeTo(outputStream);
String httpMessage = new String(outputStream.toByteArray());
System.out.println(httpMessage);
} catch (IOException e) {
e.printStackTrace();
}
}

Related

Feign Client Error Handling - Suppress the Error/Exception and convert to 200 success response

I am using feign client to connect to downstream service.
I got a requirement that when one of the downstream service endpoint returns 400 ( it's partial success scenario ) our service need this to be converted to 200 success with the response value.
I am looking for a best way of doing this.
We are using error decoder to handle the errors and the above conversion is applicable for only one endpoint not for all the downstream endpoints and noticed that decode() method should returns exception back.
You will need to create a customized Client to intercept the Response early enough to change the response status and not invoke the ErrorDecoder. The simplest approach is to create a wrapper on an existing client and create a new Response with a 200 status. Here is an example when using Feign's ApacheHttpClient:
public class ClientWrapper extends ApacheHttpClient {
private ApacheHttpClient delegate;
public ClientWrapper(ApacheHttpClient client) {
this.client = client;
}
#Override
public Response execute(Request request, Request.Options options) throws IOException {
/* execute the request on the delegate */
Response response = this.client.execute(request, options);
/* check the response code and change */
if (response.status() == 400) {
response = Response.builder(response).status(200).build();
}
return response;
}
}
This customized client can be used on any Feign client you need.
Another way of doing is by throwing custom exception at error decoder and convert this custom exception to success at spring global exception handler (using #RestControllerAdvice )
public class CustomErrorDecoder implements ErrorDecoder {
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() == 400 && response.request().url().contains("/wanttocovert400to200/clientendpoints") {
ResponseData responseData;
ObjectMapper mapper = new ObjectMapper();
try {
responseData = mapper.readValue(response.body().asInputStream(), ResponseData.class);
} catch (Exception e) {
responseData = new ResponseData();
}
return new PartialSuccessException(responseData);
}
return FeignException.errorStatus(methodKey, response);
}}
And the Exception handler as below
#RestControllerAdvice
public class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(PartialSuccessException.class)
public ResponseData handlePartialSuccessException(
PartialSuccessException ex) {
return ex.getResponseData();
}
}
Change the microservice response:
public class CustomFeignClient extends Client.Default {
public CustomFeignClient(
final SSLSocketFactory sslContextFactory, final HostnameVerifier
hostnameVerifier) {
super(sslContextFactory, hostnameVerifier);
}
#Override
public Response execute(final Request request, final Request.Options
options) throws IOException {
Response response = super.execute(request, options);
if (HttpStatus.SC_OK != response.status()) {
response =
Response.builder()
.status(HttpStatus.SC_OK)
.body(InputStream.nullInputStream(), 0)
.headers(response.headers())
.request(response.request())
.build();
}
return response;
}
}
Add a Feign Client Config:
#Configuration
public class FeignClientConfig {
#Bean
public Client client() {
return new CustomFeignClient(null, null);
}
}

Soap Web services with Apache Camel and Apache CXF

I am implementing SOAP web services with Apache CXF. I am using Jboss EAP server. I have used following code to expose SOAP web services.
CxfComponent cxfComponent = new CxfComponent(context);
CxfEndpoint serviceEndpoint = new CxfEndpoint(FPSoapServiceConstants.WSDL_CONFIG_URI, cxfComponent);
serviceEndpoint.setDataFormat(DataFormat.PAYLOAD);
serviceEndpoint.setServiceClass(com.fp.en.webservices.fulfillment.FulfillmentService.class);
HashMap<String, Object> properties = new HashMap<>();
properties.put("faultStackTraceEnabled", true);
properties.put("exceptionMessageCauseEnabled", true);
serviceEndpoint.configureProperties(properties);
serviceEndpoint.setLoggingFeatureEnabled(true);
context.addEndpoint(FPSoapServiceConstants.SOAP_ENDPOINT_FULFILLMENT_SERVICE, serviceEndpoint);
I am using apache camel to process incoming soap message
route.process(fpSOAPRequestProcessor).process(xyzProcessor).process(fpSOAPResponseProcessor)
I want to get all parameters in an object I created a class and try to get body
BuyProductRequest buyRequest = message.getBody(BuyProductRequest.class);
but this is giving me null. But when I try to get
String buyRequest = message.getBody(String.class);
It is giving me SOAP message So I have to convert xml SOAP message to Object by JAXB Marshaller.
Processor code is as follows
public class FPSoapRequestProcessor implements Processor{
#Override
public void process(Exchange exchange) throws Exception {
Message message = exchange.getIn();
String operation = String.valueOf(exchange.getIn().getHeader("operationName"));
if(FPSoapServiceConstants.BUY_PRODUCT_SOAP_OPERATION.equalsIgnoreCase(operation)) {
populateBuyProductOperationProperties(message);
}
}
private void populateBuyProductOperationProperties(Message message) {
String buyRequest = message.getBody(String.class);
BuyProductRequest productInfo= parseRequest(buyRequest);
message.setHeader("MSISDN", productInfo.getMsisdn());
message.setHeader("iname", productInfo.getIname());
message.setHeader("input", productInfo.getInput());
message.setHeader("username", productInfo.getUserName());
message.setHeader("password", productInfo.getPassword());
message.setHeader("soapConversion", true);
}
private BuyProductRequest parseRequest(String soapRequest){
try(InputStream is = new ByteArrayInputStream(soapRequest.getBytes())) {
JAXBContext jaxbContext = JAXBContext.newInstance(BuyProductRequest.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
return (BuyProductRequest) jaxbUnmarshaller.unmarshal(is);
} catch (Exception e) {
throw new RuntimeException("SOAP Request Object Resolving Error",e);
}
}
}
So Is there any simple way to construct request object and Similarly at that time when I am done with processing, in fpSOAPResponseProcessor I have to convert my object into soap string then I am sending it.
fpSoapResponseProcessor code is as follows
public class FDPSoapResponseProcessor implements Processor{
#Override
public void process(Exchange exchange) throws Exception {
Message message = exchange.getIn();
FulfillmentResponse response = XmlUtil.unmarshall(message.getBody(String.class), FulfillmentResponse.class);
BuyProductResponse buyProductResponse = new BuyProductResponse();
buyProductResponse.setProductResponse(response);
String soapResponse = parse(buyProductResponse);
exchange.getOut().setBody(soapResponse);
}
private String parse(BuyProductResponse buyProductResponse) {
try(StringWriter writer = new StringWriter()){
JAXBContext jContext = JAXBContext.newInstance(BuyProductResponse.class);
Marshaller marshallObj = jContext.createMarshaller();
marshallObj.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshallObj.setProperty("com.sun.xml.bind.xmlDeclaration", Boolean.FALSE);
marshallObj.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
marshallObj.setProperty("com.sun.xml.bind.namespacePrefixMapper", new FulfillmentResponseMapper());
marshallObj.marshal(buyProductResponse, writer);
return writer.toString();
} catch(Exception e) {
throw new RuntimeException("SOAP Request String Parsing Error",e);
}
}
private static class FulfillmentResponseMapper extends NamespacePrefixMapper {
#Override
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
if(FPSoapServiceConstants.SOAP_NAMESPACE_URI.equalsIgnoreCase(namespaceUri)) {
return FPSoapServiceConstants.SOAP_PREFIX;
}
return suggestion;
}
#Override
public String[] getPreDeclaredNamespaceUris() {
return new String[] { FPSoapServiceConstants.SOAP_NAMESPACE_URI};
}
}
}
Please suggest a proper simple way If there is?
AFAICS, this is the simple way.

Spring-WS: How to include a soapheader in the response message

I'm using Spring-WS to build a web service (contract first). I defined an endpoint like below
#Endpoint
public class ReportingEndpoint {
private static final Logger LOGGER = LoggerFactory.getLogger(ReportingEndpoint.class);
private static final String NAMESPACE_URI = "http://localhost/reporting";
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "RequestDocument")
#ResponsePayload
public ResponseDocument accountReporting(
#RequestPayload JAXBElement<RequestDocument> request,
#SoapHeader(value = "{http://localhost/reporting}Hdr") SoapHeaderElement header) {
try {
ApplicationHeader headers = ((JAXBElement<ApplicationHeader>) JAXBUtils
.unmarshal(header.getSource(), ObjectFactory.class)).getValue();
LOGGER.info(headers.getSystemName());
LOGGER.info("Hello world.");
ResponseDocument response = new ResponseDocument();
response.setReportTitle("Report Title");
return response;
} catch (Exception ex) {
return null;
}
}
}
This code can receive and read the soap header sent from client but when I return a response message, I don't know how to send back to client the server soap header as the client did.
Can anybody help me to solve this issue?

Getting content of a SOAP Header using Spring WS

I'm trying to build an endpoint that will receive SOAP messages from a client. The message I'm receiving contains a username and password inside the soap header ...
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="http://www.company.com/Application">
<soapenv:Header xmlns:wsse="http://__________.xsd">
<wsse:Security >
<wsse:UsernameToken>
<wsse:Username>username</wsse:Username>
<wsse:Password>password</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
<soapenv:Body>
I'm using Spring WS - the obvious solution is to create a filter inside web.xml that will bypass Spring WS completely, parse the SOAP message, extract the username and password and then continue to Spring WS which will parse the SOAP again.
Is there a way to get the content of the header without circumventing Spring WS?
I've tried adding a bean inside sws:interceptors:
<sws:interceptors>
<!-- extract Security details from Header -->
<bean class="com.company.application.service.SecurityInterceptorService" />
<!-- log full Body of request -->
<bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>
<!-- validate Request against XSD to make sure it's a valid request -->
<bean id="CompanyApplication" class="com.company.application.interceptor.ValidatingInterceptor">
<property name="schema" value="/WEB-INF/_______________.xsd" />
<property name="validateRequest" value="true" />
<property name="validateResponse" value="true" />
</bean>
</sws:interceptors>
and then implementing that class:
public class SecurityInterceptorService implements EndpointInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
System.out.println("---------------");
System.out.println("handleRequest") ;
System.out.println("---------------");
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception {
System.out.println("---------------");
System.out.println("handleResponse");
System.out.println("---------------");
return true;
}
#Override
public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception {
System.out.println("---------------");
System.out.println("handleFault");
System.out.println("---------------");
return true;
}
#Override
public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) throws Exception {
System.out.println("---------------");
System.out.println("afterCompletion");
System.out.println("---------------");
}
}
endpoint only contains data about the endpoint inside handleRequest and after traversing through many layers and layers inside messageContext while in debug mode, I can't seem to spot the content of the header.
Is the content I'm looking for inside messageContext and if so, how do I access it?
From the messageContext object, you can retrieve either the request or the response (In your case, I guess you need the request).
The request/response is basically a WebServiceMessage. If you examine the webServiceMessage, you will see that the object can be casted to a SoapMessage. From the soap message, you can now get the soap header.
WebServiceMessage webServiceMessageRequest = messageContext_.getRequest();
SoapMessage soapMessage = (SoapMessage) webServiceMessageRequest;
SoapHeader soapHeader = soapMessage.getSoapHeader()
Afterwards, You might want to get the source object and convert it to a DOMSource object and then get the Node object which make the information retrieval much easier.
Source bodySource = soapHeader .getSource();
DOMSource bodyDomSource = (DOMSource) bodySource;
Node bodyNode = _bodyDomSource.getNode();
If you are using spring-boot you can use this kind of configuration:
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
PayloadValidatingInterceptor validatingInterceptor = new PayloadValidatingInterceptor();
validatingInterceptor.setValidateRequest(true);
validatingInterceptor.setValidateResponse(true);
validatingInterceptor.setXsdSchema(resourceSchema());
interceptors.add(validatingInterceptor);
}
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/api/*");
}
#Bean(name = "registros")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("ResourcePort");
wsdl11Definition.setLocationUri("/api");
wsdl11Definition.setTargetNamespace("http://resource.com/schema");
wsdl11Definition.setSchema(resourceSchema());
return wsdl11Definition;
}
#Bean
public XsdSchema resourceSchema() {
return new SimpleXsdSchema(new ClassPathResource("registro.xsd"));
}
}
In this example the addInterceptors method is the important one, the others 3 are basic to expose a WSDL API.
Maybe it'll be useful for someone else.
There is no easy way to unmarshall Soap headers with Spring-ws (it's currently not supported)
However, you can access the SoapHeaderElement in your #PayloadRoot annotated method, and do the process of unmarshalling with JAXB.
#Endpoint
public class SubmitEndpoint implements EndpointInterface {
private static final String NAMESPACE_URI = "http://www.example.com/namespace";
private Security unmarshallSecurityFromSoapHeader(SoapHeaderElement header) {
Security security = null;
try {
JAXBContext context = JAXBContext.newInstance(Security.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
security = (Security) unmarshaller.unmarshal(header.getSource());
} catch (JAXBException e) {
e.printStackTrace();
}
return security;
}
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "submit")
#ResponsePayload
public SubmitResponse submit(#RequestPayload Submit submit, #SoapHeader(
value = "{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}Security") SoapHeaderElement wsseSecurityHeader) throws JAXBException {
Security security = unmarshallSecurityFromSoapHeader(wsseSecurityHeader);
}
}
Security.java
#Getter
#Setter
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(namespace = Security.SECURITY_NS, name = "Security")
public class Security {
public static final String SECURITY_NS = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
#XmlElement(namespace = SECURITY_NS, name = "UsernameToken")
private UsernameToken usernameToken;
}
UsernameToken.java
#Getter
#Setter
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(namespace = Security.SECURITY_NS, name = "UsernameToken")
public class UsernameToken {
#XmlElement(name = "Username", namespace = Security.SECURITY_NS)
private String username;
#XmlElement(name = "Password", namespace = Security.SECURITY_NS)
private String password;
}

How to add soap header when making a soap request using the java objects generated by wsdl

I generated client java objects using JAX-WS RI. I am trying to make a SOAP request to a web service. Service requires authentication in the header which looks like below:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<xsd:authHeader>
<xsd:user>username#gmail.com</xsd:user>
<xsd:password>password1</xsd:password>
</xsd:authHeader>
</soapenv:Header>
<soapenv:Body>
<ns:searchAssetsParam>
<ns:includeSubfolders>true</ns:includeSubfolders>
<ns:resultsPage>2</ns:resultsPage>
</ns:searchAssetsParam>
</soapenv:Body>
</soapenv:Envelope>
The generated java objects have methods for calling the service, creating the objects and constructing the header. But, I am having trouble setting the header while making the call.
Here's the code that I am using:
IpsApiService service = new IpsApiService();
IpsApiPortType port = service.getIpsApiSoapPort();
SearchAssetsParam searchAssetsParam = buildSearchAssetsParam();
SearchAssetsReturn response = port.searchAssets(searchAssetsParam);
buildSearchAssetsParam() constructs the request object.
I created the header object as follows:
AuthHeader header = new AuthHeader();
header.setUser("username#gmail.com");
header.setPassword("password1");
How do I set this AuthHeader to the service request?
Thanks,
Venu
Once I had the same problem. I needed to modify the JAX-WS web service SOAP header at every request. To solve this problem I have created a handler like this:
public class MyHandler implements SOAPHandler<SOAPMessageContext> {
private static final Logger LOGGER = LoggerFactory.getLogger(MyHandler.class);
private String username;
private String password;
#Override
public boolean handleMessage(SOAPMessageContext context) {
try {
SOAPMessage message = context.getMessage();
SOAPHeader header = message.getSOAPHeader();
SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();
if (header == null) {
header = envelope.addHeader();
}
QName qNameUserCredentials = new QName("https://your.target.namespace/", "UserCredentials");
SOAPHeaderElement userCredentials = header.addHeaderElement(qNameUserCredentials);
QName qNameUsername = new QName("https://your.target.namespace/", "Username");
SOAPHeaderElement username = header.addHeaderElement(qNameUsername );
username.addTextNode(this.username);
QName qNamePassword = new QName("https://your.target.namespace/", "Password");
SOAPHeaderElement password = header.addHeaderElement(qNamePassword);
password.addTextNode(this.password);
userCredentials.addChildElement(username);
userCredentials.addChildElement(password);
message.saveChanges();
//TODO: remove this writer when the testing is finished
StringWriter writer = new StringWriter();
message.writeTo(new StringOutputStream(writer));
LOGGER.debug("SOAP message: \n" + writer.toString());
} catch (SOAPException e) {
LOGGER.error("Error occurred while adding credentials to SOAP header.", e);
} catch (IOException e) {
LOGGER.error("Error occurred while writing message to output stream.", e);
}
return true;
}
//TODO: remove this class after testing is finished
private static class StringOutputStream extends OutputStream {
private StringWriter writer;
public StringOutputStream(StringWriter writer) {
this.writer = writer;
}
#Override
public void write(int b) throws IOException {
writer.write(b);
}
}
#Override
public boolean handleFault(SOAPMessageContext context) {
LOGGER.debug("handleFault has been invoked.");
return true;
}
#Override
public void close(MessageContext context) {
LOGGER.debug("close has been invoked.");
}
#Override
public Set<QName> getHeaders() {
LOGGER.debug("getHeaders has been invoked.");
return null;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
}
It adds the needed parameters to my SOAP header and it is invoked on every request. All you need to do is to modify handleMessage method to suit your needs.
It works for me by overriding the public void setAttribute(String namespace, String localName, String value) method.
import javax.xml.namespace.QName;
import org.apache.axis.Constants;
import org.apache.axis.message.SOAPHeaderElement;
#SuppressWarnings("serial")
public class ADESHeaderElement extends SOAPHeaderElement
{
public ADESHeaderElement(QName qname, Object value)
{
super(qname, value);
}
#Override
public void setAttribute(String namespace, String localName, String value)
{
if (!Constants.ATTR_MUST_UNDERSTAND.equals(localName))
{ // Or any other attribute name you'd want to avoid
super.setAttribute(namespace, localName, value);
}
}
}
Create header element like this:
ADESHeaderElement custheader = new ADESHeaderElement(qname, clientserv);
custheader.setActor(null);
When you create your service from classess generated by cxf, add custom interceptor
Service service = new MyService(wsdlURL, new QName("http://myservice.com/MyService/", "MyService"));
MyPort port = service.getMyPort();
Client client = ClientProxy.getClient(port);
// adding interceptor programmatically
client.getOutInterceptors().add(new MyHeaderHandler());
Your can extend AbstractSoapInterceptor to implement your custom interceptor to handle message.
import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.headers.Header;
import org.apache.cxf.jaxb.JAXBDataBinding;
import org.apache.cxf.phase.Phase;
import com.rpc.core.utils.DomainContext;
public class MyHeaderHandler extends AbstractSoapInterceptor {
/**
* Constructor
*/
public MyHeaderHandler() {
super(Phase.PRE_LOGICAL);
}
#Override
public void handleMessage(org.apache.cxf.binding.soap.SoapMessage message) throws org.apache.cxf.interceptor.Fault {
try {
message.getHeaders().add(new Header(new QName("MyCustomHeader"),"value", new JAXBDataBinding(String.class)));
} catch (JAXBException e) {
e.printStackTrace();
}
};
}
}
Yes, I did the same that Rangappa Tungal, following this example:
Service w = new ServiceLocator();
ServiceSoap ws = new ServiceSoapStub(new URL(w.getServiceSoapAddress()),w); Stub mystub = (Stub) ws;
AuthHeader up = new AuthHeader("user","pass");
mystub.setHeader("namespace", "AuthHeader", up);
ws.get***();
Link to the example!

Categories