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.
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("[email protected]");
return new PersonView(person);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With