ops4j / org.ops4j.pax.web

OSGi R7 Http Service, Whiteboard and Web Applications (OSGi CMPN Release chapters 102, 140 and 128) implementation using Jetty 9, Tomcat 9 or Undertow 2.
https://ops4j1.jira.com/wiki/display/paxweb/Pax+Web
Other
144 stars 183 forks source link

JasperClassLoader causes JSF FactoryFinder to fail during request dispatch [PAXWEB-98] #410

Closed ops4j-issues closed 13 years ago

ops4j-issues commented 16 years ago

Thomas Joseph created PAXWEB-98

I am now trying to build JSF application with Pax Web + Extender and Whiteboard. I am using Spring DM to register listeners, servlets and jsps with the Pax Web Whiteboard.

I faced some issue that I observed:
1. JSF during initialisation stores FactoryClassNames in a HashMap that is stored against classloaders (Classloader instance is used as key).
2. At all instances (initialisation and during later usage) an instance of "BundleClassLoader" is used as the ClassLoader, except for a place where there is a EL expression
3. At EL expression evaluation it dispatches to JspServletWrapper that uses the JaperClassLoader.
4. The JSF's hashmap is now queried for FactoryNames against the JasperClassLoader (that returns a different hash) and hence a null for the FactoryClassNames, which causes a failure in JSF page rendering whenever it carries a reference to a ManagedBean.


Affects: 0.5.1 Fixed in: 1.0.0 Votes: 3, Watches: 1


Referenced issues

is related from:

ops4j-issues commented 16 years ago

Thomas Joseph commented

Alin, Tested the JSF application on fix in Revision No. 12253. But it doesn't work.

ops4j-issues commented 16 years ago

Alin Dreghiciu commented

Same problem? It will be much useful if I can get an example.
Till then can you point me to the source code of the class that you think is generating the exception (the part where the factory class names are stored against class loaders)

ops4j-issues commented 16 years ago

Thomas Joseph commented

Yes, its the same problem again.

I am preparing the samples out from my junk working space,.. enough to be working independently.

You may look at the FactoryFinder code here:
http://svn.apache.org/viewvc/myfaces/core/tags/1_2_3/api/src/main/java/javax/faces/FactoryFinder.java?view=markup

or here:
http://www.krugle.org/kse/files/svn/svn.apache.org/myfaces/current/core/api/src/main/java/javax/faces/FactoryFinder.java

