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.
Related
I'm reading https://jersey.github.io/documentation/latest/filters-and-interceptors.html and http://www.dropwizard.io/1.1.4/docs/manual/core.html#jersey-filters to try and make this:
#CookieParam("User-Data") userData: String,
#HeaderParam("User-Agent") userAgent: String,
Not needed in each and every resource GET method of my web app. userData is json data from a cookie with fields like "name" and "id" and userAgent is the full User-Agent string from the header. For each view I pass in:
AppUser.getName(userData), AppUser.isMobile(userAgent)
The getName function parses the json and returns just the name field and the isMobile function returns a true boolean if the string "mobile" is found.
I use this in each view of the app in FreeMarker to display the user's name and to change some layout stuff if mobile is true.
Is there a way to make this less repetitive? I'd rather use a BeforeFilter to just set this automatically each time.
Sounds like something you can just do in a ContainerResponseFilter, which gets called after the return of the view resource/controller. Assuming you are returning a Viewable, you get the Viewable from the ContainerRequestContext#getEntity, get the model from it, and add the extra information to the model.
#Provider
#UserInModel
public class UserInModelFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext request,
ContainerResponseContext response) throws IOException {
Cookie cookie = request.getCookies().get("User-Data");
String header = request.getHeaderString("User-Agent");
String username = AppUser.getName(cookie.getValue());
boolean isMobile = AppUser.isMobile(header);
Viewable returnViewable = (Viewable) response.getEntity();
Map<String, Object> model = (Map<String, Object>) returnViewable.getModel();
model.put("username", username);
model.put("isMobile", isMobile);
}
}
The #UserInModel annotation is a custom Name Binding annotation, which is used to determine which resource classes or methods should go through this filter. Since you don't want all endpoints to go through this filter, just annotate the methods or classes you want.
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface UserInModel {
}
#Path("/")
public class IndexController {
#GET
#UserInModel
#Produces(MediaType.TEXT_HTML)
public Viewable home() {
Map<String, Object> model = new HashMap<>();
return new Viewable("/index", model);
}
}
With Dropwizard, all you need to do is register the filter.
env.jersey().register(UserInModelFilter.class);
If you want to do some preprocessing of the cookie and header before the resource method is called, you can do that in a ContainerRequestFilter, which can also be name bound. And instead of recalculating the AppUser.xxx method in the response filter, you can also just set a property on the ContainerRequestContext#setProperty that you can later retrieve from the same context (getProperty) in the response filter.
UPDATE
The above answer assumes you are using Jersey's MVC support, hence the use of Viewable. If you are using Dropwizard's view support, then it's not much different. You may want to create an abstract class as a parent for all the view classes, that way you can just cast to the abstract type when retrieving the entity from the filter.
public class AbstractView extends View {
private String userName;
private boolean isMobile;
protected AbstractView(String templateName) {
super(templateName);
}
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public boolean isMobile() { return isMobile; }
public void setIsMobile(boolean mobile) { isMobile = mobile; }
}
public class PersonView extends AbstractView {
private final Person person;
public PersonView(Person person) {
super("person.ftl");
this.person = person;
}
public Person getPerson() {
return this.person;
}
}
In the filter
#Provider
#UserInModel
public class UserInModelFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext request,
ContainerResponseContext response) throws IOException {
Cookie cookie = request.getCookies().get("User-Data");
String header = request.getHeaderString("User-Agent");
String username = AppUser.getName(cookie.getValue());
boolean isMobile = AppUser.isMobile(header);
AbstractView returnViewable = (AbstractView) response.getEntity();
returnViewable.setUserName(username);
returnViewable.setIsMobile(isMobile);
}
}
Tested resource class for completeness
#Path("person")
public class PersonController {
#GET
#UserInModel
#Produces(MediaType.TEXT_HTML)
public PersonView person() {
Person person = new Person("peeskillet#fake.com");
return new PersonView(person);
}
}
I'd like to create a single method and configure both GET + POST on it, using spring-mvc:
#RestController
public class MyServlet {
#RequestMapping(value = "test", method = {RequestMethod.GET, RequestMethod.POST})
public void test(#Valid MyReq req) {
//MyReq contains some params
}
}
Problem: with the code above, any POST request leads to an empty MyReq object.
If I change the method signature to #RequestBody #Valid MyReq req, then the post works, but the GET request fails.
So isn't is possible to just use get and post together on the same method, if a bean is used as input parameters?
The best solution to your problem seems to be something like this:
#RestController
public class MyServlet {
#RequestMapping(value = "test", method = {RequestMethod.GET})
public void testGet(#Valid #RequestParam("foo") String foo) {
doStuff(foo)
}
#RequestMapping(value = "test", method = {RequestMethod.POST})
public void testPost(#Valid #RequestBody MyReq req) {
doStuff(req.getFoo());
}
}
You can process the request data in different ways depending on how you receive it and call the same method to do the business logic.
#RequestMapping(value = "/test", method = { RequestMethod.POST, RequestMethod.GET })
public void test(#ModelAttribute("xxxx") POJO pojo) {
//your code
}
This will work for both POST and GET. (make sure the order first POST and then GET)
For GET your POJO has to contain the attribute which you're using in request parameter
like below
public class POJO {
private String parameter1;
private String parameter2;
//getters and setters
URl should be like below
/test?parameter1=blah
Like this way u can use it for both GET and POST
I was unable to get this working on the same method and I'd like to know a solution, but this is my workaround, which differs from luizfzs's in that you take the same request object and not use #RequestParam
#RestController
public class Controller {
#GetMapping("people")
public void getPeople(MyReq req) {
//do it...
}
#PostMapping("people")
public void getPeoplePost(#RequestBody MyReq req) {
getPeople(req);
}
}
I'm looking for a way to pass a map that contains param names and values to a GET Web Target. I am expecting RESTEasy to convert my map to a list of URL query params; however, RESTEasy throws an exception saying Caused by: javax.ws.rs.ProcessingException: RESTEASY004565: A GET request cannot have a body.
. How can I tell RESTEasy to convert this map to a URL query parameters?
This is the proxy interface:
#Path("/")
#Consumes(MediaType.APPLICATION_JSON)
public interface ExampleClient {
#GET
#Path("/example/{name}")
#Produces(MediaType.APPLICATION_JSON)
Object getObject(#PathParam("name") String name, MultivaluedMap<String, String> multiValueMap);
}
This is the usage:
#Controller
public class ExampleController {
#Inject
ExampleClient exampleClient; // injected correctly by spring DI
// this runs inside a spring controller
public String action(String objectName) {
MultivaluedMap<String, String> params = new MultivaluedHashMap<>();
// in the real code I get the params and values from a DB
params.add("foo", "bar")
params.add("jar", "car")
//.. keep adding
exampleClient.getObject(objectName, params); // throws exception
}
}
After hours digging down in RESTEasy source code, I found out that there is no way to do that though interface annotation. In short, RESTEasy creates something called a 'processor' from org.jboss.resteasy.client.jaxrs.internal.proxy.processors.ProcessorFactory to map the annotation to the target URI.
However, it is really simple to solve this issue by creating a ClientRequestFilter that takes the Map from the request body (before executing the request of course), and place them inside the URI query param. Check the code below:
The filter:
#Provider
#Component // because I'm using spring boot
public class GetMessageBodyFilter implements ClientRequestFilter {
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
if (requestContext.getEntity() instanceof Map && requestContext.getMethod().equals(HttpMethod.GET)) {
UriBuilder uriBuilder = UriBuilder.fromUri(requestContext.getUri());
Map allParam = (Map)requestContext.getEntity();
for (Object key : allParam.keySet()) {
uriBuilder.queryParam(key.toString(), allParam.get(key));
}
requestContext.setUri(uriBuilder.build());
requestContext.setEntity(null);
}
}
}
PS: I have used Map instead of MultivaluedMap for simplicity
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();
I want to authorize calls made to my rest api differently depending on which method is being called. But the RequestHandler looks like this:
public interface RequestHandler {
Response handleRequest(Message m,
ClassResourceInfo resourceClass);
}
I can't figure out how to get the Method that will be called from that resourceClass. Is this possible?
The ResponseHandler seems to have a parameter that can do this named OperationResourceInfo:
public interface ResponseHandler {
Response handleResponse(Message m,
OperationResourceInfo ori,
Response response);
}
But by that time, I will have already deleted something I had no permission to delete (as an example).
How do I figure out what method will be called in a request filter? FWIW, the reason I want the Method is because I want to search for a custom built annotation I will put on each method. If there is a better way to approach this, I'm open to the idea.
For completeness, here's the documentation on the topic: http://cxf.apache.org/docs/jax-rs-filters.html
You can use Interceptors, rather than RequestHandler filters as the request handlers are deprecated and replaced in JAXRS 2.0 with ContainerRequestFilter and ContainerResponseFilter
For Example
Say I've RestService shown below
#Service
#Path("/Course")
public class KPRestService {
private final Logger LOG = LoggerFactory.getLogger(KPRestService.class);
#POST
#Path("/create")
#Consumes(MediaType.APPLICATION_JSON)
public Response create(CourseType course){
LOG.info("You have selected {}", course.getCName());
return Response.ok().build();
}
#POST
#Path("/get")
#Produces(MediaType.APPLICATION_JSON)
public CourseType get(#FormParam("cDate")Date date){
final CourseType course = new CourseType();
if(date.after(new Date())){
course.setCName("E&C");
course.setCDuration(4);
}else{
course.setCName("Mech");
course.setCDuration(3);
}
return course;
}
}
I prevent calling the get method using interceptor as shown below.
#Component
public class KPFilter extends AbstractPhaseInterceptor<Message> {
private final static Logger LOG = LoggerFactory.getLogger(KPFilter.class);
public KPFilter() {
super(Phase.PRE_LOGICAL);
}
public void handleMessage(Message message) throws Fault {
final Exchange exchange = message.getExchange();
exchange.put(Message.REST_MESSAGE, Boolean.TRUE);
OperationResourceInfo resourceInfo = exchange.get(OperationResourceInfo.class);
LOG.info("Method name is {}", resourceInfo.getMethodToInvoke().getName());
if (resourceInfo != null && resourceInfo.getMethodToInvoke().getName().equals("get")) {
Response response = Response.status(Response.Status.FORBIDDEN).entity("You are not authorised")
.type(MediaType.TEXT_XML).build();
exchange.put(Response.class, response);
}
}
}