Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jersey filter in Dropwizard to set some global FreeMarker variables

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.

like image 604
Andrew Arrow Avatar asked Aug 25 '17 04:08

Andrew Arrow


1 Answers

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("[email protected]");
        return new PersonView(person);
    }
}
like image 135
Paul Samsotha Avatar answered Nov 05 '22 20:11

Paul Samsotha