The Factory is initialised (setFactory) during web app startup by a ServletContextListener (http://www.krugle.org/kse/files/svn/svn.apache.org/myfaces/current/core/impl/src/main/java/org/apache/myfaces/webapp/StartupServletContextListener.java)
and used (getFactory) for every request (and all its phases).

ops4j-issues commented 16 years ago

Alin Dreghiciu commented

Well, looking at what I committed it cannot work :smile: Sorry for asking your to try it out.
I will try another approach and let you know.

ops4j-issues commented 14 years ago

PeterP commented

We are seeing this issue as well trying to deploy our jsf based webclient on pax-web.

MyFaces tries to register som factories in the map:

MyFaces' javax.faces.FactoryFinder

private static Map _registeredFactoryNames = new HashMap();

registeredFactoryNames is a map from classloader to the factories.

When faces is initialzed the classloader for the bundle is used,

BundleClassLoader{bundle=stepweb [23],parent=null}

when the jsp is rendered the jasper classloader is used.

JasperClassLoader{bundleClassLoader=BundleClassLoader{bundle=stepweb [23],parent=8.0}}

Apparently faces expects the same classloader to be used during initialization and subsequent invocations, and Pax-web does not agree.

MyFaces' javax.faces.FactoryFinder

factoryClassNames = (Map) _registeredFactoryNames.get(classLoader);

if (factoryClassNames == null)
{
  String message = "No Factories configured for this Application. This happens if the faces-initialization "+...

Is there a workaround for this issue?

ops4j-issues commented 14 years ago

Thomas Joseph commented

Yes there is a workaround, that we fixed in our company, but the fix was not very neat and the code is also deeply buried inside our codebase, that I cannot share. However, looking at the votes made for this issue, I will try to see if I can make a neat fix available for this.

ops4j-issues commented 14 years ago

Thomas Joseph commented

I have committed a fix. Please let me know if it works. I cannot right at the moment try out this fix.

ops4j-issues commented 14 years ago

PeterP commented

Took a little doing to get pax checked out and maven to work and to switch our pax-web usage to 0.7.2-SNAPSHOT from 0.6.0 - this caused a few problems in itself.

I think it does get a little further, but still it fails in the same way - It might also be that 0.7.2 behaves a little different than 0.6 when invoking jsp's.

I put a break point in the myfaces FactoryFinder and activated it just before calling the login.jsp and it still seems that the JasperClassloader is passed as context classloader to the FactoryFinder. The stacktrace to the FactoryFinder is included below.

I get the jsp error page with the following stack-traces:

javax.faces.FacesException: An exception occurred processing JSP page /login/login.jsp at line 36

33:     <script language="JavaScript" src='<c:url value="/js/progress.js" />'></script>
34: </head>
35: <body class="standard">
36: <f:view>
37:     <table width="100%" height="100%" cellpadding="0">
38:         <tr>
39:             <td width="100%" height="75px">

Stacktrace:
    at org.apache.myfaces.context.servlet.ServletExternalContextImpl.dispatch(ServletExternalContextImpl.java:432)
    at org.apache.myfaces.application.jsp.JspViewHandlerImpl.renderView(JspViewHandlerImpl.java:255)
    at de.mindmatters.faces.lifecycle.RenderResponsePhase.executePhase(RenderResponsePhase.java:45)
    at de.mindmatters.faces.lifecycle.AbstractPhase.execute(AbstractPhase.java:37)
    at de.mindmatters.faces.lifecycle.LifecycleImpl.executePhase(LifecycleImpl.java:166)
    at de.mindmatters.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:226)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:147)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1162)
    at com.stibo.stepweb.settings.impl.SettingsHttpSessionIntegrationFilter.doFilterInternal(SettingsHttpSessionIntegrationFilter.java:39)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:75)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1153)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:96)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:75)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1153)
    at org.ops4j.pax.web.service.internal.WelcomeFilesFilter.doFilter(WelcomeFilesFilter.java:169)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1153)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
    at org.ops4j.pax.web.service.jetty.internal.HttpServiceServletHandler.handle(HttpServiceServletHandler.java:64)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
    at org.ops4j.pax.web.service.jetty.internal.HttpServiceContext.handle(HttpServiceContext.java:111)
    at org.ops4j.pax.web.service.jetty.internal.JettyServerHandlerCollection.handle(JettyServerHandlerCollection.java:64)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.Server.handle(Server.java:326)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:536)
    at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:913)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:539)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:405)
    at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409)
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
Caused by: org.apache.jasper.JasperException: An exception occurred processing JSP page /login/login.jsp at line 36

33:     <script language="JavaScript" src='<c:url value="/js/progress.js" />'></script>
34: </head>
35: <body class="standard">
36: <f:view>
37:     <table width="100%" height="100%" cellpadding="0">
38:         <tr>
39:             <td width="100%" height="75px">

Stacktrace:
    at org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:505)
    at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:410)
    at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:342)
    at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:267)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
    at org.ops4j.pax.web.jsp.JspServletWrapper$2.call(JspServletWrapper.java:132)
    at org.ops4j.pax.web.jsp.JspServletWrapper$2.call(JspServletWrapper.java:127)
    at org.ops4j.pax.swissbox.core.ContextClassLoaderUtils.doWithClassLoader(ContextClassLoaderUtils.java:60)
    at org.ops4j.pax.web.jsp.JspServletWrapper.service(JspServletWrapper.java:124)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:390)
    at org.ops4j.pax.web.service.jetty.internal.HttpServiceServletHandler.handle(HttpServiceServletHandler.java:64)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
    at org.ops4j.pax.web.service.jetty.internal.HttpServiceContext.handle(HttpServiceContext.java:111)
    at org.mortbay.jetty.servlet.Dispatcher.forward(Dispatcher.java:327)
    at org.mortbay.jetty.servlet.Dispatcher.forward(Dispatcher.java:126)
    at org.apache.myfaces.context.servlet.ServletExternalContextImpl.dispatch(ServletExternalContextImpl.java:426)
    ... 31 more
