Jersey/Tomcat - Producing media conflict - java

I am using a restful web service in which CRUD operations work, except for listing every single user on one page. The getUser() method is only used for logging into the webapp. I already took a look at this question, but I am not using named queries.
The error I am getting::
SEVERE: Producing media type conflict. The resource methods public
...UserResource.getUser() and ...UserResource.list() throws
org.codehaus.jackson.JsonGenerationException,org.codehaus.jackson.map.JsonMappingException,java.io.IOException
can produce the same media type
UserResource.list()
#GET
#Produces(MediaType.APPLICATION_JSON)
public String list() throws JsonGenerationException, JsonMappingException, IOException {
this.logger.info("list()");
ObjectWriter viewWriter;
if (this.isAdmin()) {
viewWriter = this.mapper.writerWithView(JsonViews.Admin.class);
} else {
viewWriter = this.mapper.writerWithView(JsonViews.User.class);
}
List<User> allEntries = this.userDao.findAll();
return viewWriter.writeValueAsString(allEntries);
}
UserResource.getUser()
/**
* Retrieves the currently logged in user.
*
* #return A transfer containing the username and the roles.
*/
#GET
#Produces(MediaType.APPLICATION_JSON)
public UserTransfer getUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
if (principal instanceof String && ((String) principal).equals("anonymousUser")) {
throw new WebApplicationException(401);
}
UserDetails userDetails = (UserDetails) principal;
return new UserTransfer(userDetails.getUsername(), this.createRoleMap(userDetails));
}
Thanks in advance,

Your resources are to the same path, and there is nothing to differentiate them when Jersey needs to select a method (they have the same HTTP method, same path, same media type). The error is about media types, because it is completely possible to have two methods on the same path and HTTP method, just with different media types. This differentiates them
#GET
#Produces(MediaType.APPLICATION_XML)
public String list();
#GET
#Produces(MediaType.APPLICATION_JSON)
public String getUser();
But this it probably not what you want. So the solution is to just change the path on one of them
#GET
#Produces(MediaType.APPLICATION_JSON)
public String list();
#GET
#Path("/loggedInUser")
#Produces(MediaType.APPLICATION_JSON)
public String getUser();

Related

Spring return unauthorized when it is not data of user

