Trying to handle an empty SOAP message with Spring Web Services but failing.
So, I have a request to provide an endpoint for a sort of a PING method. Basically the SOAP messages I can handle look like this:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:def="http://www.something.com/edf/services/defaultservice">
<soapenv:Header/>
<soapenv:Body>
<def:ServiceReqType>
<transactionId>1111</transactionId>
<subscriberId>2222</subscriberId>
</def:ServiceReqType>
</soapenv:Body>
</soapenv:Envelope>
and that I can handle with an endpoint that is handling ServiceReqType.
But the PING looks like this:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body/>
</soapenv:Envelope>
That I can't handle, Spring logs a Can't handle [SaajSoapMessage].
What I need to return is the exactly same message.
I understand there that there is a type/class missing that I would provide to #PayloadRoot.
So I am wondering what would an endpoint specification be for this empty bodied request?
Just for reference here is my endpoint for handling the ServiceReqType:
#PayloadRoot(namespace = NAMESPACE_EDF, localPart = "ServiceReqType")
#ResponsePayload
public ServiceRespType serviceResponse(#RequestPayload ServiceReqType request) {
LOGGER.debug("-----> ServiceReqType:{}", request);
return reqProcessor.process(request);
}
UPDATE 1:
So I tried with implementing the interceptor in the following way:
public class CustomEndpointInterceptor implements EndpointInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomEndpointInterceptor.class);
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
LOGGER.info("---> Message context: {}", messageContext.toString());
LOGGER.info("---> Message endpoint: {}", endpoint.toString());
return false;
}
#Override
public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception {
LOGGER.info("handleResponse");
return false;
}
#Override
public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception {
LOGGER.info("handleFault");
return false;
}
#Override
public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) throws Exception {
LOGGER.info("afterCompletion");
}
}
and then in the WebServiceConfiguration class I added this:
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(new CustomEndpointInterceptor());
super.addInterceptors(interceptors);
}
But it still does not work. What I get is that the interceptor is called when SOAP message has a BODY but when it is sent a Ping that is BODYless then I get again the same error message and interceptor is not called. So it seems I must find an interceptor that is further up the chain...
UPDATE 2:
Here is how WSDL file looks like for this Ping...
<message name="PingRequest"/>
<message name="PingResponse"/>
There is nothing as a part inside of these...
For comparison this is how ServiceReqType looks:
<message name="ServiceReqType">
<part name="body" element="tns:ServiceReqTypeDefinition"/>
</message>
and then the ServiceReqTypeDefinition is defined in an accompanying xsd file.
UPDATE 3:
So, found the reason why interceptors won't work on this type of message :-/
Below code is from MessageDispatcher line 234.
// No triggering of interceptors if no endpoint is found
if (endpointNotFoundLogger.isWarnEnabled()) {
endpointNotFoundLogger.warn("No endpoint mapping found for [" + messageContext.getRequest() + "]");
}
throw ex;
So it's on purpose, now I need to find how to handle these incomplete or incorrect SOAP messages... any ideas because rewriting Dispatcher doesn't feel like the right way.
UPDATE 4
So, I managed to intercept the payload, the one that is invalid, by extending AbstractEndpointMapping class and then overriding getInternalEndpoint gives me the possibility to evaluate the message and see if it is this empty Ping request I have been trying to process.
I think this would all be easily solved if I knew how to define a defaultEndpoint because I see that in case SOAP message is not recognised, so no endpoint mapping is found, this defaultEndpoint is used to handle that message.
I noticed that when working with XML specification of Beans then there is a property to define the endpoint as a default one, but how to do it when using annotations?
Another way is to just create an endpoint and then return it from getInternalEndpoint so spring can handle processing, I guess, but I don't know yet how to create an endpoint object... working on that now.
p.s. This documentation mentions defaultEndpoint but not how to set it up in a non XML defining Bean way.
So, in the end I had to create a custom exception handler. Something like this:
public class CustomEndpointNotFoundException extends Exception {
public CustomEndpointNotFoundException(String message) {
super(message);
}
}
then:
#Component
#Order(Ordered.LOWEST_PRECEDENCE)
public class NoEndpointFoundEndpointMapping implements EndpointMapping {
#Override
public EndpointInvocationChain getEndpoint(MessageContext messageContext) throws Exception {
throw new CustomEndpointNotFoundException("");
}
}
and finally:
#Component
public class CustomEndpointExceptionResolver implements EndpointExceptionResolver {
#Override
public boolean resolveException(MessageContext messageContext, Object endpoint, Exception ex) {
if (messageIsPing()) {
return true;
}
return false;
}
}
So basically I am handling it as an error. It's not really ideal solution IMHO, I will still see if I can intercept the endpoint resolution, or define a default endpoint.
Related
I am trying to use an interceptor to introspect and change exceptions that occur on the backend. The interceptor looks something like this:
public class ApplicationErrorInterceptor {
private static final Logger LOGGER = Logger.getLogger(ApplicationErrorInterceptor.class);
#AroundInvoke
public Object handleException(InvocationContext context) {
try {
return context.proceed();
} catch (Exception ex) {
LOGGER.errorf(ex, "An unhandled exception occured!");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("some custom 500 error text")
.build();
}
}
}
And the service using this interceptor looks something like this:
#Path("/somepath")
#Interceptors({ApplicationErrorInterceptor.class})
public interface SomeRestService {
#POST
#Path("getResponse")
Response getResponse();
#POST
#Path("getVoid")
void getVoid();
}
Assume the implementation for both of these methods throw an exception. I expect the exception to be mapped to a 500 server error with the supplied custom message in both cases. Unfortunately, the method returning void will get mapped to a 204 No Content response. If I remove the interceptor altogether, the default 500 server error occurs, which is at least the correct status code, but I lost the error customization
I am passing a header to a spring REST api like:
#RequestHeader(value="test-header")
header is mandatory here for the API, so I do not want to keep it optional.
when no header is passed, any call to the API returns a standard 400 error indicating that request is syntantically wrong and then it does not enter the REST API. But, I want to construct a proper ResponseBody and return a json for this error. I am not sure about the best way to do this. I thought about using spring interceptor and check if this header was passed or not, but then I am not sure if I can create a responsebody from here. Atleast I could not figure out how to do so.
will interceptor approach work for this? If yes, how? If not, then what are the options? Can someone please help on this?
Update:
This is how the REST API is:
public void methodA(#RequestHeader(value="test-header") String header, #RequestBody User user, HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
...
...
}
When the header is present, it will enter the REST API and continue with the logic. But, if the header is not present, it does not enter the API and simply returns a standard 400 error.
The interceptor that I wrote is like:
public class XXXInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
...
...
return true;
}
}
STEP1: Use spring validator annotation like #valid to validate your request.
STEP 2: Write your custom validator class. which will be responsible to check the header and see if it has value or it has the expected value.
STEP 3: If the request is not correct validator throws your custom exception.
STEP 4: write your exception handler class. In the class define what response must me returned if the exception in STEP 3 is caught.
For more information on Exception Handling in Spring.
In our current projet we do use a java interceptor to authenticate the request but nothing beyound that.
Write a method with the annotation #ExceptionHandler and use ServletRequestBindingException.class as this exception is thrown in case of miss. You can return any type of object from this method.
For example
#ExceptionHandler(ServletRequestBindingException.class)
public ResponseEntity<ResponseObject> handleHeaderError(){
ResponseObject responseObject=new ResponseObject();
responseObject.setStatus(Constants.ResponseStatus.FAILURE.getStatus());
responseObject.setMessage(header_missing_message);
ResponseEntity<ResponseObject> responseEntity=new ResponseEntity<ResponseObject>(responseObject, HttpStatus.BAD_REQUEST);
return responseEntity;
}
Another approach would be using Spring Interceptors (HandlerInterceptorAdapter), as you mentioned in your question, with #ControllerAdvice and return your JSON in an #ExceptionHandler method.
Take a look at the following post: http://www.journaldev.com/2651/spring-mvc-exception-handling-exceptionhandler-controlleradvice-handlerexceptionresolver-json-response-example
This is coming late but then, a very straightforward way to deal with this type of issue is to use a Controller Advice class which allows you to handle exceptions across the whole application in one global handling component.
The exception throw by spring is the MissingRequestHeaderException which you can then provide a custom handler in your controller advice class.
#Slf4j
#RestControllerAdvice
public class ControllerAdvice {
#ExceptionHandler(MissingRequestHeaderException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public ErrorResponse handleMissingRequestHeaderException(MissingRequestHeaderException ex) {
log.error(ex.getMessage(), ex);
return new ErrorResponse("Missing request header: " + ex.getHeaderName());
}
}
public class ErrorResponse implements Serializable {
private String message;
public ErrorResponse(String message) {
this.message = message;
}
}
I am calling REST webservices from JSP using AJAX . Can you tell me the best way to send custom error message from REST webservice to JSP ?
Consider using HTTP response codes with (possibly) json response bodies to supply any required information so the client application can react accordingly.
Consider using the WebapplicationException. You can give it the Errorcode (also custom ones) and a body for the response. You could use the JSON Format if you have a complex structure to display your errors but i would suggest just using the an errormessage (for example in case of a bad request, what part of the request was bad).
If you are using JAX-RS REST webservice, you can configure Spring #Controller. Your method should produce application/json and return Response object, like in this example:
#GET
#Path("/get/{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response getUserById(#PathParam("id") String userId) {
// Here your logic
Foo foo = new Foo();
foo.setMsg("Bad Request");
foo.setData("User " + userId + " not found")
return Response.status(400).entity(foo).build();
}
And from AJAX, you can catch error message
// Get user details
$.getJSON(encodeURI("./rest/user/get/" + userId), function(data) {
// Some logic on success
// Fail
}).fail( function(jqxhr) {
console.log(jqxhr.responseJSON.msg);
});
There are a couple of ways.
1. You can look at the response status you receive from the web service. The statuses starting with 2** are a success response (Eg: 200, 201), the ones starting with 4** or 5** are errors.
But the optimal way to handle and track exceptions is to use ExceptionMapper. You can write your own class that implements ExceptionMapper like below:
#Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable arg0) {
return Response.status(Status.INTERNAL_SERVER_ERROR)
.entity("Custom Exception: Error retrieving data")
.build();
}
}
You can write your own custom exceptions like below or can throw blanket exception like below. The above approach is the preferred one though.
#Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable arg0) {
return Response.status(Status.INTERNAL_SERVER_ERROR)
.entity("Custom Exception: Error retrieving data")
.build();
}
}
In my JAXWS web service I need to send a specific message back to the client when an exception occurs, not the standard Fault message with the description of the exception.
How can this be done?
I am using jaxws-rt version 2.1.3
I have already tried to use exception mappers like this but without luck (some how they are not called, this could also be caused by a mistake in the configuration):
#Provider
public class ThrowableExceptionMapper implements ExceptionMapper<Throwable> {
public ThrowableExceptionMapper() {
// TODO Auto-generated constructor stub
}
#Override
public Response toResponse(Throwable throwable) {
if (throwable instanceof WebApplicationException) {
return ((WebApplicationException) throwable).getResponse();
} else {
return Response.serverError().entity("").build();
}
}
}
The server we use is JBoss EAP 6.4.
Edit:
The exception mapper approach is not suited for my JAXWS web service, because this is for JAX-RS (thanks SRINIVAS K).
Is there something similar available for JAXWS?
I managed to rewrite the response message with the help of this page:
http://www.java-tips.org/java-ee-tips-100042/156-java-api-for-xml-web-services/1958-writing-a-handler-in-jax-ws.html
And with some examples of this page:
http://www.programcreek.com/java-api-examples/index.php?api=javax.xml.ws.handler.MessageContext
I put together this class:
public class MyLogicalHandler implements LogicalHandler<LogicalMessageContext> {
private final String RejectionResponseBody = "<ns2:MessageControllerResponse xmlns:ns2=\"http://some.namespace.com/\"><return>SOMEDATA</return></ns2:MessageControllerResponse>";
#Override
public boolean handleMessage(LogicalMessageContext context) {
return true;
}
#Override
public boolean handleFault(LogicalMessageContext context) {
processMessage(context);
return true;
}
#Override
public void close(MessageContext context) {
}
private void processMessage(LogicalMessageContext context) {
Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty) {
LogicalMessage msg = context.getMessage();
msg.setPayload(new StreamSource(new ByteArrayInputStream(RejectionResponseBody.getBytes())));
}
}
}
Edit, additional information added:
You also need to add the HandlerChain annotation to the web service:
...
#HandlerChain(file = "handler-chain.xml")
public class MyWebService {
...
}
And create a handler-chain xml file:
<?xml version="1.0" encoding="UTF-8"?>
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee">
<handler-chain>
<handler>
<handler-class>my.package.ws.jaxws.MyLogicalHandler</handler-class>
</handler>
</handler-chain>
</handler-chains>
And place this file in your resources folder of the web service, you need to use the same package structure as you did with the web service. So create the following package: my.package.ws in the resources folder if your web service class resides in the my.package.ws package.
Can you try using below?
String errorMessage = "this is custom error message";
return Response.status(e.getResponse().getStatus()).entity(errorMessage).build();
Can you check if ExceptionMapper works with generic exceptions like Throwable or Exception. I see some examples with custom defined exceptions that are configured with WebFault annotation.
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.