Caused by: java.lang.IllegalStateException: No Factories configured for this Application. This happens if the faces-initialization does not work at all - make sure that you properly include all configuration settings necessary for a basic faces application and that all the necessary libs are included. <br/>
 Make sure that the JSF-API (myfaces-api-xxx.jar) is not included twice on the classpath - if not, the factories might be configured in the wrong class instance. <br/>
If you use Tomcat, it might also pay off to clear your /[tomcat-installation]/work/ directory - Tomcat sometimes stumbles over its own tld-cache stored there, and doesn't load the StartupServletContextListener. <br/>
Also check the logging output of your web application and your container for any exceptions! 
<br/>If you did that and find nothing, the mistake might be due to the fact that you use one of the very few web-containers which do not support registering context-listeners via TLD files and a context listener is not setup in your web.xml. <br/>
Add the following lines to your web.xml file to work around this issue : <br/>
<listener>
<br/>  <listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
<br/></listener>
    at javax.faces.FactoryFinder.getFactory(FactoryFinder.java:110)
    at javax.faces.webapp.UIComponentTag.setupResponseWriter(UIComponentTag.java:1116)
    at javax.faces.webapp.UIComponentTag.doStartTag(UIComponentTag.java:461)
    at org.apache.myfaces.taglib.core.ViewTag.doStartTag(ViewTag.java:108)
    at org.apache.jsp.login.login_jsp._jspService(login_jsp.java:153)
    at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
    at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:374)
    ... 47 more
ops4j-issues commented 14 years ago

Thomas Joseph commented

Probably you don't have to build it yourself :smile: the snapshot repository hosts the artifacts that are built for every commit. http://repository.ops4j.org/mvn-snapshots/org/ops4j/pax/web/

If you feel that its a problem due to a migration from 0.6.0 to 0.7.2-SNAPSHOT, consider migrating to 0.7.1 first and compare the result with the snapshot version.

ops4j-issues commented 14 years ago

PeterP commented

I noticed the snapshot dir - but felt that it was easier to get the code and compile it myself - that way I have access to the code if I need to provide more information.

Seeing that it fails with the exact same problem I don't think it is related to the snapshot. I also tried our other (non-jsf) web based component (our admin interface) and that seems to function as intended my compiled 0.7.2-SNAPSHOT.

ops4j-issues commented 14 years ago

PeterP commented

I took a look at the fix you committed and I see how it was intended to fix the problem.
I have tried it, and unfortunately it does not fix the issue.

Implementing the hashCode() can be made to function, I changed it to:

    @Override
    public int hashCode()
    {
        /*
         * Fix for PAXWEB-98 - JasperClassLoader causes JSF FactoryFinder to fail during request dispatch.
         *
         * Determine hashcode based on the Bundle/BundleClassLoader
         */
        System.out.println("Using m_bundleClassloader.hashCode()");
        final Bundle bundle = m_bundleClassLoader.getBundle();
        return ( bundle != null ? bundle.hashCode() * 37 : m_bundleClassLoader.hashCode() );
    }

It might be wrong but then the hashCode lookup in the map does indeed find the appropriate bucket to perform the search in.
Once this bucket has been found it then proceeds to do equals comparisons, using the equals method you implemented, this fails because of the if statement

// I believe the commit version was something like:
if (!(o instanceof BundleClassLoader)) {
            // fallback to default (should not happen)
            return super.equals(o);
        }

