JAX-WS RI: Best way to implement a Method Interceptor - java

I want to provide my own method interceptor for my webservice invocations. Basically, this method interceptor should be called right before the real method is called... See the snippet below:
public class MyMethodInterceptor {
public Object invoke(Object t, Method m, Object[] args) throws Throwable {
// do some magic, such as tracing, authorise, etc...
return m.invoke(t, args);
}
}
// ....
public class MyWebServiceImpl implements MyWebServiceInterface {
public String greet(final String name) {
return "Hi there, " + name;
}
}
The idea is that everytime that the webservice gets invoked, it will be dispatched through my interceptor. I've looked at hooking up my own InstanceResolver, but it is getting out of control. I know how to do this in CXF and with JAX-RS (Jersey) + Guice.
JAX-WS provides handler-chains, but these handlers get invoked way too early (i.e., much before the method invocation), so I do not have the needed information at this point.
What is the best way to do this with the Referene Implementation of JAX-WS?

In a jax-ws handler you are just before the real thing, you have access to the content of entire SOAP message, what you need that isn't available yet?
EDIT:
Some examples, to use in the handler:
public String getMessage(SOAPMessageContext smc) {
SOAPMessage message = smc.getMessage();
ByteArrayOutputStream soapEnvelope = new ByteArrayOutputStream();
message.writeTo(soapEnvelope);
soapEnvelope.close();
return new String(soapEnvelope.toByteArray());
}
public String getMethod(SOAPMessageContext smc) {
SOAPMessage message = smc.getMessage();
SOAPBody body = message.getSOAPBody();
return body.getFirstChild().getLocalName();
}

Related

Synchronize the asynchronous callback method call

We are working on a solution which is like this;
Request: (We receive the request via API call and send to third-party via a library we use)
OUR-Client --> OUR-API --> THIRD-PARTY
Response: (This response we receive from third-party asynchronously through a callback method given in the library we are using)
THIRD-PARTY --> OUR-CODE --> OUR-Client
Here is the below code and want to get rid of Thread.sleep() call and make use of the callback to provide response.
----- API Method -------------
#GetMapping
public ResponseEntity<String> getData(#RequestBody String requestId) throws SessionNotFound, InterruptedException {
dataService.get(requestId);
String msg;
long start = System.currentTimeMillis();
do {
// We want to get rid of this sleep() statement and some way to callback here as soon there is message.
Thread.sleep(30);
msg = clientApp.getRespnse(requestId);
} while(msg == null);
return ResponseEntity.ok(msg);
}
------- Service Class and Methods ---------------
#Service
public class DataService {
#Autowired
private ClientApp clientApp;
public void get(String requestId) throws SessionNotFound {
// This method is from the library we use. This only submits the request, response is received on different method.
send(requestId);
}
------- Component Class and Methods ---------------
#Component
public class ClientFixApp {
private Map<String, String> responseMap = new HashMap<>();
// This method is callback from the third party library, whenever there is response this method will get invoked and this message we need to send as response of the API call.
#Override
public void onResponse(String requestId)
throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, UnsupportedMessageType {
responseMap.put(msgId, jsonMsg);
}
public String getRespnse(String requestId) {
return responseMap.get(requestId);
}
}
DataService and ClientFixApp are flawed by design (the very fact it is 2 different classes while there must be one, speaks a lot). Truly asynchronous programs must allow to register user procedure as a callack, called when the I/O operation finished (successfully or not). ClientFixApp silently writes the result in a table, leaving for client no other option except polling.
You can use any existing asynchronous http library. For example, below is the code for the library java.net.http included in Java 11 and later. Other libraries have similar functionality.
public static CompletableFuture<HttpResponse<String>> doGet(String uri) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
}
public static void main(String[] args) {
CompletableFuture<HttpResponse<String>> future = doGet("https://postman-echo.com/get");
future.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
}

JAXB.marshal blocking webservice call

