Spring #RestController produces XML without namespaces - spring

I have a #RestController which should return a result from a SOAP web service. The web service client classes are generated with maven-jaxb2-plugin and therefore using JAXB annotations.
#RestController
public class ZemisPersonSearchController {
#Autowired(required = true)
private SoapClient soapClient;
#RequestMapping(path = "/api/persons/{no}", produces = { MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_XML_VALUE })
#PreAuthorize("hasRole('ROLE_GET_PERSON_DETAILS')")
public ResponseEntity<Object> getPersonDetails(HttpServletRequest httpReq, #PathVariable String no) {
Result result = soapClient.getPersonDetails(UUID.randomUUID().toString(), no);
return new ResponseEntity<>(result, HttpStatus.OK);
}
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"responseHeader",
"getPersonDetailsResponse",
"searchPersonResponse",
"systemException"
})
#XmlRootElement(name = "result")
public class Result {
#XmlElement(name = "ResponseHeader")
protected ResponseHeaderType responseHeader;
#XmlElement(name = "GetPersonDetailsResponse")
protected PersonType getPersonDetailsResponse;
#XmlElement(name = "SearchPersonResponse")
protected SearchPersonResponseType searchPersonResponse;
#XmlElement(name = "SystemException")
protected FaultInfoType systemException;
...
As long as all works as expected, the result looks like:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:result
xmlns:ns2="http://mynamespace/personsearchservice/v1">
<ns2:ResponseHeader>
...
But if there goes something wrong (i.e. soap endpoint isn't available) and an excpetion is thrown, the REST controller returns an 406 http status since the automatically generated response cannot be transformed to XML.
I've tried to extend my application with Jackson XML and registered the module to process JAXB annotations as suggested in documentations and blogs I found.
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
</dependency>
#Bean
public Module jaxbModule() {
return new JaxbAnnotationModule();
}
But if I do so, the error for exceptions can now be generated as XML and I got the correct http status 500, but also the response when no error occurs contains no longer namespaces and it is important to keep the namespaces since it is a big and complex xml:
<result>
<ResponseHeader>
Does anybody have an idea what I have to do to get either the namespaces with jackson or the error transformed to xml with JAXB?

I found out, that spring creates a LinkedHashMap with error details automatically. When this map should be converted to html without jackson-xml on classpath, the http status 406 is given back due to a missing converter for the LinkedHashMap to html. So my solution is to add a simple AbstractHttpMessageConverter to my application which converts the map with error details to html. Therefore I don't need jackson-xml on the classpath and my XML is generated from JAXB with namespaces included.
The converter:
public class HttpXmlExceptionConverter extends AbstractHttpMessageConverter<Map<String, Object>> {
public HttpXmlExceptionConverter() {
super(MediaType.APPLICATION_XML, MediaType.TEXT_XML, new MediaType("application", "*+xml"));
}
#Override
protected boolean supports(Class<?> clazz) {
return Map.class.isAssignableFrom(clazz);
}
#Override
protected Map readInternal(Class<? extends Map<String, Object>> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
throw new NotImplementedException("readFromSource is not supported!");
}
#Override
protected void writeInternal(Map<String, Object> map, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
OutputStream s = outputMessage.getBody();
StringBuilder sb = new StringBuilder();
sb.append("<map>");
for (Entry<String, Object> entry : map.entrySet()) {
sb.append("<").append(entry.getKey()).append(">");
if (entry.getValue() != null)
sb.append(StringEscapeUtils.escapeXml(entry.getValue().toString()));
sb.append("</").append(entry.getKey()).append(">");
}
sb.append("</map>");
s.write(sb.toString().getBytes(Charset.defaultCharset()));
}
}
And register the converter:
#Configuration
//#EnableWebMvc
// -> EnableWebMvc is required if you don't want the spring boot auto configuration should be extended
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new HttpXmlExceptionConverter());
}
}

Related