But the instanceof BundleClassLoader fails since this is performed in a different class space so the BundleClassLoader refers to a different BundleClassLoader and subsequently super.equals(o) is invoked which reports not equal.

Invoking the appropriate methods in the debugger gives:

getClassLoader()
     (org.ops4j.pax.web.jsp.internal.JasperClassLoader) JasperClassLoader{bundleClassLoader=BundleClassLoader{bundle=com.stibo.stepweb [25],parent=13.0}}

o.getClass().getClassLoader()
     (org.apache.felix.framework.searchpolicy.ModuleImpl$ModuleClassLoader) 15.0

BundleClassLoader.class.getClassLoader()
     (org.apache.felix.framework.searchpolicy.ModuleImpl$ModuleClassLoader) 13.0

o.getClass().getName()
     (java.lang.String) org.ops4j.pax.swissbox.core.BundleClassLoader

BundleClassLoader.class.getName()
     (java.lang.String) org.ops4j.pax.swissbox.core.BundleClassLoader

o instanceof BundleClassLoader
     (boolean) false

So both o and BundleClassLoader are BundleClassLoaders but o is not instance of BundleClassLoader - or atleast not the same BundleClassLoader.

I hope this provides more information on how to resolve this issue.

ops4j-issues commented 14 years ago

Thomas Joseph commented

Peter, based on your feedback, I have committed another fix. Please let me know how it works.

ops4j-issues commented 14 years ago

PeterP commented

Still no go.
Basically o is a BundleClassLoader - loaded with bundle stepweb and parent "null" and this is a JasperClassLoader with an internal bundleClassLoader loaed with bundle stepweb and using a different parent class loader.

I put a breakpoint in the return false of the if statement below and inspected the different variables:

if( o == null || ! (o.getClass() == getClass() || o.getClass() == BundleClassLoader.class)) {
  return false; // HERE 
}

The variables inspected using eclipse debuggers display view on the appropriate thread:

o
     (org.ops4j.pax.swissbox.core.BundleClassLoader) BundleClassLoader{bundle=stepweb [25],parent=null}

this
     (org.ops4j.pax.web.jsp.internal.JasperClassLoader) JasperClassLoader{bundleClassLoader=BundleClassLoader{bundle=stepweb [25],parent=13.0}}

o.getClass().getClassLoader()
     (org.apache.felix.framework.searchpolicy.ModuleImpl$ModuleClassLoader) 15.0

BundleClassLoader.class.getClassLoader()
     (org.apache.felix.framework.searchpolicy.ModuleImpl$ModuleClassLoader) 13.0

o.getClass().getName()
     (java.lang.String) org.ops4j.pax.swissbox.core.BundleClassLoader

BundleClassLoader.class.getName()
     (java.lang.String) org.ops4j.pax.swissbox.core.BundleClassLoader

o instanceof BundleClassLoader
     (boolean) false

// The statement if( o == null || ! (o.getClass() == getClass() || o.getClass() == BundleClassLoader.class)) analysed

o == null
     (boolean) false

! (o.getClass() == getClass() || o.getClass() == BundleClassLoader.class)
     (boolean) true

// Because both negated statments are false => !false => true

o.getClass() == getClass()
     (boolean) false

o.getClass() == BundleClassLoader.class
     (boolean) false
ops4j-issues commented 14 years ago

PeterP commented

