Design: HtmlUnit and our RichFacesClient
ssilvert Jun 26, 2008 8:48 AMIt's clear that we will need a new version of the RichFacesClient that uses the new HtmlUnit-based JSFClientSession. The good news is, this should be MUCH simpler than the old HttpUnit based RichFacesClient.
I have a few goals in mind for this:
1) It should be easy to use
2) It should be complete. That is, it should provide support for all RichFaces components.
3) It should serve as a model by which someone can create a client for other JSF component libraries.
Feel free to add other goals if I've left something out.
I want to discuss the design of the new RichFacesClient. The old one uses simple delegation when it needs access to the core JSFClientSession. So you create the RichFacesClient like this:
RichFacesClient richClient = new RichFacesClient(jsfClientSession);
RichFacesClient just holds onto an instance of JSFClientSession.
Of course, the alternative to delegation is inheritance. So RichFacesClient could just inherit from JSFClientSession.
public class RichFacesClient extends JSFClientSession
Then the JSFSession becomes a factory. You would get a RichFacesClient like this:
JSFSession jsfSession = new JSFSession("/index.jsf"); RichFacesClient client = (RichFacesClient)jsfSession.getJSFClientSession();
And we would have some way to configure JSFSession as a factory that creates whatever client you are expecting. This approach is nice because you have one object that you use for all client-side services.
The downside comes in when you want to mix in a second component library. For instance, what if you want to use both RichFaces components and the Seam components like <s:link>? Since java doesn't have multiple inheritance, this gets a little tricky.
One solution is to use interfaces for the mixin:
public interface JSFClientSession { // Today's JSFClientSession methods } public class JSFClientSessionImpl implements JSFClientSession {} public interface RichFacesClient { // RichFacesClient methods } public class RichFacesClientImpl implements RichFacesClient {} public interfaces SeamClient { // SeamClient methods } public class SeamClientImpl implements SeamClient {} public class MyJSFUnitClient implements JSFClientSession, RichFacesClient, SeamClient { // delegates private JSFClientSession jsfClientSession; private RichFacesClient richFacesClient; private SeamClient seamClient; public MyJSFUnitClient(JSFServerSession jsfServerSession, Page initialPage) { this.jsfClientSession = new JSFClientSession(jsfServerSession, initialPage); this.richFacesClient = new RichFacesClient(this.jsfClientSession); this.seamClient = new SeamClient(this.jsfClientSession); } // JSFClientSession delegation methods public void click(String componentID) throws IOException { this.jsfClientSession.click(componentID); } // etc. // RichFacesClient delegation methods // SeamClient delegation methods }
Then the developer would use his class like this:
JSFSession jsfSession = new JSFSession("/index.jsf"); MyJSFUnitClient client = (MyJSFUnitClient)jsfSession.getJSFClientSession();
The nice thing about it is that now the developer is free to call any method he needs from that one MyJSFUnitClient instance.
The downside is that the developer has to write the MyJSFUnitClient class. For the RichFaces/Seam combo, we could provide an implementation in JSFUnit proper. But for someone who wants to mix in something like TomahawkClient and SeamClient, they would have to write it themselves.
Thoughts?
Stan