Springboot exception handling when there is no controllers

I have a spring-boot application without any controller classes.
How can I write exception handlers for this application. Exception handler classes annotated with #ControllerAdvice doesn't work.
If you are developing web applications, ErrroController is available.
#Controller
#RequestMapping("${server.error.path:${error.path:/error}}")
public class MyErrorController implements ErrorController {
private final ErrorAttributes errorAttributes;
public MyErrorController(final ErrorAttributes errorAttributes) {
this.errorAttributes = errorAttributes;
}
#Override
public String getErrorPath() {
return null;
}
#RequestMapping
public ResponseEntity<Map<String, Object>> error(final HttpServletRequest request) {
final WebRequest webRequest = new ServletWebRequest(request);
final Throwable th = errorAttributes.getError(webRequest);
// ...
// see also: BasicErrorController implementation
}
}

JAXB class returned from #RestController XML elements wonky

I am porting an old application that runs on JBoss to Spring Boot/Tomcat and have most everything working except the response XML. The old code appears to be using xmlbeans for the XSD source generation. I've changed this to use JAXB. Here's my class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "EventsResponseType1_1", propOrder = {
"getAllEventCodesResponse",
"saveEventCodeResponse",
"failMessageResponse",
"getEmailHistoryResponse"
})
public class EventsResponseType11 {
protected GetAllEventCodesResponseType getAllEventCodesResponse;
protected SaveEventCodeResponseType saveEventCodeResponse;
#XmlElement(name = "FailMessageResponse")
protected ResponseType failMessageResponse;
protected GetEmailHistoryResponseType getEmailHistoryResponse;
// Getters and setters
}
And one of the element classes:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "getAllEventCodesResponseType", propOrder = {
"_return",
"results"
})
public class GetAllEventCodesResponseType {
#XmlElement(name = "Return", required = true)
protected ReturnType _return;
#XmlElement(name = "Results")
protected GetAllEventCodesResponseType.Results results;
// Getters and setters
}
Here's the response XML:
<com.foo.bar.EventsResponseType11>
<getAllEventCodesResponse>
<__return>
<returnCode>0</returnCode>
<returnMessage />
</__return>
<results>
<eventCodes>
<com.foo.bar.EventCodeInfoType>
<eventCodeID>1</eventCodeID>
<eventCode>1000</eventCode>
<eventCodeDesc>Success</eventCodeDesc>
<eventCodeIndicator>SUCCESS</eventCodeIndicator>
<eventCodeContext>General</eventCodeContext>
<createdBy>system</createdBy>
</com.foo.bar.EventCodeInfoType>
</eventCodes>
</results>
</getAllEventCodesResponse>
</com.foo.bar.EventsResponseType11>
I have configured my application:
#SpringBootApplication
#ComponentScan("com.foo.bar")
public class WsApp extends WebMvcConfigurerAdapter{
public static void main(String[] args) {
SpringApplication.run(WsApp.class, args);
}
/**
* Configure the XML as the only return type on requests
*/
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.TEXT_XML);
XStreamMarshaller xmlMarshaller = new XStreamMarshaller();
MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter(xmlMarshaller);
xmlConverter.setSupportedMediaTypes(mediaTypes);
converters.add(xmlConverter);
super.configureMessageConverters(converters);
}
}
And my controller:
#RestController
#RequestMapping("/go")
public class EventService {
#RequestMapping(value = "/events", method = RequestMethod.POST)
public ResponseEntity<EventsResponseType11> events(#RequestBody EventsRequestType11 request){
EventsResponseType11 responseDoc = eventCodesProxy.invoke(request);
return ResponseEntity.ok(responseDoc);
}
}
So my first question is, how can I stop the marshaller from including the package name on those elements that have it.
And second, since the XSD defines a field as "return" JAXB added an underscore to the field name. The #XmlElement annotation on that field identifies this as "Return" which is what I want on the response (without any underscores)
I've tried using a JAXB Marshaller in place of the XStreamMarshaller with no luck. If at all possible, I would opt not to modify the schema because it's old and has a lot of inter-department dependencies.
Thanks in advance for your help!
So after a lot of trial and error, I stumbled upon this post:
Spring 4 mvc REST XML and JSON response
My application class:
#SpringBootApplication
#ComponentScan("com.foo.bar")
#EnableWebMvc
public class WsApp extends WebMvcConfigurerAdapter{
public static void main(String[] args) {
SpringApplication.run(WsApp.class, args);
}
/**
* Configure the negotiator to return ONLY XML
*/
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
favorParameter(true).
parameterName("mediaType").
ignoreAcceptHeader(true).
useJaf(false).
defaultContentType(MediaType.APPLICATION_XML).
mediaType("xml", MediaType.APPLICATION_XML);
}
}
I stated before that I wouldn't be too happy about modifying the XSDs, but I had to make a few tweaks to add the #XmlRootElement. I had tried to modify the JAXB source generation through additional libraries but that didn't work out so well.