I have a endpoint, where the user sees the info of hic user account. But an user should only be able to see his own data and not of another user account. So my function looks like this
#GetMapping("{userId}")
#Operation(security = #SecurityRequirement(name = OpenApiConfig.USER_AUTH))
public GetUserResponse getUser(#PathVariable UUID userId, Principal principal) {
AppUser user = getUserService.findByUsername(principal.getName()).get();
if(!user.getId().equals(userId)){
//return ResponseEntity unauthorized
}
return GetUserResponse.of(getUserService.getUser(userId).orElseThrow());
}
But as you can see I return a GetUserResponse, how can I return something like a ResponseEntity with unauthorized return code?
My GetUserResponse class looke like this
public class GetUserResponse {
private UUID id;
private String name;
public static GetUserResponse of(AppUser user) {
return new GetUserResponse(user.getId(), user.getUsername());
}
public GetUserResponse(UUID id, String name) {
this.id = id;
this.name = name;
}
As it is written now I would simply remove the parameter and use the id from the principle thus making it impossible to request information about somebody else.
If you would like to be able to let some users (admin, super etc) be able to use the same method then you could create your own exception and use the ResponseStatus annotation on that. Then simply throw that exception if the user id don't match up and the user isn't authorized to read other users.
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
#ResponseStatus(code = HttpStatus.UNAUTHORIZED, reason = "Invalid target id")
public class InvalidTarget extends RuntimeException {}
Then just throw that exception if the user isn't allowed to lookup others.
You can find more information about exceptions and spring in this blog entry:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
If you want to throw an exception and return a generic response using ResponseEntity, you can use #ControllerAdvice annotation.
#ControllerAdvice annotation allows you to handle the exceptions in the whole application. It is an annotation-driven interceptor. Refer to this link to know more about exception handling and #ControllerAdvice annotation.
Refer to the below code to get more understanding about #ControllerAdvice.
Controller where the exception is thrown
#GetMapping("{userId}")
#Operation(security = #SecurityRequirement(name = OpenApiConfig.USER_AUTH))
public GetUserResponse getUser(#PathVariable UUID userId, Principal principal) {
AppUser user = getUserService.findByUsername(principal.getName()).get();
if(!user.getId().equals(userId)){
throw new UnauthorisedException("Unauthorised User");
}
return GetUserResponse.of(getUserService.getUser(userId).orElseThrow());
}
UnauthorisedException.java
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public class UnauthorisedException extends RuntimeException {
public UnauthorisedException(String message) {
super(message);
}
}
Class with #ControllerAdvice annotation
#ControllerAdvice
public class ControllerAdviceExceptionHandler {
#ExceptionHandler(UnauthorizedException.class)
protected ResponseEntity<?> unauthorisedException(final UnauthorizedException exception) {
return new ResponseEntity<>(exception.getMessage(), HttpStatus.UNAUTHORIZED);
}

jetty: store request-specific information

I am creating some sort of RESTful API with basic auth. To handle the auth information I added a custom ContainerRequestFilter. This works quite good, but I want to set global information like the "username" of the caller. How can I set global/request-specific information or properties and get them within a "Controller" method?
//some filter
public class AuthFilter implements ContainerRequestFilter{
//...
#Override
public void filter( ContainerRequestContext requestContext ) throws IOException {
requestContext.setProperty("username", "someusername");
}
//...
}
//example "route-handler"
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Event> getEvents() {
//HOW to get the username property?!
}
You can inject HttpServletRequest into your controller and use HttpServletRequest.getAttribute to retrieve the values you set in ContainerRequestContext.setProperty.
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Event> getEvents(#Context HttpServletRequest req) {
String username = (String) req.getAttribute("username");
...
}
I've used that on Glassfish/Jersey and it works fine so it should work in your environment.

How I return HTTP 404 JSON/XML response in JAX-RS (Jersey) on Tomcat?

I have the following code:
#Path("/users/{id}")
public class UserResource {
#Autowired
private UserDao userDao;
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public User getUser(#PathParam("id") int id) {
User user = userDao.getUserById(id);
if (user == null) {
throw new NotFoundException();
}
return user;
}
If I request for a user that doesn't exists, like /users/1234, with "Accept: application/json", this code returns an HTTP 404 response like one would expect, but returns Content-Type sets to text/html and a body message of html. Annotation #Produces is ignored.
Is it a problem of code or a problem of configuration?
Your #Produces annotation is ignored because uncaught exceptions are processed by the jax-rs runtime using a predefined (default) ExceptionMapper If you want to customize the returned message in case of a specific exception you can create your own ExceptionMapper to handle it. In your case you need one to handle the NotFoundException exception and query the "accept" header for the requested type of the response:
#Provider
public class NotFoundExceptionHandler implements ExceptionMapper<NotFoundException>{
#Context
private HttpHeaders headers;
public Response toResponse(NotFoundException ex){
return Response.status(404).entity(yourMessage).type( getAcceptType()).build();
}
private String getAcceptType(){
List<MediaType> accepts = headers.getAcceptableMediaTypes();
if (accepts!=null && accepts.size() > 0) {
//choose one
}else {
//return a default one like Application/json
}
}
}
You can use the Response return. Example below:
#GET
#Path("{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response get(#PathParam("id") Long id) {
ExampleEntity exampleEntity = getExampleEntityById(id);
if (exampleEntity != null) {
return Response.ok(exampleEntity).build();
}
return Response.status(Status.NOT_FOUND).build();
}
that 404 is returned by your server as it is expected that you will pass things in following form
/users/{id}
but you are passing it as
/users/user/{id}
which resource is not existing at all
try accessing resource as /users/1234
EDIT:
create a class like
class RestResponse<T>{
private String status;
private String message;
private List<T> objectList;
//gettrs and setters
}
now in case you want response for User you can create it as following
RestResponse<User> resp = new RestResponse<User>();
resp.setStatus("400");
resp.setMessage("User does not exist");
and signature of your rest method would be like following
public RestResponse<User> getUser(#PathParam("id") int id)
while in case successful response you can set things like
RestResponse<User> resp = new RestResponse<User>();
List<User> userList = new ArrayList<User>();
userList.add(user);//the user object you want to return
resp.setStatus("200");
resp.setMessage("User exist");
resp.setObjectList(userList);

Removing duplication from Spring controllers

I have been looking for a way to somehow reduce the amount of code that is duplicated with subtle variance in my Spring MVC controllers, but searching through the SO questions so far has only yielded some questions without any satisfactory answers.
One example of duplication that I want to remove is this, where the user creation page and the role creation page share similarities:
#RequestMapping(value = "user/create", method = RequestMethod.GET)
public String create(#ModelAttribute("user") User user, BindingResult errors) {
LOG.debug("Displaying user creation page.");
return "user/create";
}
#RequestMapping(value = "role/create", method = RequestMethod.GET)
public String create(#ModelAttribute("role") Role role, BindingResult errors) {
LOG.debug("Displaying role creation page.");
return "role/create";
}
A slightly more involved variant of duplication that I would like to remove is the one for posting the create form:
#RequestMapping(value = "user/create", method = RequestMethod.POST)
public String save(#ModelAttribute("user") User user, BindingResult errors) {
LOG.debug("Entering save ({})", user);
validator.validate(user, errors);
validator.validatePassword(user, errors);
validator.validateUsernameAvailable(user, errors);
String encodedPassword = encoder.encode(user.getPassword());
user.setPassword(encodedPassword);
if (errors.hasErrors()) {
return create(user, errors);
} else {
service.save(user);
}
return "redirect:/user/index/1";
}
#RequestMapping(value = "role/create", method = RequestMethod.POST)
public String save(#ModelAttribute("role") Role role, BindingResult errors) {
LOG.debug("Entering save({})", role);
validator.validate(role, errors);
if (errors.hasErrors()) {
return create(role, errors);
} else {
service.save(role);
}
return "redirect:/index";
}
This example includes a validate then save if correct and a redirect to the error page if things don't go as planned.
How to remove this duplication?
Spring uses your handler method parameter types to create class instances from the request parameters or body. As such, there is no way to create a handler (#RequestMapping) method that could take an Object and check if it is either a Role or a User. (Technically you could have both parameters and just check which one isn't null, but that is terrible design).
Consequently, you need a handler method for each. This makes sense since, even through the logic is similar, it is still specific to the exact type of model object you are trying to create. You perform different validation, call a different service method, and return a different view name.
I say your code is fine.
Thought I would provide the solution that I settled on in the hope that it might help someone. My gf suggested that I use the name of the entity as a path variable for the controller, and this has proved to provide a very nice solution for the problem at hand.
The two methods now look like this:
#RequestMapping(value = "{entityName}/create", method = RequestMethod.GET)
public String create(#PathVariable("entityName") String entityName, #ModelAttribute("entity") BaseEntity entity, BindingResult errors) {
LOG.debug("Displaying create page for entity named: [{}]", entityName);
return handlerFactory.getHandler(entityName).getCreateView();
}
#RequestMapping(value = "{entityName}/create", method = RequestMethod.POST)
public String save(#PathVariable("entityName") String entityName, #ModelAttribute("entity") BaseEntity entity, BindingResult errors) {
LOG.debug("Saving entity of type {}", entityName);
CrudHandler handler = handlerFactory.getHandler(entityName);
handler.getCreateValidator().validate(entity, errors);
if (errors.hasErrors()) {
return create(entityName, entity, errors);
}
handler.preSave(entity);
handler.getService().save(entity);
return "redirect:" + DASHBOARD_URL;
}
The CrudHandler interface has implementations for each entity, and provides the controller with the entity specific classes that it needs, such as service and validator. A sample CrudHandler implementation looks like this for me:
#Component
public class RoleCrudHandler implements CrudHandler {
private static final String ENTITY_NAME = "role";
public static final String CREATE_VIEW = "role/create";
public static final String EDIT_VIEW = "role/edit";
#Resource
private RoleService roleService;
#Resource
private RoleValidator validator;
#Resource
private CrudHandlerFactory handlerFactory;
#PostConstruct
public void init() {
handlerFactory.register(ENTITY_NAME, this);
}
#Override
public GenericService getService() {
return roleService;
}
#Override
public Validator getCreateValidator() {
return validator;
}
#Override
public Validator getUpdateValidator() {
return validator;
}
#Override
public BaseEntity createEntity() {
return new Role();
}
#Override
public void preSave(BaseEntity entity) {
}
#Override
public String getCreateView() {
return CREATE_VIEW;
}
#Override
public String getUpdateView() {
return EDIT_VIEW;
}
}
If someone sees some ways to improve this, feel free to share. Hope this will be of use for someone.

"Chaining" two controllers?

A REST-API I am developing allows access to various kinds of user data.
Users can be identified via their Id, their email or their username. A user then has a couple of other data such as orders etc.
Now I am planning to expose the following endpoints:
/rest/user/byemail/test#example.org/profile
/rest/user/byemail/test#example.org/orders
/rest/user/byemail/test#example.org/address
/rest/user/byid/123456/profile
/rest/user/byid/123456/orders
/rest/user/byid/123456/address
/rest/user/byusername/test/profile
/rest/user/byusername/test/orders
/rest/user/byusername/test/address
As you can see, the URL always consists of two "parts": One for identifying the user and the other one for identifying the resource.
It would be great if I could now avoid writing 9 different methods in my controller (as there might be other types of information in the future).
Is it somehow possible to write one UserController which is then returned and parametrized by the MainController?
public class UserController {
#RequestMapping("/profile")
public ModelAndView profile(User user) {
//...
}
#RequestMapping("/orders")
public ModelAndView profile(User user) {
//...
}
#RequestMapping("/address")
public ModelAndView profile(User user) {
//...
}
}
#RequestMapping("/rest/user")
public class MainController {
#RequestMapping("byemail/{email}")
public ??? byEmail(#PathVariable String email) {
User user = //Find user by email
//???
}
#RequestMapping("byusername/{username}")
public ??? byUserName(#PathVariable String username) {
User user = //Find user by username
//???
}
#RequestMapping("byid/{id}")
public ??? byId(#PathVariable String id) {
User user = //Find user by id
//???
}
}
Or is it maybe possible to solve this via some kind of routing?
However, it would be important to "split" the URL and use one half to find the correct user which will then be available as a parameter when parsing the second half.
Why not just one controller with these request mappings?
#RequestMapping("/rest/user")
public class UserController {
#RequestMapping("{keyType}/{key}/orders")
public ModelAndView orders(#PathVariable String keyType, #PathVariable String key) {
User u = findUser(keyType, key);
// ...
}
#RequestMapping("{keyType}/{key}/profile")
public ModelAndView profile(#PathVariable String keyType, #PathVariable String key) {
User u = findUser(keyType, key);
// ...
}
#RequestMapping("{keyType}/{key}/address")
public ModelAndView address(#PathVariable String keyType, #PathVariable String key) {
User u = findUser(keyType, key);
// ...
}
private User findUser(String keyType, String key) {
// ...
}
}
Take a look at Spring Data REST.
This API exposes a SimpleJpaRespository to the web, accessible via RESTful paths.

Categories

Resources