Friday, March 11, 2011

JavaServer Faces (JSF2) and JAX-RS don't play well together

I've been working with JavaServer Faces 2 (JSF2) since shortly after its release, and it continues to be an annoying mix of excellent and frustrating to work with. It's theoretically a great technology, especially with CDI (contexts and dependency injection) even despite the holes and quirks in the spec. There are just two wee problems.

First, unlike many of the other EE specs, the JSF2 spec really isn't written to be useful for developers intending to use JSF2 in their apps. It's very much a specification not a reference, and I found it hard to read even as a specification. Because I was getting started with JSF2 in the very early days of EE 6, I seem to remember that the now very helpful Java EE Tutorial wasn't EE6 ready yet or was incomplete, so I didn't have that as a basis for learning JSF2 and EE6 either. Perhaps my memory is just failing me. In any case, I often landed up stumbling through Google-land trying to find out details that I thought should be obvious and easy to find in the spec or other reference documentation. At the time, Google-land was full of outdated references to JSF 1, lots of migration guides from Spring to EE 6, and plenty of other things to side-track a confused newbie. All in all, it was really hard to get going with JSF2 alone.

Second, the JSF2 spec was clearly concocted with no thought of or co-operation with the JAX-RS spec, and it shows. They don't work well together, and it's really, really frustrating because they should be so useful in combination.

Edit: Part of the issue here appears to be that I was trying to use JSF2 just for the Facelets templating language, expecting to be able to interoperate with JAX-RS where possible. JSF2 is a framework and doesn't isn't really designed to enable you to easily step outside the abstractions imposed by the framework - not least because you shouldn't need to. I was probably going about this the wrong way in a square-peg, round-hole kind of way, and shouldn't have been trying to use JSF2 in the first place. That's not made particularly obvious by the docs, though.

Documentation

How do you insert the context path of your servlet into the page? The answer is to use the contextPath property of the implicit request property - but good luck finding that out unless you already know about it, or you've somehow managed to wade through the JSF 2.0 spec to find it. I was using FacesContext to obtain it via a very roundabout route before I finally found out about the implicit request object. There's plenty of information about the pageContext object from the older JSP standard, but that's defunct in JSF2.

Reading this might help you with JSF implicit variables. I wouldn't recommend trying to read the JSF2 spec its self as a reference and learning aid, though, it's really focused on spec implementers and isn't especially accessible to JSF2/facelets users. This one, at least.

JSF2 doesn't mesh well with JAX-RS

The other thing that's increasingly frustrating is the mismatch between JAX-RS (Jersey etc) and JSF2. They have different injection points in different lifecycles, making it hard to make a single class cleanly handle both JSF2 and JAX-RS requests. For example, if your method needs access to the HttpServletRequest, in JAX-RS you add a @Context HttpServletRequest request method parameter, which JAX-RS will automagically pass when calling your method. In JSF2 you access it via (HttpServletRequest)FacesContext.getInitialContext().getExternalContext().getRequest(). These two mechanisms are incompatible, and mean that you have to write two wrapper methods to call the same underlying code depending on how the call came in. Needless to say, that's annoying - and it's enshrined in the JAX-RS and JSF2 specs.

While you can call the JAX-RS method using EL method calls like ${myClass.myMethod(request)} you can't use that as a writable EL property. You land up writing SIX methods (JSF2 getter, JSF2 setter, JAX-RS getter, JAX-RS setter, private impl getter, private impl setter) instead of just annotating the two real implementation methods and letting injection take care of it.

Instead of something like the (non-working) code:

@Named
@Path("/test")
@RequestScoped
public class Something {

  @Inject private HttpServletRequest currentRequest;

  @Path("/property")
  @Produces("text/plain")
  public String getProperty() {
    // do the work here
  }

};

we land up writing something like:

@Named
@Path("/test")
@RequestScoped
public class Something {

  @Path("/property")
  @Produces("text/plain")
  public String getPropertyJAXRS(@Context HttpServletRequest request) {
    return getPropertyImpl(request);
  }