I am using Spring WebserviceTemplate to make SOAP call to a service. I ran performance test to see how it behaves under load. I also have a interceptor to copy the header parameters from my incoming request over to the service I am calling.
#Component
public class HeaderPropagationInterceptor implements ClientInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
SoapMessage request = (SoapMessage) messageContext.getRequest();
Result result = request.getSoapHeader().getResult();
JAXB.marshal(getRequestHeader(), result);
return true;
}
When I ran the performance test , I see the below statement blocking for 4-5 seconds
JAXB.marshal(getRequestHeader(), result);
Is there a reason why this might be blocking?
The JAXB utility class will create the JAXBContext the first time it is called (expensive operation). It will be weakly cached, which means if memory runs low the context may be recycled, and recreated on the following call. You really should create your context and keep it explicitly. Something like this (as already suggested in the comments by others) should solve your problem:
#Component
public class HeaderPropagationInterceptor implements ClientInterceptor
{
private JAXBContext jaxbContext;
#PostConstruct
public void createJaxbContext() {
try {
jaxbContext = JAXBContext.newInstance(RequestHeader.class);
}
catch(JAXBException e) {
throw new IllegalStateException("Unable to create JAXBContext.", e);
}
}
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
SoapMessage request = (SoapMessage) messageContext.getRequest();
Result result = request.getSoapHeader().getResult();
jaxbContext.createMarshaller().marshal(getRequestHeader(), result);
return true;
}
}
You have to replace the RequestHeader.class with the actual class used by your code. If performance needs to be improved further, it's also possible to use a thread-local for reusing the marshaller, but you should probably do further profiling to verify that is really a bottleneck. Good luck with your project!

How to get the XML response body back from a JAX-WS client?

I've found a read a number of threads on here about how to retrieve the XML response from a JAX-WS client. In my case, the client is generated from the WSDL via Oracle's JDeveloper product and is going to invoke a Document/Literal service endpoint that was written in .NET. What I want to do is obtain the XML response from the call FROM the calling client, not from inside a handler.
The closest thread that I saw to this issue was:
http://www.coderanch.com/t/453537/Web-Services/java/capture-SoapRequest-xml-SoapResponse-xml
I don't think I want to generate a Dispatch call because the endpoint's XML schema for the SOAP packet is rather complex, and the automatic proxy makes the call trivial. Unless there is some other way to populate the generated bean(s) and then invoke some method that simply produces the XML and I then make the call?
private void storeSOAPMessageXml(SOAPMessageContext messageContext) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
SOAPMessage soapMessage = messageContext.getMessage();
try {
soapMessage.writeTo(baos);
String responseXml = baos.toString();
log.debug("Response: " + responseXml );
PaymentGatewayXMLThreadLocal.set(responseXml);
} catch (SOAPException e) {
log.error("Unable to retrieve SOAP Response message.", e);
} catch (IOException e) {
log.error("Unable to retrieve SOAP Response message.", e);
}
}
My thought was to store the response to the call in a ThreadLocal inside the handler and then read it after the call. Is that reasonable? So after the handler does the above code in the handleMessage and handleFault, the client calling code invokes this method:
#Override
public String getSOAPResponseXML(Object clientstub) {
String returnValue = PaymentGatewayXMLThreadLocal.get();
return returnValue;
} // getSOAPResponseXML
It appears there may be another way after all. After reading jax-ws-handlers, I saw that the handler can introduce an Application scoped variable. I changed the handler to do this:
private void storeSOAPMessageXml(SOAPMessageContext messageContext) {
String xml = getSOAPMessageXml(messageContext);
// YourPayXMLThreadLocal.set(xml);
// put it into the messageContext as well, but change scope
// default of handler Scope, and client can't read it from responsecontext!
messageContext.put(SOAP_RESPONSE_XML, xml);
messageContext.setScope(SOAP_RESPONSE_XML, MessageContext.Scope.APPLICATION );
} // storeSOAPMessageXml
The client just reads it like this:
#Override
public String getSOAPResponseXML(Object clientstub) {
String returnValue = null;
// works (assuming a threadlocal is ok)
// returnValue = YourPayXMLThreadLocal.get();
BindingProvider bindingProvider = (BindingProvider) clientstub;
// Thought this would work, but it doesn't - it returns null.
// Map<String, Object> requestContext = bindingProvider.getRequestContext();
// String returnValue = (String) requestContext.get(JaxWsClientResponseXmlHandler.SOAP_RESPONSE_XML);
// this works!!
Map<String, Object> responseContext = bindingProvider.getResponseContext();
System.out.println("has key? " + responseContext.containsKey(JaxWsClientResponseXmlHandler.SOAP_RESPONSE_XML));
returnValue = (String) responseContext.get(JaxWsClientResponseXmlHandler.SOAP_RESPONSE_XML);
return returnValue;
} // getSOAPResponseXML
If you just want to see the request, you can use the system property
-Dcom.sun.xml.ws.transport.http.client.HttpTransportPipe.dump=true
If you actually want to do something with the request, then a handler seems the natural solution. Perhaps use the request context to pass values to the handler? On the client:
((BindingProvider) port).getRequestContext().put("KEY", "VALUE");
In the handler:
String value = (String) messageContext.get("KEY");
Unfortunately, the only way to get the XML before sending it and without using Message Handlers is to marshall it your self (see JAXB). This will give you an XML representation of the data, however it might not LOOK exactly the same as the message sent to the WS. Differences might arise in the way that namespaces are used, ect., but most importantly you will not get the whole SOAP envelope, just the XML data for the header you choose to marshall.

