How to work with application modes in JSF

AlexSC :

My application (JSF 2, Java 6, JBoss 7.1) has to offer two operating modes: accessibility mode and non-accessibility mode.

In accessibility mode, some (not all) pages have a particular design to be better read by a screen reader. The difference between the two modes is purely visual, the managed beans are precisely the same. Ideally, no Java code has to be changed.

Most of the work is done:

  1. A link in the page top was added to switch between modes
  2. A backing bean was added to handle the click on the link
  3. When accessibility is turned on, an attribute is added to the session cookie in order to mark it to be accessible
  4. A ResourceResolver was added to rewrite the pages path to the accessible version when accessibility is on for a certain request from a certain user

With all that, it works almost perfectly, but it seems there is some kind of view cache that breaks my solution. Consider the scenario below:

  1. Application starts in non-accessibility mode
  2. I navigate along some pages
  3. I turn accessibility mode on by clicking the corresponding link
  4. I receive a page telling me I'm now in accessibility mode and I notice that the menu has changed to its accessibility version (a different component existing in a different page template)
  5. I navigate to non-visited pages and they all are in accessibility mode
  6. I navigate to pages visited before turning accessibility to on and I see them in non-accessibility version

In the last step we can understand that, even in accessibility mode and with the resource path translation happening (I have logs to proof), the pages are generated as they were in the default, non-accessibility mode.

So, is really there a pages cache in JSF? How can I clear it, so the pages will indeed be rendered again?

Update 1

A network monitoring showed me that the request is indeed issued to the application, so no browser cache is playing here.

AlexSC :

After some time I could finally find a solution, which means, a coding strategy that satisfies all the requirements I had. Maybe it's not a technically good solution, but it is a functinal solution in the sense it produces the experience I needed to provide, saving me to touch all already existing my Java code. There it goes!

1. A managed bean to flip and flop the accessibility mode

The managed bean below is responsible to turn the accessibility on and off in response to the users click on a certain command link. The idea is to add an attribute to the session when the accessibility mode is turned on and remove it when the accessibility mode is turned off.

Those actions return a redirection the current page so the user immedially sees the interface changing from one mode to the other.

When the accessibility is turned on, the view path is changed to append a /ac, because all my accessible views have the same file name that the non-accessibility ones, but in a subdirectory named ac.

@ManagedBean
@RequestScoped
public class AccessibilityMB {
    private boolean accessibilityOn = false;

    @PostConstruct
    public void init() {
        FacesContext context = FacesContext.getCurrentInstance();
        HttpSession session = (HttpSession)context.getExternalContext().getSession(false);
        if(session!=null) {
            Boolean accessibilityMode = (Boolean)session.getAttribute("AccessibilityMode");
            accessibilityOn = accessibilityMode!=null && accessibilityMode;
        }
    }

    public String turnAccessibilityOff() {
        FacesContext context = FacesContext.getCurrentInstance();
        HttpSession session = (HttpSession)context.getExternalContext().getSession(false);
        if(session!=null) {
            session.setAttribute("AccessibilityMode", accessibilityOn = true);
        }
        String viewId = context.getViewRoot().getViewId();
        return viewId+"?faces-redirect=true";
    }

    public String turnAccessibilityOn() {
        FacesContext context = FacesContext.getCurrentInstance();
        HttpSession session = (HttpSession)context.getExternalContext().getSession(false);
        if(session!=null) {
            accessibilityOn = false;
            session.removeAttribute("AccessibilityMode");
        }
        String viewId = context.getViewRoot().getViewId();
        int index = viewId.lastIndexOf("/ac/");
        if(index>-1)
            viewId = viewId.substring(0, index)+viewId.substring(index+3);
        return viewId+"?faces-redirect=true";
    }

    public boolean getAccessibilityOn() {
        return accessibilityOn;
    }
}

2. A PhaseListener to correct the path the views when accessibility mode is on

The PhaseListener just checks for the accessibility mode and, in such a circunstance, rewrites the path the view to look for in the ac subdirectoty. If the desided view exists there, the current component tree is discarded and rebuilt from the accessible version of the same view.

Here the solution is not so good, because JSF already worked to build a component tree and I'm simply discarding it to reworking it from another file.

public class AccessibilityPhaseListener implements PhaseListener{
    private static final long serialVersionUID = 1L;

    @Override
    public void beforePhase(PhaseEvent event) {
        FacesContext context = FacesContext.getCurrentInstance();
        HttpSession session = (HttpSession)context.getExternalContext().getSession(false);
        if(session==null) {
            return;
        }
        Boolean acessibilityMode = (Boolean)session.getAttribute("AcessibilityMode");
        if(acessibilityMode==null || !acessibilityMode)
            return;

        String viewId = context.getViewRoot().getViewId();
        if(acessibilityMode) {
            int index = viewId.lastIndexOf("/");
            viewId = viewId.substring(0, index+1)+"ac/"+viewId.substring(index+1);
        } else {
            int index = viewId.lastIndexOf("/");
            if(viewId.substring(index-3, index).equals("/ac"))
                viewId = viewId.substring(0, index-3)+viewId.substring(index);
        }

        URL url = null;
        try {
            url = context.getExternalContext().getResource(viewId);
        } catch (MalformedURLException e) {
        }

        if(url==null)
            return;
        ViewHandler handler = context.getApplication().getViewHandler();
        UIViewRoot root = handler.createView(context, viewId);

        root.setViewId(viewId);
        context.setViewRoot(root);
    }

    @Override
    public PhaseId getPhaseId() {
        return PhaseId.RENDER_RESPONSE;
    }
}

Conclusion

I could satisfy all my requirements:

  1. No Java code had to be refactored and the application now works in two different modes;
  2. I rebuilt only the views I wanted, in cases where the original view works fine with the screen reader it was kept as it is;
  3. The accessibility mode can be turned on and off at any time and the user has immediate response to such an action;

I undestand that this solution works with any kind of application modes, not only accessibility. Any time someone needs to select a certain view instead of the other based on an application or session parameter, it will work. For instance, a multiculture application where the culture customization goes futher than color and language, requiring a complete redesign of views can take advantage of this model.

The downside of all this is the fact that, when the accessibility mode is on and there is an accessible version of a certain view, JSF will work twice, one time to build the original view and a second time to build the accessible version.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=457314&siteId=1
jsf