  public String getProperty() {
  getPropertyImpl((HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest());
  }

  private String getPropertyImpl(HttpServletRequest request) {
    // Do the work here
  }

};

This might be fixable with a CDI extension to provide consistent injection of things like the HttpServletRequest, but Seam Servlet (which should theoretically do it) fails to deploy to Glassfish with an NPE. Yay!

Of course, someone will chime up and say "you shouldn't need to do that, stay within the framework". Unfortunately web frameworks like JSF2 have large feature holes that sometimes force you to go to a lower level, especially if you need to inter-operate with other tools or frameworks. How do you get to the application's initial context parameters via facelets-only or jax-rs-only API, for example? You can't, you need to access the ServletContext to do that. How do you accept a multipart/mixed file upload with JSF2? Or exchange JSON data with a client?

Another problem is the difficulty of accessing the JSF2 context to do things like map outcomes to URLs from JAX-RS methods when you want to do something like send a redirect from a JAX-RS POST request to a JSF2 page.

Results

The end result is that whenever I need to add web services capabilities to my beans, I land up converting them to pure JAX-RS beans and converting my JSF2 xhtml across to plain XHTML+JavaScript with JQuery. So much pain just goes away when I'm not trying to mix both systems.

2 comments:

  1. CR> First, the JSF2 documentation (and writing of the JSF2 spec) is
    CR> horrific. One often lands up stumbling through Google-land trying to
    CR> find out details that should be obvious and easy to find in the spec
    CR> or other documentation.

    Have you looked at any of the books on the topic? As the editor of the
    spec, I apologize for not meeting your desired quality requirements. I
    must point out that the spec is written to specify the implementation,
    not how to use it.

    CR> Second, the JSF2 spec was clearly concocted with no thought of or
    CR> co-operation with the JAX-RS spec, and it shows.

    You are correct here. Since the first release of the spec in 2004, we
    have not excerted any effort to simplify the use of these two
    technologies together. I am happy to report that we are working toward
    this during the run of JAX-RS 2.0 and JSF 2.2.

    CR> if your method needs access to the HttpServletRequest

    ...I assert that the method is not operating at a level of abstraction
    which JSF is designed to facilitate. In JSF, all access to the request
    is encapsulated within UI components. It is the components that
    interface with the HttpServletRequest. Your code interacts with the
    components, not the name/value pairs of HTTP directly. Now, I
    understand there are many times when you really just want the darn
    name/value pair, but in that case, JAX-RS is a better fit.

    That said, there is more we can do to make these technologies work well
    together. For example, Facelets is a very useful page templating
    technology in its own right, even using only the ,
    , and tags. I hope that we can make it possible
    to use these tags from JAX-RS generated UIs.

    There are other ideas in this vein as well.

    I would welcome your input on the
    users@javaserverfaces-spec-public.java.net mailing list. Perhaps you
    can help us improve the quality of the spec document.

    Sincerely,

    Ed Burns
    JSF spec editor

    ReplyDelete
  2. First: please accept my apology for my choice of wording re the spec. I wrote this when I was extremely frustrated while learning JSF2 in an EE6 environment and tripping over a fair few bugs in the process, so I wasn't being as diplomatic as I could be. It was rude and I apologize.

    I do stand by my opinion that the JSF2 spec is significantly less readable than most of the other specs referenced under the EE 6 umbrella, and is much less useful for the "end developer" who is trying to work with the specified technology. I realize that its primary purpose is to be a precise and detailed specification, not a how-to document, and that the most important target audience are those creating implementations of the spec. Nonetheless, many of the other EE specs serve as a useful reference for when other resources are insufficient. The JPA 2.0 and CDI specs are two examples of specifications that're extremely useful as references and quite readable.

    I have tried, and do use, a couple of books on EE 6. That said, when I was getting started with EE 6 there was very little out there and much of that wasn't available in Australia. In any case, most of the books on the market focus on particular technologies within EE 6 and often don't spend much time on how they can be used together to satisfy needs that one alone does not.

    As for the issue of abstraction: I agree, you ideally shouldn't need to get access to the HttpServletRequest, and that when you do need to mess with it a servlet filter is usually the right place to do it. Ideally. In practice, sometimes a high level abstraction is the right answer for 95% of what you're doing, but when you need to get the other 5% done you run into trouble and have to step outside the abstraction.

    In this case the issue had more to do with my ignorance and less to do with the abstraction. I should've just written a servlet filter that exposed the information I needed.

    In this case, I needed access to a context parameter from the EE environment. In retrospect I should've used a JNDI environment parameter instead. What I landed up doing in this case was using the preferences API and a setup web page to store the app installation info required instead of getting it from the container.

    As for the users list: Having stuck my foot firmly in my mouth, I think I'll have to do just that.

    Thanks again for reviewing my comments. I realize I've been far from diplomatic in places and that I show my ignorance in more than a few of those places. Perhaps that in its self will be informative, because it's a window into the learning process and experience for someone approaching EE 6 cold, and may help show some of the areas that're harder to grasp than someone already familiar with them would expect.

    ReplyDelete