How can I pass data back from a SOAP handler to a webservice client?

(Following up on this question: Getting raw XML response from Java web service client)
I've got a SOAP message handler that is able to get the raw XML of a web service response. I need to get this XML into the webservice client so I can perform some XSL transformations on the response before sending it on its way. I'm having trouble figuring out a good way to get data from a SOAP handler that catches incoming messages, and makes the raw XML available to a generated (from a WSDL) web service client. Any ideas if this is even feasible?
I've come up with something like this:
public class CustomSOAPHandler implements javax.xml.ws.handler.soap.SOAPHandler<javax.xml.ws.handler.soap.SOAPMessageContext>
{
private String myXML;
public String getMyXML()
{
return myXML;
}
...
public boolean handleMessage(SOAPMessageContext context)
{
...
myXML = this.getRawXML(context.getMessage());
}
//elsewhere in the application:
...
myService.doSomething(someRequest);
for (Handler h: ((BindingProvider)myService).getBinding().getHandlerChain())
{
if (h instanceof CustomSOAPHandler )
{
System.out.println("HandlerResult: "+ ((CustomSOAPHandler )h).getMyXML());
}
}
In very simple tests, this seems to work. But this solution feels somewhat like a cheap hack. I don't like setting the raw XML as a member of the chain handler, and I have a gut feeling this violates many other best practices. Does anyone have a more elegant way of doing this?
The two choices that seemed to work for me are both documented here. I didn't receive a response yet about whether using a ThreadLocal was fine or not, but I don't see why it shouldn't be.
My secoond method which was added to the original question was to go the route of the handler. While debugging the WS callout, I noticed that the invocationProperties map had the SOAP response as part of an internal packet structure within the responseContext object, but there appeared to be no way of getting to it. The ResponseContext was a set of name value pairs. However, when I read the source code for ResponseContext at this location, I saw that the code for the get method had a comment about returning null if it could not find an Application Scoped property, otherwise, it would read it from the packet invocationProperties, which seemed to be what I wanted. So I seached on how to set the scope on the key/value pair (Google: setting application-scope property for jaxws) that the context was introducing it low-and-behold, it was in the jax-ws spec that I referenced in the other thread.
I also did some reading about the Packet, https://jax-ws.java.net/nonav/jax-ws-20-fcs/arch/com/sun/xml/ws/api/message/Packet.html.
I hope this makes some sense for you. I was concerned that three wouldn't be anything to use JAXB against if the web service call resulted in a Soap FAULT, and I really wanted to log this packet, since it was being returned from a Payment Gateway which to this day has a number of undocumented results.
Good luck.
The solution was to use JAXB to convert the objects back to XML. I didn't really want to do this because it seems redundant to have the webservice client receive XML, convert it to a POJO, only to have that POJO converted back to XML, but it works.
Example of handler that passes out request / response message bodies:
public class MsgLogger implements SOAPHandler<SOAPMessageContext> {
public static String REQEST_BODY = "com.evil.request";
public static String RESPONSE_BODY = "com.evil.response";
#Override
public Set<QName> getHeaders() {
return null;
}
#Override
public boolean handleMessage(SOAPMessageContext context) {
SOAPMessage msg = context.getMessage();
Boolean beforeRequest = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(32_000);
context.getMessage().writeTo(baos);
String key = beforeRequest ? REQEST_BODY : RESPONSE_BODY;
context.put(key, baos.toString("UTF-8"));
context.setScope(key, MessageContext.Scope.APPLICATION);
} catch (SOAPException | IOException e) { }
return true;
}
#Override
public boolean handleFault(SOAPMessageContext context) {
return true;
}
#Override
public void close(MessageContext context) { }
}
To register handler and use preserved properties:
BindingProvider provider = (BindingProvider) port;
List<Handler> handlerChain = bindingProvider.getBinding().getHandlerChain();
handlerChain.add(new MsgLogger());
bindingProvider.getBinding().setHandlerChain(handlerChain);
Req req = ...;
Rsp rsp = port.serviceCall(req); // call WS Port
// Access saved message bodies:
Map<String, Object> responseContext = provider.getResponseContext();
String reqBody = (String) responseContext.get(MsgLogger.REQEST_BODY);
String rspBody = (String) responseContext.get(MsgLogger.RESPONSE_BODY);
TL;DR
Metro JAX WS RI docs says about MessageContext.Scope.APPLICATION property:
The message context object can also hold properties set by the client or provider. For instance, port proxy and dispatch objects both extend BindingProvider. A message context object can be obtained from both to represent the request or response context. Properties set in the request context can be read by the handlers, and the handlers may set properties on the message context objects passed to them. If these properties are set with the scope MessageContext.Scope.APPLICATION then they will be available in the response context to the client. On the server end, a context object is passed into the invoke method of a Provider.
metro-jax-ws/jaxws-ri/rt/src/main/java/com/sun/xml/ws/api/message/Packet.java contains property:
/**
* Lazily created set of handler-scope property names.
*
* <p>
* We expect that this is only used when handlers are present
* and they explicitly set some handler-scope values.
*
* #see #getHandlerScopePropertyNames(boolean)
*/
private Set<String> handlerScopePropertyNames;
On other hand metro-jax-ws/jaxws-ri/rt/src/main/java/com/sun/xml/ws/client/ResponseContext.java is an implementation of Map with:
public boolean containsKey(Object key) {
if(packet.supports(key))
return packet.containsKey(key); // strongly typed
if(packet.invocationProperties.containsKey(key))
// if handler-scope, hide it
return !packet.getHandlerScopePropertyNames(true).contains(key);
return false;
}
In SOAPHandler we can mark property as APPLICATION instead of default MessageContext.Scope.HANDLER:
/**
* Property scope. Properties scoped as <code>APPLICATION</code> are
* visible to handlers,
* client applications and service endpoints; properties scoped as
* <code>HANDLER</code>
* are only normally visible to handlers.
*/
public enum Scope {APPLICATION, HANDLER};
by:
/**
* Sets the scope of a property.
*
* #param name Name of the property associated with the
* <code>MessageContext</code>
* #param scope Desired scope of the property
* #throws java.lang.IllegalArgumentException if an illegal
* property name is specified
*/
public void setScope(String name, Scope scope);
As an alternative, instead of putting request/response details into the soap context, (in my case it did not work), you can put it into the ThreadLocal. So you need SOAPHandler, that #gavenkoa described (ty), but add it to the ThreadLocal instance, instead of the soap context.

