3 Replies Latest reply on Jun 4, 2013 8:15 AM by sboscarine

    Is it possible to trick the EAP 6.0.1 classloader to load my commons logging class?

    sboscarine

      I want to overrride 1 class in commons-logging.  What's the easiest way to do so in EAP 6.0.1?

       

      In porting a large legacy application, we're having issues getting Commons Logging to return a log4j log factory...including dozens of e-mails with RedHat Support which went nowhere.  Long story short, we can't get our jar to behave the way it did in 4.x.  Rather than beating our heads against the desk trying to get apache's class to do what we want, is there any easy way just to write my own class and have my war load it? 

       

      I created my own org.apache.commons.logging.LogFactory in my war, but it never loaded.  Presumably this is a security feature. 

       

      What's the easiest way to trick the container to load this single class in my war?  Ideally, I'd rather not fork the entire project and just this one class, however, we're getting pretty desperate. 

       

      Any suggestions?

        • 1. Re: Is it possible to trick the EAP 6.0.1 classloader to load my commons logging class?
          jaikiran

          Steven Boscarine wrote:

           

           

           

          In porting a large legacy application, we're having issues getting Commons Logging to return a log4j log factory

          What's that real issue?

          • 2. Re: Is it possible to trick the EAP 6.0.1 classloader to load my commons logging class?
            jaikiran

            Okay, I found some context to your question. So you are trying to do:

             

            org.apache.commons.logging.LogFactory.getLog(getClass()) 
            

             

            and it's always returning an instance of org.apache.commons.logging.impl.SLF4JLocationAwareLog. Instead what you want is commons logging to use Log4j's LogFactory implementation.

             

            It should be doable once you understand how auto/implicit dependencies (added by the server) are setup on your deployment. The logging dependencies that are added to the deployment, by the server are these:

             

            private static ModuleIdentifier JBOSS_LOGGING = ModuleIdentifier.create("org.jboss.logging");
            ...
            private static ModuleIdentifier COMMONS_LOGGING = ModuleIdentifier.create("org.apache.commons.logging");
            private static ModuleIdentifier LOG4J = ModuleIdentifier.create("org.apache.log4j");
            private static ModuleIdentifier SLF4J = ModuleIdentifier.create("org.slf4j");
            private static ModuleIdentifier JUL_TO_SLF4J = ModuleIdentifier.create("org.jboss.logging.jul-to-slf4j-stub");
            

             

            (source https://github.com/wildfly/wildfly/blob/7.1.3.Final/server/src/main/java/org/jboss/as/server/deployment/module/ServerDependenciesProcessor.java#L45)

             

            So what you need to do is to exclude the relevant dependencies from that set using the jboss-deployment-structure.xml in your deployment. Something like:

             

            <jboss-deployment-structure>
                <deployment>
                    <!-- Exclude relevant logging dependencies -->
                    <exclusions>
                        <module name="org.slf4j" />
                        <module name="org.apache.commons.logging" />
                    </exclusions>
                </deployment>
            </jboss-deployment-structure>
            

             

            (If your top level deployment has nested sub-deployments then make sure you repeat those exlusions under a <sub-deployment> element too so that those are excluded for the sub deployments too).

             

            Notice that we have excluded the org.apache.commons.logging module from the deployment. Although it might seem weird to exclude it, since your application wants to use the commons logging API, it is important to exclude it in this specific case because, this is how the module.xml of org.apache.commons.logging module looks like:

             

            <module-alias xmlns="urn:jboss:module:1.1" name="org.apache.commons.logging" target-name="org.slf4j.jcl-over-slf4j"/>
            

             

            (source https://github.com/wildfly/wildfly/blob/7.1.3.Final/build/src/main/resources/modules/org/apache/commons/logging/main/module.xml)

             

            As you can see the org.apache.commons.logging module is acting as an alias for the org.slf4j.jcl-over-slf4j module. The org.slf4j.jcl-over-slf4j has it's own custom implementation of org.apache.commons.logging.LogFactory and as mentioned in its javadoc http://www.slf4j.org/apidocs/org/apache/commons/logging/LogFactory.html:

             

            Factory for creating Log instances, which always delegates to an instance of SLF4JLogFactory.

             

            So it's no wonder that the Log4j's LogFactory implementation isn't being used in your case.

             

            So getting back to the solution, the first part is to add that jboss-deployment-structure.xml with the relevant module exclusions *and* package the org.apache.commons.logging JAR (which you can download from the apcahe commons project page) within your application. That way, when you use the org.apache.commons.logging.LogFactory class, it's loaded from the JAR packaged in your application and not the SLF4J one which always returns the SLF4JLogFactory.

             

            I haven't tested this myself so not sure if you'll run into some other problems. But give it a try and see if it works.

            • 3. Re: Is it possible to trick the EAP 6.0.1 classloader to load my commons logging class?
              sboscarine

              I think my issue was that we are using an ear and I didn't define jboss-deployment-structure correctly.

               

              Late last night, I realized our issue was that jboss-deployment-structure.xml requires each war to exclude sl4j and commons logging as well. 

               

              <?xml version="1.0" encoding="UTF-8"?>
              <jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.2">
                <!-- This is how we expose global modules to our application. -->
                <!-- Remember: EAP6 uses a modular classloader, which means that server libs are isolated from your libs until you specify 
                  otherwise as we're doing below -->
                <deployment>
                  <exclude-subsystems>
                    <!-- We bundle CXF since it is a legacy applicaiton -->
                    <subsystem name="webservices" />    </exclude-subsystems>
                  <dependencies>
                    <!-- Expose JCA Adapter to get to oracle.jdbc.OracleConnection from java.sql.Connection -->
                    <module name="org.jboss.ironjacamar.jdbcadapters" export="true" />
                    <!-- Expose Global JDBC Driver we installed to get to oracle.jdbc.OracleConnection class itself -->
                    <module name="com.oracle.jdbc" export="true" />
                  </dependencies>
              
                  <!-- This appears to be completely ignored -->
                  <exclusions>
                    <module name="org.slf4j" />
                    <module name="org.slf4j.impl" />
                    <module name="org.slf4j.jcl-over-slf4j" />
                    <module name="org.apache.commons.logging" />
                  </exclusions>
              
                </deployment>
              
                <!--This is what I was missing -->
                <sub-deployment name="mywar1.war">
                  <exclusions>
                    <module name="org.slf4j" />
                    <module name="org.slf4j.impl" />
                    <module name="org.slf4j.jcl-over-slf4j" />
                    <module name="org.apache.commons.logging" />
                  </exclusions>
                </sub-deployment>
                <sub-deployment name="mywar2.war">
                  <exclusions>
                    <module name="org.slf4j" />
                    <module name="org.slf4j.impl" />
                    <module name="org.slf4j.jcl-over-slf4j" />
                    <module name="org.apache.commons.logging" />
                  </exclusions>
                </sub-deployment>
                <sub-deployment name="mywar3.war">
                  <exclusions>
                    <module name="org.slf4j" />
                    <module name="org.slf4j.impl" />
                    <module name="org.slf4j.jcl-over-slf4j" />
                    <module name="org.apache.commons.logging" />
                    <!-- <module name="org.apache.log4j" /> -->
                  </exclusions>
                </sub-deployment>
                   <!-- Repeat for all 10 of my wars -->
              </jboss-deployment-structure>
              


              I was excluded modules in the precise place where I was successfully including them, but that wasn't working.  I was also a bit surprised that commons logging is exported automatically as I imagine that breaks everyone's logging settings from previous versions.

               

              Once I excluded commons logging on each war, I was able to get my commons-logging.properties to work and therefore could use log4j instead of sl4j.

               

              Thank you!