Spring #ControllerAdvice vs ErrorController

In my REST service app, I am planning to create a #ControllerAdvice class to catch controller thrown exceptions and return ResponseEntity objects according to the error type.
But I already have a #RestController class implementing the ErrorController interface to catch all exceptions.
Do these two interfere in any manner?
In which cases will ErrorController be called when #ControllerAdvice exists?
Edit:
The ErrorController code as requested
#RestController
public class ControllerCustomError implements ErrorController{
//error json object
public class ErrorJson {
public Integer status;
public String error;
public String message;
public String timeStamp;
public String trace;
public ErrorJson(int status, Map<String, Object> errorAttributes) {
this.status = status;
this.error = (String) errorAttributes.get("error");
this.message = (String) errorAttributes.get("message");
this.timeStamp = errorAttributes.get("timestamp").toString();
this.trace = (String) errorAttributes.get("trace");
}
}
private static final String PATH = "/error";
#Value("${hybus.error.stacktrace.include}")
private boolean includeStackTrace = false;
#Autowired
private ErrorAttributes errorAttributes;
#RequestMapping(value = PATH)
ErrorJson error(HttpServletRequest request, HttpServletResponse response) {
// Appropriate HTTP response code (e.g. 404 or 500) is automatically set by Spring.
// Here we just define response body.
return new ErrorJson(response.getStatus(), getErrorAttributes(request, includeStackTrace));
}
#Override
public String getErrorPath() {
return PATH;
}
private Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
return errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
}
}
An implementation of the ErrorController is used to provide a custom whitelabel error page.
A class annotated with #ControllerAdvise is used to add a global exception handling logic for the whole application. Thus, more than one controller in your application.
If in your application there is no mapping found for a request or page then spring will fallback to the 'whitelabel error page'. And in this case it will be the custom implementation of ErrorController

Rest Custom HTTP Message Converter Spring Boot 1.2.3

