1

Here is my scenario:

I have my-jsf-app.war (JSF 1.2 application) and daos-and-entities.har (Hibernate 3.3.1) deployed to a JBOSSAS 5 server. What I want is to have some sort of change log in my application, that contains the information "who made the change". So my idea was to have hibernate event listeners that access the FacesContext (or actually the HttpSession inside the context) to retrieve the currently logged in user.

So basically I configured event listeners and added the following method to retrieve the currently logged in user.

protected static String tryToFindUserLogin() {

    try {
        Class classFacesContext = Class.forName("javax.faces.context.FacesContext");
        Class classExternalContext = Class.forName("javax.faces.context.ExternalContext");
        Method methodGetCurrentInstance =  classFacesContext.getMethod("getCurrentInstance", (Class[])null);
        Method methodGetExternalContext =  classFacesContext.getMethod("getExternalContext", (Class[])null);
        Method methodGetRemoteUser =  classExternalContext.getMethod("getRemoteUser", (Class[])null);

        // This call always returns null
        Object currentFacesContext = methodGetCurrentInstance.invoke(null, (Object[])null);

        Object currentExternalContext = methodGetExternalContext.invoke(currentFacesContext, (Object[])null);
        String login = (String)  methodGetRemoteUser.invoke(currentExternalContext, (Object[])null);
        logger.debug("Found Weblogin: " + login);
        return login;

    } catch (Exception e) {
        logger.debug(e, e);
    }

    return "anonymous";
    }

I use java reflection to do this, because the HAR file is also used in other non-jsf-projects.

The problem is that I never get a currentFacesContext. I understand that FacesContext.getCurrentInstance() is ThreadLocal. But if the hibernate action (e.g. saveOrUpdate()) is directly triggered from within a JSF ManagedBean actionmethod, shouldn't the event listener be executed by the same thread as the actionmethod?

Any help is appriciated, maybe there ist also a totally different approach to my scenario, that I'm not aware of. I obviously don't want to add a userContext paramter to all my dao calls though, as this would be a huge amount of work.

Thx in advance

huo73
  • 599
  • 6
  • 17
  • I've seen something similar work with EclipseLink and JSF so it seems like it should be possible. Try single-stepping with a debugger - set a breakpoint on the event listener and see what the stack trace looks like. That would at least confirm your assumption about scoping (which seems reasonable), that the event listener will be running inside the scope of a JSF request such that the FacesContext is bound. Other things to try: use regular method calls without reflection; get rid of "static"; or put the listener in the webapp itself. Maybe some subtle classloader hierarchy issue. – wrschneider Nov 13 '11 at 02:36
  • Thanks for the hint to classloader issue, which was lead me to a solution. I'll post an answer. – huo73 Nov 15 '11 at 09:59

1 Answers1

1

After getting a valueable hint, I found a solution. The WAR has the JSF Implementation JARs inside its WEB-INF\lib. As WAR and HAR are separate deployments, they use separate class loaders, which results in the WAR loading JSF Implementation from its WEB-INF\lib, while the HAR loads it from the JBOSS installation.

I was able to force the use of the correct classloader by changing the above code in the following way:

protected static String tryToFindUserLogin() {

    try {
        /*** Obtain the classLoader from the calling Thread, instead of using the default ***/
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class classFacesContext = classLoader.loadClass("javax.faces.context.FacesContext");
        Class classExternalContext = classLoader.loadClass("javax.faces.context.ExternalContext");
        /**************/

        Method methodGetCurrentInstance =  classFacesContext.getMethod("getCurrentInstance", (Class[])null);
        Method methodGetExternalContext =  classFacesContext.getMethod("getExternalContext", (Class[])null);
        Method methodGetRemoteUser =  classExternalContext.getMethod("getRemoteUser", (Class[])null);

        Object currentFacesContext = methodGetCurrentInstance.invoke(null, (Object[])null);

        Object currentExternalContext = methodGetExternalContext.invoke(currentFacesContext, (Object[])null);
        String login = (String)  methodGetRemoteUser.invoke(currentExternalContext, (Object[])null);
        logger.debug("Found Weblogin: " + login);
        return login;

    } catch (Throwable e) {
        logger.debug(e, e);
    }

    return "anonymous";
    }

Additionally I changed the catch block to catch(Throwable), because if run in non JSF environment getMethod() might throw a RuntimeException, which ist not caught by catch(Exception)

This works perfectly!

huo73
  • 599
  • 6
  • 17
  • Thanks Huo73. One point to clarify the exception part: Catch clause of Exception DOES CATCH RuntimeException. Since it's subclass of Exception ... http://stackoverflow.com/questions/12951236/if-runtimeexception-is-thrown-can-it-be-caught-as-an-exception – devBinnooh Aug 14 '13 at 08:12