Looking at the way the bundles are constructed it seems that both pax-web-jsp and pax-web-runtime bundles contain
org/ops4j/pax/swissbox/** which means that the BundleClassLoader is loaded in two different bundles each with their own class space.

We tried the following hack which seems to work, well at least the error we get now seems at first glance to be unrelated to the pax web stuff.
Whether you want to include it in the code base is a completely different matter....

Notice that I updated both hashCode and equals.

    @Override
    public boolean equals( Object o )
    {
        /*
         * Fix for PAXWEB-98 - JasperClassLoader causes JSF FactoryFinder to fail during request dispatch.
         *
         * Will apply equals logic based on the internal BundleClassLoader. This will make the HashMap to work
         * in subsequent lookup invocations, which used BundleClassLoader instance as key during initialization.
         *
         */
        if( this == o ) {
            return true;
        }
        // the class may be either JasperClassLoader or BundleClassLoader
        if( o == null || ! (o.getClass() == getClass() || o.getClass().getName() == BundleClassLoader.class.getName())) {
            return false;
        }

        final Bundle thisBundle = m_bundleClassLoader.getBundle();
        Object thatObject = null;
        try {
            thatObject = o.getClass().getMethod("getBundle").invoke(o, null);
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        final Bundle thatBundle = (Bundle)thatObject;

        if(thisBundle != null) {
            return thisBundle.equals( thatBundle );
        }
        return thatBundle == null;
    }

    @Override
    public int hashCode()
    {
        /*
         * Fix for PAXWEB-98 - JasperClassLoader causes JSF FactoryFinder to fail during request dispatch.
         *
         * give out the same hashCode as the wrapped BundleClassLoader
         */
//        return m_bundleClassLoader.hashCode();
//      System.out.println("Using m_bundleClassloader.hashCode()");
        final Bundle bundle = m_bundleClassLoader.getBundle();
        return ( bundle != null ? bundle.hashCode() * 37 : m_bundleClassLoader.hashCode() );

    }
ops4j-issues commented 14 years ago

Thomas Joseph commented

Peter, I think I need to now run this to make a proper fix. Could you please provide the testcase or atleast the provisioning urls, so that I can reproduce it in my environment.

ops4j-issues commented 14 years ago

Thomas Joseph commented

There has been, no feedback or submission of testcase for this issue. Since, the earlier commits could not provide a proper fix to the problem, I plan to revert back the code and take it out from the coming release (0.8.0) and change the issue status back to "Open".

I will be reverting back the code in next two days. Any concerns regarding this can be commented on this issue.

ops4j-issues commented 14 years ago

Thomas Joseph commented

Reverted back the changes, since the commited changes could not provide a proper fix. Stopping progress with this issue as of now. Will start again on this when a testcase becomes available.

ops4j-issues commented 14 years ago

lu4242N commented

on this link:

https://issues.apache.org/jira/browse/MYFACES-2290

There is a file called myfaces-test-helloworld-osgi-pax-web.zip. With this one and some fixes on myfaces code and pax web ( PAXWEB-199 ), myfaces could run.

JSF uses Thread.currentThread().getContextClassLoader() to obtain the web context classloader. The problem is OSGi doesn't have this concept in its HttpService definition. But in theory this could be solved creating a custom classloader that wraps the context classloader but also use the bundle classloader of the web application, allowing the user to bypass the bundle restriction. Unfortunately I tried this solution but pax uses a classloader to make jsp works, so any call to getFactory() fails, because the context classloader was replaced ( PAXWEB-98 ). In theory, with this hack it is possible to run an application using facelets.

ops4j-issues commented 13 years ago

Guillaume Nodet commented

What's the status of this bug?

ops4j-issues commented 13 years ago

Achim Nierbeck commented

With revision https://github.com/ops4j/org.ops4j.pax.web/commit/cdad349373b802ccd0d07b5f3e5c37a6ed1d3103
Pax web also provides following packages needed for JSF

javax.servlet.jsp.jstl.core; version="[1.1.2, 2.0.0)",
javax.servlet.jsp.jstl.fmt; version="[1.1.2, 2.0.0)",
javax.servlet.jsp.jstl.sql; version="[1.1.2, 2.0.0)",
javax.servlet.jsp.jstl.tlv; version="[1.1.2, 2.0.0)",
org.apache.taglibs.standard.*; version="1.1.2"

This was tested with

myfaces-test-helloworld-osgi-pax-web.zip
from https://issues.apache.org/jira/browse/MYFACES-2290

myfaces in version 1.29 was used.