I want to create a custom of HttpMessageConverter using Rest, Json, Spring Boot 1.2.3 and Spring 4, However my custom HTTPMessageConverter its' never called.
I have preformed the following steps :
1: Created a class that extends AbstractHttpMessageConverter
#Component
public class ProductConverter extends AbstractHttpMessageConverter<Employee> {
public ProductConverter() {
super(new MediaType("application", "json", Charset.forName("UTF-8")));
System.out.println("Created ");
}
#Override
protected boolean supports(Class<?> clazz) {
return false;
}
#Override
protected Employee readInternal(Class<? extends Employee> clazz,
HttpInputMessage inputMessage) throws IOException,
HttpMessageNotReadableException {
InputStream inputStream = inputMessage.getBody();
System.out.println("Test******");
return null;
}
#Override
protected void writeInternal(Employee t,
HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
// TODO Auto-generated method stu
}
}
2: I create a configuration class to register HTTPMessageConverters
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
System.out.println("Configure Message Converters");
converters.add(new ProductConverter());
super.configureMessageConverters(converters);
//super.extendMessageConverters(converters);
}
}
3: The rest class method
#RequestMapping(value="/{categoryId}" ,method=RequestMethod.POST, consumes="application/json")
#PreAuthorize("permitAll")
public ResponseEntity<ProductEntity> saveProduct(#RequestBody Employee employee , #PathVariable Long categoryId) {
logger.log(Level.INFO, "Category Id: {0}" , categoryId);
ResponseEntity<ProductEntity> responseEntity =
new ResponseEntity<ProductEntity>(HttpStatus.OK);
return responseEntity;
}
My Custom HTTPMessageCoverter it's created but is never called ? Is there a configuration or step I'm missing ? any input or advice is appreciated.
After overriding the (AbstractHttpMessageConverter) class methods, I found out there's two annotations for achieving polymorphism #JsonTypeInfo and #JsonSubTypes. For anyone who wants achieve polymorphism can use these two annotations.
I believe you want to configure these message converters using the configureMessageConverters method in a configuration class that extends WebMvcConfigurerAdapter. I've done this myself with a converter for CSV content. I've included that code below. This link shows an example as well. This link may also be helpful. It seems like with Spring configuration it is not always clear on the best place to configure things. :) Let me know if this helps.
#Configuration
public class ApplicationWebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(new CsvMessageConverter());
}
}
You will also need top modify your supports() method to return true for classes supported by the converter. See the Spring doc for AbstractHttpMessageConverter supports method.

No headers when using Mock RestEasy framework

I am trying to use the server-side resteasy mock framework to make a GET request however when trying to retrieve the header from the server code it simply doesn't exist.
Below is the Test class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:base-spring-context.xml" }, loader = DelegatingSmartContextLoader.class)
#DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public class TestClass {
// Sets up RESTEasy processors to test #Provider classes
#Configuration
static class ContextConfiguration {
#Autowired
private ApplicationContext context;
#Bean
public Dispatcher getDispatcher() {
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getRegistry().addResourceFactory(getSpringResourceFactory());
return dispatcher;
}
#Bean
public SpringBeanProcessor getRSPostProcessor() {
SpringBeanProcessor processor = new SpringBeanProcessor(getDispatcher(), getDispatcher().getRegistry(),
getDispatcher().getProviderFactory());
return processor;
}
#Bean
public SpringResourceFactory getSpringResourceFactory() {
SpringResourceFactory noDefaults = new SpringResourceFactory("restClass", context,
RestClass.class);
return noDefaults;
}
}
#Autowired
Dispatcher dispatcher;
#Autowired
private ServletContext servletContext;
#Before
public void setUp() {
ResteasyProviderFactory.getContextDataMap().put(HttpServletRequest.class, new MockHttpServletRequest(servletContext));
}
#Test
public void testRest() throws URISyntaxException, ECUNotFoundException {
MockHttpRequest request = MockHttpRequest.get("/")
.header("someHeader", "VALUE")
.accept("application/myValue+XML");
logger.info("HEADERS: {}",request.getHttpHeaders().getRequestHeader("someHeader"));
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
logger.info("Got response: \n\tStatus: '{}'\n\tResponse body: '{}'",response.getStatus(),new String(response.getOutput()));
}
}
Here is the method the Rest method gets triggered from RestClass
#GET
#Path("/")
#Produces("application/myValue+XML")
#GZIP
public Response getMethod(#Context HttpServletRequest request) {
String header = request.getHeader("someHeader");
logger.info("HEADER NAME: {}","someHeader");
if (header == null || header.isEmpty()) {
logger.warn("the header must be present in the request");
return Response.status(Status.BAD_REQUEST).build();
}
The issue here is that the Rest method does not receive the header. It is null however when the printout from the test method clearly shows that the header is set.
Can anybody help understanding why this happens.

Resources