I try to configure a spring exception handler for a rest controller that is able to render a map to both xml and json based on the incoming accept header. It throws a 500 servlet exception right now.
This works, it picks up the home.jsp:
#ExceptionHandler(IllegalArgumentException.class)
public String handleException(final Exception e, final HttpServletRequest request, Writer writer)
{
return "home";
}
This does not work:
#ExceptionHandler(IllegalArgumentException.class)
public #ResponseBody Map<String, Object> handleException(final Exception e, final HttpServletRequest request, Writer writer)
{
final Map<String, Object> map = new HashMap<String, Object>();
map.put("errorCode", 1234);
map.put("errorMessage", "Some error message");
return map;
}
In the same controller mapping the response to xml or json via the respective converter works:
#RequestMapping(method = RequestMethod.GET, value = "/book/{id}", headers = "Accept=application/json,application/xml")
public #ResponseBody
Book getBook(#PathVariable final String id)
{
logger.warn("id=" + id);
return new Book("12345", new Date(), "Sven Haiges");
}
Your method
#ExceptionHandler(IllegalArgumentException.class)
public #ResponseBody Map<String, Object> handleException(final Exception e, final HttpServletRequest request, Writer writer)
does not work because it has the wrong return type. #ExceptionHandler methods have only two valid return types:
String
ModelAndView.
See http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html for more information. Here's the specific text from the link:
The return type can be a String, which
is interpreted as a view name or a
ModelAndView object.
In response to the comment
Thanx, seems I overread this. That's
bad... any ideas how to provides
exceptions automatically in xml/json
format? – Sven Haiges 7 hours ago
Here's what I've done (I've actually done it in Scala so I'm not sure if the syntax is exactly correct, but you should get the gist).
#ExceptionHandler(Throwable.class)
#ResponseBody
public void handleException(final Exception e, final HttpServletRequest request,
Writer writer)
{
writer.write(String.format(
"{\"error\":{\"java.class\":\"%s\", \"message\":\"%s\"}}",
e.getClass(), e.getMessage()));
}
Thanx, seems I overread this. That's bad... any ideas how to provides
exceptions automatically in xml/json format?
New in Spring 3.0 MappingJacksonJsonView can be utilized to achieve that:
private MappingJacksonJsonView jsonView = new MappingJacksonJsonView();
#ExceptionHandler(Exception.class)
public ModelAndView handleAnyException( Exception ex )
{
return new ModelAndView( jsonView, "error", new ErrorMessage( ex ) );
}
This seems ilke a confirmed Bug (SPR-6902
#ResponseBody does not work with #ExceptionHandler)
https://jira.springsource.org/browse/SPR-6902
Fixed in 3.1 M1 though...
The following could be a workaround if you are using message converters to marshall error objects as the response content
#ExceptionHandler(IllegalArgumentException.class)
public String handleException(final Exception e, final HttpServletRequest request)
{
final Map<String, Object> map = new HashMap<String, Object>();
map.put("errorCode", 1234);
map.put("errorMessage", "Some error message");
request.setAttribute("error", map);
return "forward:/book/errors"; //forward to url for generic errors
}
//set the response status and return the error object to be marshalled
#SuppressWarnings("unchecked")
#RequestMapping(value = {"/book/errors"}, method = {RequestMethod.POST, RequestMethod.GET})
public #ResponseBody Map<String, Object> showError(HttpServletRequest request, HttpServletResponse response){
Map<String, Object> map = new HashMap<String, Object>();
if(request.getAttribute("error") != null)
map = (Map<String, Object>) request.getAttribute("error");
response.setStatus(Integer.parseInt(map.get("errorCode").toString()));
return map;
}
I am using Spring 3.2.4. My solution to the problem was to make sure that the object I was returning from the exception handler had getters.
Without getters Jackson was unable to serialize the object to JSON.
In my code, for the following ExceptionHandler:
#ExceptionHandler(RuntimeException.class)
#ResponseBody
public List<ErrorInfo> exceptionHandler(Exception exception){
return ((ConversionException) exception).getErrorInfos();
}
I needed to make sure my ErrorInfo object had getters:
package com.pelletier.valuelist.exception;
public class ErrorInfo {
private int code;
private String field;
private RuntimeException exception;
public ErrorInfo(){}
public ErrorInfo(int code, String field, RuntimeException exception){
this.code = code;
this.field = field;
this.exception = exception;
}
public int getCode() {
return code;
}
public String getField() {
return field;
}
public String getException() {
return exception.getMessage();
}
}
AnnotationMethodHandlerExceptionResolver also need MappingJacksonHttpMessageConverter
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver">
<property name="messageConverters">
<list>
<bean
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
</list>
</property>
</bean>
<bean id="jacksonObjectMapper"
class="iacm.cemetery.framework.web.servlet.rest.JacksonObjectMapper" />
I faced the similar issue, this problem occurs when your Controller method return type and ExceptionHandler return types are not same. Make sure you have exactly same return types.
Controller method:
#RequestMapping(value = "/{id}", produces = "application/json", method = RequestMethod.POST)
public ResponseEntity<?> getUserById(#PathVariable String id) throws NotFoundException {
String response = userService.getUser(id);
return new ResponseEntity(response, HttpStatus.OK);
}
Advice method:
#ExceptionHandler(NotFoundException.class)
public ResponseEntity<?> notFoundException(HttpServletRequest request, NotFoundException e) {
ExceptionResponse response = new ExceptionResponse();
response.setSuccess(false);
response.setMessage(e.getMessage());
return new ResponseEntity(response, HttpStatus.NOT_FOUND);
}
As you can see return types in both the classes are same ResponseEntity<?>.
Related
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.
I've been writing RESTful Web service. The technologies that I use:
Glassfish 3 (based on Java 6)
JDK v7
Eclipse EE Kepler
Jersey (part of Glassfish)
I've created custom POJO for custom MediaType:
public final class SimpleEntity{
private int value = 0;
private String stValue = "";
public SimpleEntity(){}
public SimpleEntity(int value, String stValue) {
super();
this.value = value;
this.stValue = stValue;
}
... getters, setters
My resource method:
#POST
#Produces("application/entity")
#Path("/getSimpleEntityNeedsProvider")
public Response getSimpleEntityNeedsProvider(){
SimpleEntity entity = new SimpleEntity(47, "String input value");
return Response.ok().entity(entity).build();
}
My message body writer:
#Provider
#Produces("application/entity")
public final class SimpleEntityBodyWriterProvider implements MessageBodyWriter<SimpleEntity>{
private static Logger log = Logger.getLogger(Class.class);
#Override
public long getSize(SimpleEntity arg0, Class arg1, Type arg2, Annotation[] arg3,
MediaType arg4) {
return -1;
}
#Override
public boolean isWriteable(Class arg0, Type arg1, Annotation[] arg2,
MediaType arg3) {
if(arg0.equals(SimpleEntity.class)){
return true;
}
return false;
}
#Override
public void writeTo(SimpleEntity entity, Class arg1, Type arg2, Annotation[] arg3,
MediaType media, MultivaluedMap arg5, OutputStream out)
throws IOException, WebApplicationException {
log.log(Level.INFO, "Input to SimpleEntityBodyWriterProvider: "+entity.getValue());
out.write(entity.getValue());
out.write(entity.getStValue().getBytes());
}
}
My service client:
private void getSimpleEntityNeedsProvider(String strUrl, String method){
HttpURLConnection connect = null;
try {
URL url = new URL(strUrl);
connect = (HttpURLConnection)url.openConnection();
connect.setRequestProperty("Accept", "application/entity");// Accept from server
connect.setRequestMethod(method);
connect.connect();
InputStream input = connect.getInputStream();
int size = input.available();
byte[] b = new byte[size];
input.read(b, 0, size);
System.out.println("From resource: "+new String(b));
System.out.println("Status: "+connect.getResponseCode()+" : "+connect.getResponseMessage());
}
catch(IOException e){
e.printStackTrace();
}
}
The root path:
#ApplicationPath("/rest/v6")
public class AppV6 extends Application {
private static Logger log = Logger.getLogger(Class.class.getName());
public AppV6(){}
#Override
public Set<Class<?>> getClasses(){
Set<Class<?>> cls = new HashSet<>();
cls.add(SimpleEntity.class);
cls.add(SimpleEntityBodyWriterProvider.class);
return cls;
}
}
When I run the application I get the following output from my service client:
From resource: /String input value
Status: 200 : OK
I want to use custom media type and MessageBodyWriter in order to have better understanding of RESTful/Jersey service.
The questions that I have:
Is this is the correct way of involving/coding the custom media type?
The data that I receive at service client is not totally correct. I.e., in stead of "/" the value needs to be a digit 47. So, why do I get the a character instead of digit?
As you have seen at resource method I input two values to custom POJO: digit and string. At service client I've got InputStream. I get byte array of overall data. How can I read data as chunks of separate data, which is encoded as fields in my POJO? I could read byte by byte, but is it a correct way of doing it?
The solution, that I figured out, is simple.
I just needed to use wrapper class for InputStream, i.e.,
InputStream data = connect.getInputStream();
DataInputStream input = new DataInputStream(data);
System.out.println("From resource: "+input.readInt()+" : "+input.readUTF());
The same wrapper needs to be applied at MessageBodyWriter implementation class. I added implementation for MessageBodyReader (the same rules apply as above) for reading custom POJO/MediaType at resource method.
It works smoothly.
I want to capture and log the response payload in JAX-RS filter. Here is the code snippet of the filter method that I'm using to intercept the response. (FYI - I'm using RestEasy for implementation)
#Override
public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) throws IOException {
...
final OutputStream out = responseContext.getEntityStream();
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
out.write(baos.toByteArray());
....
}
}
However, the ByteArrayOutputStream turns out be empty. Looking at the RestEasy code, it's using DeferredOutputStream, but not sure how it would matter here in pulling the response payload. I have tried writing to byte[] directly, but that doesn't help either. Am I missing anything here ? Thanks.
If you don't want to write more data to the response you don't need to deal with the OutputStream. Just use the response entity:
#Provider
public class SomeFilter implements ContainerResponseFilter {
private Logger LOG = LoggerFactory.getLogger(SomeFilter.class);
#Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
LOG.info("response entity: " + responseContext.getEntity());
}
}
The OutputStream is empty at the time the Filter is called because the JAX-RS runtime has not written to it. After your Filter the runtime will choose the correct MessageBodyWriter which will serialize the entity to the OutputStream.
You could also intercept all MessageBodyWriters with a WriterInterceptor. Following example passes a ByteArrayOutputStream to the MessageBodyWriter and restores the original OutputStream afterwards:
#Provider
public class ResponseInterceptor implements WriterInterceptor {
private Logger LOG = LoggerFactory.getLogger(ResponseInterceptor.class);
#Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
OutputStream originalStream = context.getOutputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
context.setOutputStream(baos);
try {
context.proceed();
} finally {
LOG.info("response body: " + baos.toString("UTF-8"));
baos.writeTo(originalStream);
baos.close();
context.setOutputStream(originalStream);
}
}
}
I had the same problem and solved differently, so I leave here my response as well although the question is already marked as correctly answered.
I implemented a ContainerResponseFilter and injected Providers through which I retrieved the MessageBodyWriter for the particular entity of the response and the particular MediaType; then I used it to write the entity to an accessible OutputStream which I used to log the entity.
This approach allows you to capture the exact payload of the response, not just the entity attached to the Response, i.e. if the entity is going to be serialized as JSON, then you will log the JSON, if it is serialized as XML, you will log the XML. If using the toString() method of the attached entity is enough, this approach is just a useless computational cost.
Here is the code (the trick is done in the function CustomResponseLogger.payloadMessage):
#Provider
public class CustomResponseLogger implements ContainerResponseFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomResponseLogger.class);
#Context private Providers providers;
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
String message = new String("Outgoing message").concat(System.lineSeparator());
if (responseContext.getMediaType() != null)
message = message.concat("Content-Type: ").concat(responseContext.getMediaType().toString()).concat(System.lineSeparator());
message = message.concat("Payload: ").concat(payloadMessage(responseContext)).concat(System.lineSeparator());
LOGGER.info(message);
}
private String payloadMessage(ContainerResponseContext responseContext) throws IOException {
String message = new String();
if (responseContext.hasEntity()) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Class<?> entityClass = responseContext.getEntityClass();
Type entityType = responseContext.getEntityType();
Annotation[] entityAnnotations = responseContext.getEntityAnnotations();
MediaType mediaType = responseContext.getMediaType();
#SuppressWarnings("unchecked")
MessageBodyWriter<Object> bodyWriter = (MessageBodyWriter<Object>) providers.getMessageBodyWriter(entityClass,
entityType,
entityAnnotations,
mediaType); // I retrieve the bodywriter
bodyWriter.writeTo(responseContext.getEntity(),
entityClass,
entityType,
entityAnnotations,
mediaType,
responseContext.getHeaders(),
baos); // I use the bodywriter to write to an accessible outputStream
message = message.concat(new String(baos.toByteArray())); // I convert the stream to a String
}
return message;
}
}
Hope this helps!
I have the following method on sever.
public HashMap<String,Set> select()
{
HashMap <String,Set> mp = new HashMap();
//some code
return mp;
}
whenver I am trying to return
<String , Set>
it is going onFailur
but I did this
<String , String >
then its success why this happning
i am using gwt RPC and my client code is
greetingService.select(usertextbox.getText(),new AsyncCallback<HashMap<String,Set>>()
{
public void onFailure(Throwable caught) {
Window.alert("not done");
}
#Override
public void onSuccess(HashMap hm) {
Window.alert("done");
}
Service code is
HashMap<String, Set> select(String user);
service implmentation is
public HashMap<String,Set> select(String user)
{
try {
Session studentDbSession = new Session("localhost",5984);
Database db = studentDbSession.getDatabase("hello");
Document d = db.getDocument("xyz");
JSONArray key = d.names().discard(0).discard(0);
for(int i=0;i<key.size();i++)
{
if(d.containsKey(key.get(i)))
{
k=key.getString(i);
Set aaa=d.getJSONObject(key.getString(i)).entrySet();
System.out.println("----------------");
mp.put(k,aaa);
return mp;
}
Always try to avoid Raw type. Let me share you a sample code. Try it at you end with this sample first or validate all the classes of your code.
Sample code:
RemoteService interface
#RemoteServiceRelativePath("greet")
public interface GreetingService extends RemoteService {
public HashMap<String, Set<String>> select(String input) throws IllegalArgumentException;
}
GreetingServiceAsync interface
public interface GreetingServiceAsync {
void select(String input, AsyncCallback<HashMap<String, Set<String>>> callback);
}
GreetingServiceImpl class
public class GreetingServiceImpl extends RemoteServiceServlet implements GreetingService {
#Override
public HashMap<String, Set<String>> select(String input) throws IllegalArgumentException {
HashMap<String, Set<String>> output = new HashMap<String, Set<String>>();
Set<String> set = new HashSet<String>();
set.add("Hello " + input);
output.put("greeting", set);
return output;
}
}
Entry Point class
public void greetService() {
GreetingServiceAsync greetingService = GWT.create(GreetingService.class);
greetingService.select("Mark", new AsyncCallback<HashMap<String, Set<String>>>() {
#Override
public void onSuccess(HashMap<String, Set<String>> result) {
Window.alert(result.get("greeting").iterator().next());
}
#Override
public void onFailure(Throwable caught) {
Window.alert("fail");
}
});
}
web.xml:
<servlet>
<servlet-name>gwtService</servlet-name>
<servlet-class>com.x.y.z.server.GWTServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>gwtService</servlet-name>
<url-pattern>/moduleName/gwtService</url-pattern>
</servlet-mapping>
output:
What your GWT RPC call HashMap<String, Set> select(String user); does is following:
client-side: serialize String user in order to send it to server
server-side: deserialize RPC call, find implementation of select(String user) and execute it
server-side: serialize return value HashMap<String, Set> in order to return it to client
client-side: deserialize return value and call AsyncCallback
The problem lies in step 3), the serializing of HashMap<String, Set>. The HashMap itself is not the issue; it is the Set which causes the error. When serializing a raw class, GWT usually assumes that the generic type is <Object>. And since Object is not serializable in GWT, an exception is thrown.
Fix: As Braj already mentioned -- give your Set a serializible generic type, e. g. Set<String>, or define your own interface in a package which is accessable from both client- and server-side
public interface UserProperty extends IsSerializable{
}
and change the RPC method like this:
HashMap<String, Set<UserProperty> select(String user);
Have a look at Braj's answer for where to find all the places you need to change after changing your RPC method!
I have a Library application which is already implemented in spring MVC.
I need to use ReST web services for the same application using spring 3.
I have a Controller class I want is to be as a RestFul webService
#Controller #SessionAttributes("category")
public class CategoryController {
private static final Log log = LogFactory.getLog(CategoryController.class);
#Autowired
private CategoryService categoryService;
#Autowired
private ItemService itemService;
#RequestMapping("/category/categoryList.htm")
public ModelAndView list(HttpServletRequest request,
HttpServletResponse response) throws Exception {
List<Category> list = categoryService.getAllMainCategories();
Map map = new HashMap();
map.put("categoryList", list);
map.put("category", new Category());
return new ModelAndView("categoryList", map);
}
#RequestMapping(method = RequestMethod.POST, value = "/category/save.htm")
public String save(HttpServletRequest request,
HttpServletResponse response, Category command) throws Exception {
log.debug("save method called" + command);
Category category = (Category) command;
System.out.println(category);
categoryService.saveCategory(category);
return "redirect:/category/categoryList.htm";
}
#RequestMapping("/category/edit.htm")
public String edit(#RequestParam String id, ModelMap model)
throws Exception {
log.debug("edit method called :" + id);
log.debug(Long.parseLong(id));
Category cat = categoryService.getCategory(Long.parseLong(id));
model.put("categoryList", categoryService.getAllMainCategories());
model.put("category", cat);
return "categoryList";
}
#RequestMapping("/category/delete.htm")
public String remove(#RequestParam String id, ModelMap model)
throws Exception {
log.debug("remove method called " + id);
categoryService.deleteCategory(Long.parseLong(id));
return "redirect:/category/categoryList.htm";
}
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Category.class,
new PropertyEditorSupport() {
#Override
public void setAsText(String text) {
setValue(categoryService.getCategory(Long.valueOf(text)));
}
});
}
}
it is CategoryController class which add delete or update a category
ItemService and CategoryService are data sources
Category is a domain object having properties like id,name,description etc..,
How do I write a REST web service for this?
There's a simple example showing how in Barebones Spring. Check it out.