How can I override the decisions made during JAX-RS Content Negotiation?

I'm using RESTEasy 2.2.1.GA as my JAX-RS implementation to create a client to connect to a third party service provider. (Education.com's REST API if it matters)
To make sure I haven't missed an important implementation detail here are code samples:
Service Interface
#Path("/")
public interface SchoolSearch {
#GET
#Produces({MediaType.APPLICATION_XML})
Collection<SchoolType> getSchoolsByZipCode(#QueryParam("postalcode") int postalCode);
}
Calling Class
public class SimpleSchoolSearch {
public static final String SITE_URL = "http://api.education.com/service/service.php?f=schoolSearch&key=****&sn=sf&v=4";
SchoolSearch service = ProxyFactory.create(SchoolSearch.class, SITE_URL);
public Collection<SchoolType> getSchools() throws Exception {
Collection<SchoolType> schools = new ArrayList<SchoolType>();
Collection<SchoolType> response = service.getSchoolsByZipCode(35803);
schools.addAll(response);
return schools;
}
}
After setting up tests to make this call, I execute and see the following exception being thrown.
org.jboss.resteasy.plugins.providers.jaxb.JAXBUnmarshalException: Unable to find JAXBContext for media type: text/html;charset="UTF-8"
From reading the RESTEasy/JAX-RS documentation, as I understand it, when the response is returned to the client, prior to the unmarshaling of the data, a determination is made (Content Negotiation??) about which mechanism to use for unmarshalling. (I think we're talking about a MessageBodyReader here but I'm unsure.) From looking at the body of the response, I see that what is returned is properly formatted XML, but the content negotiation (via HTTP header content-type is indeed text/html;charset ="UTF-8") is not allowing the text to be parsed by JAXB.
I think that the implementation is behaving correctly, and it is the service that is in error, however, I don't control the service, but would still like to consume it.
So that being said:
Am I correct in my understanding of why the exception is thrown?
How do I work around it?
Is there a simple one line annotation that can force JAXB to unmarshal the data, or will I need to implement a custom MessageBodyReader? (If that is even the correct class to implement).
Thanks!
Follow Up:
I just wanted to post the few changes I made to Eiden's answer. I created a ClientExecutionInterceptor using his code and the information available at Resteasy ClientExecutionInterceptor documentation. My final class looks like
#Provider
#ClientInterceptor
public class SimpleInterceptor implements ClientExecutionInterceptor {
#Override
public ClientResponse execute(ClientExecutionContext ctx) throws Exception {
final ClientResponse response = ctx.proceed();
response.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
return response;
}
}
The big difference is the addition of the #Provider and #ClientExecutionInterceptor annotations. This should insure that the interceptor is properly registered.
Also, just for completeness, I registered the Interceptor slightly differently for my tests. I used:
providerFactory.registerProvider(SimpleInterceptor.class);
I'm sure there are several solutions to this problem, but I can only think of one.
Try so set the content-type using a ClientExecutionInterceptor:
public class Interceptor implements ClientExecutionInterceptor {
#Override
public ClientResponse<?> execute(ClientExecutionContext ctx) throws Exception {
final ClientResponse<?> response = ctx.proceed();
response
.getHeaders()
.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
return response;
}
}
public void getSchools() throws Exception {
ResteasyProviderFactory.getInstance()
.getClientExecutionInterceptorRegistry()
.register( new Interceptor() );
SchoolSearch service =
ProxyFactory.create(SchoolSearch.class, SITE_URL);
}
I dont know about any such annotation, others might do, but a workaround is to create a local proxy. Create a controller, that passes all parameters to education.com using a
java.Net.URL.get()
return the answer that you received, but modify the header. Then connect your client to the local proxy controller.

Categories

Resources