I have several controllers that return the same generic Response object with #ResponseBody annotation, like this:
#RequestMapping(value = "/status", method = RequestMethod.GET)
#Transactional(readOnly = true)
public #ResponseBody Response<StatusVM> status()
I need to perform an operation on every controller, after the Response is returned. This operation will enrich the Response object with new data.
I don't want to duplicate code, so I need a single point of intervention. I thought I could do this with Interceptors, however, according to the docs http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-handlermapping-interceptor this does not work well with #ResponseBody:
Note that the postHandle method of HandlerInterceptor is not always ideally suited for use with #ResponseBody and ResponseEntity methods. In such cases an HttpMessageConverter writes to and commits the response before postHandle is called which makes it impossible to change the response, for example to add a header. Instead an application can implement ResponseBodyAdvice and either declare it as an #ControllerAdvice bean or configure it directly on RequestMappingHandlerAdapter.
I haven't been able to find an example of this tecnique, could anybody help me?
As an alternative I could work with aspects, but then I'd need to annotate every controller, which is something I'd like to avoid.
In the end I implemented ResponseBodyAdvice like this:
#ControllerAdvice
public class StatusAdvice implements ResponseBodyAdvice<Response<?>> {
#Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
if (returnTypeIsReponseVM(returnType)&&responseConverterIsJackson2(converterType)){
return true;
}
return false;
}
....
#Override
public Response<?> beforeBodyWrite(Response<?> body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
....
return body;
}
}
So it was easier then expected.
Related
I have a controller to send back a JSON Payload
#RequestMapping(value = "/MerchantMonitoringAPI", method = RequestMethod.GET,produces = "application/json")
public String MerchantMonitoring() {
ApplicationContext context =
new ClassPathXmlApplicationContext("Spring-Module.xml");
TopMerchantsDAO topMerchantsDAO = (TopMerchantsDAO) context.getBean("topMerchantsDAO");
TopMerchants topMerchants = topMerchantsDAO.retrieveMerchantList();
for(String temp:topMerchants.getMerchantList())
{
System.out.println(temp);
}
Gson gson = new Gson();
Type type = new TypeToken<TopMerchants>() {}.getType();
String jsonPayload = gson.toJson(topMerchants, type);
System.out.println(jsonPayload);
return jsonPayload;
}
It is trying to redirect me to a view with the page name as the JSON (localhost:8080/{"merchantList":["Apple","Google"]}.jsp)
How to stop this and return the JSON payload ??
Add #RestController on top of the #RequestMapping
#RestController
#RequestMapping(value = "/MerchantMonitoringAPI", method =
RequestMethod.GET,produces = "application/json")
public String MerchantMonitoring() {...}
Because the method is now annotated with #RestController, the objects returned from this methods will go through message conversion to produce a json resource representation for the client.
If you want to have several methods for returning JSON and page in the same class, you can still annotate your class with #Controller, and the method for JSON annotated with #ResponseBody
If you will annotated class with #RestController - all methods inside class will be working like #ResponseBody and class will be like #Controller annotated. Of course, this is a better approach (to not include page and JSON returning methods in one Controller).
Notice! You can use #RestController only for class (instead of #Controller), not for methods.
If you will open source of this annotation, you will see among other things the next:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Controller
#ResponseBody
public #interface RestController {
String value() default "";
}
and ElementType.TYPE has comment:
Class, interface (including annotation type), or enum declaration
I'm using Bean Validation with RestEasy in Wildfly 8.2.0.Final:
#Path("/user")
#Produces(MediaType.APPLICATION_JSON)
public class UserEndpoint
{
//more code
#GET
#Path("/encrypt/{email}")
public Response fetchEncryptedId(#PathParam("email") #NotNull String email)
{
String encryptedUserId = userService.getEncryptedUserId(email);
return Response.ok().entity(new UserBo(encryptedUserId)).build();
}
}
This basically works. Now I'd like to get the response as JSON object but I can't get it working. All my "application" exceptions are handled by my Exception Mapper, this works:
#Provider
public class DefaultExceptionMapper implements ExceptionMapper<Exception>
{
private static final String MEDIA_TYPE = "application/json";
private LoggingService loggingService;
#EJB
public void setLoggingService(LoggingService loggingService)
{
this.loggingService = loggingService;
}
#Override
public Response toResponse(Exception exception)
{
ResponseObject responseObject = new ResponseObject();
responseObject.registerExceptionMessage(exception.getMessage());
if (exception instanceof ForbiddenException)
{
loggingService.log(LogLevel.ERROR, ((ForbiddenException)exception).getUserId(), ExceptionToStringMapper.map(exception));
return Response.status(Status.FORBIDDEN).type(MEDIA_TYPE).entity(responseObject).build();
}
//more handling
loggingService.log(LogLevel.ERROR, "", ExceptionToStringMapper.map(exception));
return Response.status(Status.INTERNAL_SERVER_ERROR).type(MEDIA_TYPE).entity(responseObject).build();
}
}
But bean validation somehow bypasses it. Then I thought about using Throwable instead of Exception but it didn't help either. I guess the ExceptionMapper is not triggered because there is some life cycle problem with JAX-RS and JSR303. But how can I syncronize them to handle bean validation exceptions?
Additional information: The exception passes the javax.ws.rs.container.ContainerResponseFilter so I could write some workaround by implementing the filter method in a subclass, but this is not clean solution. The target is to handle the exceptions in the Exception mapper.
It's not always the case that your ExceptionMapper<Exception> will catch all exception under the Exception hierarchy. If there is another more specific mapper, say one for RuntimeException, that mapper will be used for all exception of RuntimeException and its subtypes.
That being said (assuming you're using resteasy-validation-provider-11), there is already a ResteasyViolationExceptionMapper that handles ValidationException.
#Provider
public class ResteasyViolationExceptionMapper
implements ExceptionMapper<ValidationException>
This mapper is automatically registered. It returns results in the form of a ViolationReport. The client needs to set the Accept header to application/json in order to see a response similar to
{
"exception":null,
"fieldViolations":[],
"propertyViolations":[],
"classViolations":[],
"parameterViolations":[
{
"constraintType":"PARAMETER",
"path":"get.arg0",
"message":"size must be between 2 and 2147483647",
"value":"1"}
],
"returnValueViolations":[]
}
You can see more at Violation reporting.
If you want to completely override this behavior, you can create a more specific mapper for ResteasyViolationException, which is the exception thrown by the RESTeasy validator
#Provider
public class MyValidationMapper
implements ExceptionMapper<ResteasyViolationException> {
#Override
public Response toResponse(ResteasyViolationException e) {
}
}
I'm a litte bit lost, I have to admit. I wrote a Spring Boot (1.3M2) application that receives a JSON object which it needs to store in a database:
#RequestMapping(value = "/fav", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<String> setFavorite(#RequestBody List<Favorite> favorites) {
...
internally this method passes the JSON to another method which stores it line by line in a database:
jdbcTemplate.batchUpdate(INSERT_FAVORITE, new BatchPreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Favorit fav = favorites.get(i);
ps.setString( ...
}
#Override
public int getBatchSize() {
int size = favorites.size();
return size;
}
When I POST a JSON to the controller which does not match the structure of my Favorite-object I only see null values in my database. Obviously Jackson tries its best to convert my JSON into a Java object but fails and sets all values of the object it finds no value for to null.
Then this list of sort of empty objects is written to the database.
I use curl to POST the values
curl -vX POST https://localhost/fav -d #incorrectype.json
This can't be the source of error because it works with a favorite.json. How can I have my controller / Jackson detect if I use a JSON that does not match ?
One solution is to use annotations from javax.validation, and instead of accepting a List in the controller signature, use a custom wrapper along the lines of this (getters/setters omitted):
public class FavoriteList {
#Valid
#NotNull
#Size(min = 1)
private List<Favorite> favorites;
}
then for the Favorite class add the validation as needed, e. g.:
public class Favorite {
#NotNull
private String id;
}
with these changes in place, modify the controller method signature along these lines:
public ResponseEntity<String> setFavorite(#Valid #RequestBody FavoriteList favoritesList) {
This way, input failing validation will throw exceptions before anything in the controller method is executed.
I am working with Spring MVC using JSON objects. while I am tring to send JSON Object from RESTClient, I am getting
HTTP Status 400 - The request sent by the client was syntactically incorrect ().
This is my controller
ObjectMapper mapper=new ObjectMapper();
#RequestMapping(value = "/addTask", method = RequestMethod.GET)
public ModelAndView addTask(#RequestParam("json") String json) throws JsonParseException, JsonMappingException, IOException
{
System.out.println("Json object from REST : "+json);
Task task=(Task) mapper.readValue(json, Task);
service.addService(task);
return new ModelAndView("Result");
}
My request URL : http://localhost:8080/Prime/addTask
My Json Object :
{"taskName":"nothing","taskId":1234,"taskDesc":"nothing doing"}
Also i tried specifying "Content-Type: application/json" in RESTClient but still am getting the same error
I ran into a similar situation using a JSON string in the request body recently, and using a very similar Spring setup as yours. In my case I wasn't specifying a String parameter and deserialising it myself though, I was letting Spring do that:
#RequestMapping(value = "/myService/{id}", method = RequestMethod.POST)
#ResponseBody
public void myService(#PathVariable(value = "id") Long id, #RequestBody MyJsonValueObject request) {
..
}
I was getting an HTTP error 400 "The request sent by the client was syntactically incorrect" response. Until I realised that there wasn't a default constructor on the #RequestBody MyJsonValueObject so there were problems deserialising it. That problem presented in this way though.
So if you are using POST and objects, and getting errors like this, make sure you have a default constructor! Add some JUnit to be sure you can deserialise that object.
Note: I'm not saying this is the only reason you get this error. The original case used just String (which does have a default constructor !) so it's a little different. But in both cases it appears the request URI appears to have been mapped to the right method, and something has gone wrong trying to extract parameters from the HTTP request.
Try this
Change
#RequestParam("json") String json
To
#RequestBody Task task
If you are not interested in POST method you can try this
change your Controller method from
#RequestMapping(value = "/addTask", method = RequestMethod.GET)
public ModelAndView addTask(#RequestParam("json") String json)
to
#RequestMapping(value = "/addTask/{taskName}/{taskId}/{taskDesc}", method = RequestMethod.GET)
public ModelAndView addTask(#RequestParam("taskName") String taskName,
#RequestParam("taskId") String taskId,#RequestParam("taskDesc") String taskDesc)
and change your URL to
http://localhost:8080/Prime/addTask/mytask/233/testDesc
My problem was due to the incorrect mapping of the #RequestBody object.
My Request Body looks like this
{data: ["1","2","3"]}
I had the following code in my controller
#RequestMapping(value = "/mentee", method = RequestMethod.POST)
public #ResponseBody boolean updateData(#RequestBody List<Integer> objDTO, HttpSession session) {
...
}
This give me HTTP 400 because Spring doesn't know how to bind my Json data to a List.
I changed the RequestBody object to the following
#RequestMapping(value = "/mentee", method = RequestMethod.POST)
public #ResponseBody boolean updateData(#RequestBody ObjectiveDto objDTO, HttpSession session) {
...
}
and defined ObjectiveDto as followed
#ToString
public class ObjectiveDto {
#Getter #Setter
private List<Integer> data;
}
This resolved the HTTP 400 error.
I have a JAX-RS REST service implemented using Jersey. One of the cool features of JAX-RS/Jersey is how easily a POJO can be turned into a REST service, simply by sprinkling a few Java annotations... including a trivially easy mechanism for translating POJOs to JSON - using JAXB annotations.
Now, I'd like to be able to take advantage of this cool JSON-ifying functionality for non-REST purposes - I'd love to be able to just serialize some of these objects to disk, as JSON text. Here's an example JAXB object that I'd want to serialize:
#XmlRootElement(name = "user")
public class UserInfoImpl implements UserInfo {
public UserInfoImpl() {}
public UserInfoImpl(String user, String details) {
this.user = user;
this.details = details;
}
public String getUser() { return user; }
public void setUser(String user) { this.user = user; }
public String getDetails() { return details; }
public void setDetails(String details) { this.details = details; }
private String user;
private String details;
}
Jersey can turn one of these into json with no additional info. I'm wondering if Jersey has exposed this functionality in the API for needs like mine? I've had no luck finding it so far...
Thanks!
UPDATE 2009-07-09: I have learned that I can use the Providers object to almost do exactly what I want:
#Context Providers ps;
MessageBodyWriter uw = ps.getMessageBodyWriter(UserInfoImpl.class, UserInfoImpl.class, new Annotation[0], MediaType.APPLICATION_JSON_TYPE);
uw.writeTo(....)
... This writes the object as json to any outputstream, which would be perfect for me, but I can only get at the Providers object using #Context from a #Component object. Does anyone know how to access it from a regular, un-annotated POJO? Thanks!
Jersey uses a couple different frameworks depending on whether you use mapped(), badgerfish(), or natural() notation. Natural is usually the one people want. And that's implemented using the very good (and very fast) standalone Jackson JSON processor, I believe, which goes from Object->JAXB->JSON. However Jackson also provides it's own JAX-RS provider to go direct Object->JSON.
In fact, they even added support for JAXB annotations. Have a look at
http://wiki.fasterxml.com/JacksonJAXBAnnotations
I think that's ultimately what you are looking for. Jackson does Object<->JSON processing...Jersey just makes the calls for you
Here's a simple brief example of using JAXB to map objects to JSON (using Jackson):
http://ondra.zizka.cz/stranky/programovani/java/jaxb-json-jackson-howto.texy
ObjectMapper mapper = new ObjectMapper();
String str = mapper.writeValueAsString(pojoObject);
JAXB annotations work fine when serializing to XML.
The main problem is that JAXB does not support empty arrays. So when serializing something like this...
List myArray = new ArrayList();
...to json via jaxb anottations all your empty arrays become null instead of [].
To solve this you can just serialize your pojos directly to json via jackson.
Take a look at this from Jersey's user guide:
http://jersey.java.net/nonav/documentation/latest/user-guide.html#d0e1959
This is the best way to use Jackson provider without JAXB. Moreover, you can always use the latest version of jackson by downlaoding jackson-all-x.y.z-jar from its web.
This method will not interfere with your jaxb annotations so I would suggest to have a try!
Since Jersey is a reference implementation of JAX-RS and JAX-RS is focused completely on providing a standard way of implementing the end-point for the REST service the issues of serializing the payload is left to other standards.
I think that if they included object serialization in the JAX-RS standard it would quickly become a large multi-headed beast that would be difficult to implement and loose some of it's focus.
I appreciate how focused Jersey is on delivering clean and simple to use REST endpoints. In my case I've just subclassed a parent that has all the JAXB plumbing in it so marshalling objects between binary and XML is very clean.
With a little Jersey specific bootstrapping, you can use it to create the necessary JSON objects for you. You need to include the following dependencies (you can use bundle, but it will cause problems if you are using Weld for testing):
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.12</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.12</version>
</dependency>
From there you can create a JAXB annotated class. The following is an example:
#XmlRootElement
public class TextMessage {
private String text;
public String getText() { return text; }
public void setText(String s) { this.text = text; }
}
Then you can create the following unit test:
TextMessage textMessage = new TextMessage();
textMessage.setText("hello");
textMessage.setUuid(UUID.randomUUID());
// Jersey specific start
final Providers ps = new Client().getProviders();
// Jersey specific end
final MultivaluedMap<String, Object> responseHeaders = new MultivaluedMap<String, Object>() {
#Override
public void add(final String key, final Object value) {
}
#Override
public void clear() {
}
#Override
public boolean containsKey(final Object key) {
return false;
}
#Override
public boolean containsValue(final Object value) {
return false;
}
#Override
public Set<java.util.Map.Entry<String, List<Object>>> entrySet() {
return null;
}
#Override
public List<Object> get(final Object key) {
return null;
}
#Override
public Object getFirst(final String key) {
return null;
}
#Override
public boolean isEmpty() {
return false;
}
#Override
public Set<String> keySet() {
return null;
}
#Override
public List<Object> put(final String key, final List<Object> value) {
return null;
}
#Override
public void putAll(
final Map<? extends String, ? extends List<Object>> m) {
}
#Override
public void putSingle(final String key, final Object value) {
}
#Override
public List<Object> remove(final Object key) {
return null;
}
#Override
public int size() {
return 0;
}
#Override
public Collection<List<Object>> values() {
return null;
}
};
final MessageBodyWriter<TextMessage> messageBodyWriter = ps
.getMessageBodyWriter(TextMessage.class, TextMessage.class,
new Annotation[0], MediaType.APPLICATION_JSON_TYPE);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
Assert.assertNotNull(messageBodyWriter);
messageBodyWriter.writeTo(textMessage, TextMessage.class,
TextMessage.class, new Annotation[0],
MediaType.APPLICATION_JSON_TYPE, responseHeaders, baos);
final String jsonString = new String(baos.toByteArray());
Assert.assertTrue(jsonString.contains("\"text\":\"hello\""));
The advantage to this approach is it keeps everything within the JEE6 API, no external libraries are explicitly needed except for testing and getting the providers. However, you need to create an implementation of MultivaluedMap since there is nothing provided in the standard and we don't actually use it. It may also be slower than GSON, and a lot more complicated than necessary.
I understand XML views but it would have shown some foresight to require JSON support for POJOs as standard equipment. Having to doctor up JSON identifiers with special characters makes no sense if your implementation is JSON and your client is a JavaScript RIA.
Also, not that Java Beans are NOT POJOs. I would like to use something like this on the outer surface of my web tier:
public class Model
{
#Property height;
#Property weight;
#Property age;
}
No default constructor, no getter/setter noise, just a POJO with my own annotations.