8 Replies Latest reply on Jul 29, 2009 10:01 AM by jcstaff

    PostConstruct loadClass fails when porting to JBoss 5

    jcstaff

      I am in the process of trying out the new JBoss 5.0CR1 with Java 5 and am running into some classpath/loading issues. All applications built, deployed, and tested successfully with a legacy instance of JBoss 4.2.2.GA just prior to running these tests, so I am sure the overall structure of the EAR/EJB is correct. Something appears different with the class loadClass() in JBoss 5 versus earlier versions.

      My @PostConstruct has the following code

       @PostConstruct
       public void init() {
       log.warn("init(), daoClass=" + daoClassName);
       teller = new TellerImpl();
      
       try {
       //direct instantiation works
       AccountDAO dao = new xxx.jpa.JPAAccountDAO();
       log.warn("found:" + dao.getClass());
       //reflection approach no longer works
       dao = (AccountDAO)Thread.currentThread()
       .getContextClassLoader()
       .loadClass(daoClassName)
       .newInstance();
       ((TellerImpl)teller).setAcctDAO(dao);
       }
       catch (Exception ex) {
       log.fatal("error loading dao class:" + daoClassName, ex);
      ...
       }
       }
      


      As shown by the following debug output, the direct instantiation (added to help debug this) works, but the reflection no longer works when I deploy to JBoss 5.0CR1 versus 4.x

      WARN [TellerEJB] init(), xxx.jpa.JPAAccountDAO
      
      WARN [TellerEJB] found:class xxx.jpa.JPAAccountDAO
      FATAL [TellerEJB] error loading dao xxx.jpa.JPAAccountDAO
      java.lang.ClassNotFoundException: xxx.jpa.JPAAccountDAO
      


      thanks,
      jim

        • 1. Re: PostConstruct loadClass fails when porting to JBoss 5
          alesj

          Can you do some more debugging / logging?
          e.g. print out TCCL in 4.x vs. 5.x in that code

          It might just be a bug in not setting TCCL properly.
          Or what does the spec say about what should the TCCL be in this case?

          • 2. Re: PostConstruct loadClass fails when porting to JBoss 5
            alrubinger

             

            "alesj" wrote:
            Or what does the spec say about what should the TCCL be in this case?


            "EJB 3.0 Specification 21.1.2: Programming Restrictions" wrote:
            The enterprise bean must not attempt to create a class loader; obtain the current class loader; set the context class loader; set security manager; create a new security manager; stop the JVM; or change the input, output, and error streams.


            In short, this is non-portable because application-level code should leave the details of the environment to the container.

            Why are we taking the reflection-based approach? Let's take a look at the structure of the JAR/EAR as well?

            S,
            ALR

            • 3. Re: PostConstruct loadClass fails when porting to JBoss 5
              jcstaff

              Thank you for the reference to the specific spec requirement that explains why this is allowed to not to work. The legacy code was from a time (EJB 2.x) when dependency injection was more limited, and it may have advanced to the point that we can inject the POJO class dynamically in other ways. However...

              If obtaining the current classloader is illegal/non-portable, how does one read in a data file from the EJB's classpath? Is there a legal classloader we can access that will have the file Resource that can be read in as a stream?

              We can come up with ways to get around obtaining the classloader to dynamically load a class, but I don't currently know of a way to avoid getting the classloader for reading on streams/files.

              thanks,
              jim

              • 4. Re: PostConstruct loadClass fails when porting to JBoss 5
                alrubinger

                Hey Jim:

                "jcstaff2" wrote:
                If obtaining the current classloader is illegal/non-portable, how does one read in a data file from the EJB's classpath? Is there a legal classloader we can access that will have the file Resource that can be read in as a stream?


                In general, use of File (and other I/O) resources is frowned upon in EJB because again you've got the issue of application code directly accessing something by bypassing the abstraction provided by the container. This means that nothing is guarding it from concurrent access, providing rollback capabilities, etc.

                For *non-mutable* resources, like reading in properties, you can package these inside your EJB JAR and get at them via this.getClass().getResource() in your bean implementation class.

                S,
                ALR



                • 5. Re: PostConstruct loadClass fails when porting to JBoss 5
                  jcstaff

                  I understand where your caution comes from about the ClassLoader after reading the spec, but when I read the Javadoc on getClass().getResource(), it sounds like it too is locating the ClassLoader. When I did some searching, I found that Tom Marrs (Lead Author, JBoss at Work: A Practical Guide) had the same solution as I did as late as 2006. http://www.coderanch.com/t/89900/JBoss/reading-properties-file

                  I spent the last few days building up a simple set of test cases where I loaded classes and resources from different types of ClassLoaders both inside and outside the EJB. In my simple test case, I could not recreate the problem. I finally tracked the issue down to the deployment descriptor. The JBoss 5 parsing of the deployment descriptor elements seems to now include white-space characters into values.

                  My class looked like the following...

                  @Stateless
                  public class TellerEJB implements TellerLocal, TellerRemote {
                  ...
                   @Resource(name="daoClass")
                   protected String daoClassName;
                  
                   @PostConstruct
                   public void init() {
                   log.debug("init(), daoClass=" + daoClassName);
                   teller = new TellerImpl();
                  
                   try {
                   AccountDAO dao = (AccountDAO)Thread.currentThread()
                   .getContextClassLoader()
                   .loadClass(daoClassName)
                   .newInstance();
                  


                  My deployment descriptor was written as follows

                  ...
                   <enterprise-beans>
                   <session>
                   <ejb-name>TellerEJB</ejb-name>
                   <env-entry>
                   <env-entry-name>daoClass</env-entry-name>
                   <env-entry-type>java.lang.String</env-entry-type>
                   <env-entry-value>xxx.jpa.JPAAccountDAO
                   </env-entry-value>
                   </env-entry>
                   </session>
                   </enterprise-beans>
                  </ejb-jar>
                  


                  Note the line break after ...DAO and the line break in the original debug output. Once I removed the white-space from the deployment descriptor all worked.

                   <enterprise-beans>
                   <session>
                   <ejb-name>TellerEJB</ejb-name>
                   <env-entry>
                   <env-entry-name>daoClass</env-entry-name>
                   <env-entry-type>java.lang.String</env-entry-type>
                   <env-entry-value>xxx.jpa.JPAAccountDAO</env-entry-value>
                   </env-entry>
                   </session>
                   </enterprise-beans>
                  


                  I've seen this same type of problem elsewhere in the processing of deployment descriptors by JBoss 5. In one post I saw where adding export JAVA_OPTS="-Dxb.builder.useUnorderedSequence=true" to the startup corrected the issue. I have not yet tried that for this issue.

                  So, in short. I may be violating what you think is wrong with my use of Thread.currentThread().getContextClassLoader(). However the problem in porting from JBoss 4 to JBoss 5 ended up being a whitespace interpretation change of the ejb-jar.xml between the two versions. JBoss 5 is adding extra whitespace to the injected String variable.

                  Thoughts?

                  • 6. Re: PostConstruct loadClass fails when porting to JBoss 5
                    alrubinger

                     

                    "jcstaff2" wrote:
                    I understand where your caution comes from about the ClassLoader after reading the spec, but when I read the Javadoc on getClass().getResource(), it sounds like it too is locating the ClassLoader.


                    The difference is in your definition of "the".

                    http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html#getResource(java.lang.String)

                    Class.getResource() uses the ClassLoader which defined the Class in question. So it's:

                    Class.getClassLoader().getResource()


                    ...under the hood, and not:

                    Thread.currentThread().getContextClassLoader().getResource()


                    In theory the two could be the same. In fact, I'd say any access to any CL from application code is a no-no, as the application could change the internal mutable state of the CL itself which may lead to problems elsewhere. But the Class.getResource() case should be fine.

                    "jcstaff2" wrote:
                    I finally tracked the issue down to the deployment descriptor. The JBoss 5 parsing of the deployment descriptor elements seems to now include white-space characters into values.

                    (...snip)

                    However the problem in porting from JBoss 4 to JBoss 5 ended up being a whitespace interpretation change of the ejb-jar.xml between the two versions.


                    This is expected.

                    http://java.sun.com/xml/ns/javaee/javaee_5.xsd

                    javaee:env-entry-value is of type javaee:xsdStringType which extends from xsd:string. Pure String types are not trimmed.

                    http://www.w3schools.com/Schema/schema_dtypes_string.asp

                    "jcstaff2" wrote:
                    JBoss 5 is adding extra whitespace to the injected String variable.


                    Looks like *you* were adding the whitespace. :) We just became a bit more compliant/strict and stopped trimming it away.

                    S,
                    ALR

                    • 7. Re: PostConstruct loadClass fails when porting to JBoss 5
                      alrubinger

                       

                      "jcstaff2" wrote:
                      When I did some searching, I found that Tom Marrs (Lead Author, JBoss at Work: A Practical Guide) had the same solution as I did as late as 2006. http://www.coderanch.com/t/89900/JBoss/reading-properties-file


                      Respectfully disagree.

                      "Tom Marrs" wrote:
                      The EJB specification forbids EJBs to use the Current Class Loader, and since the System Class Loader isn't a workable option, you're left with the Thread Context ClassLoader.


                      It's this which I say is wrong.

                      "Sun EJB Restrictions Guide" wrote:
                      Read-only data can, however, be stored in files in a deployment JAR, and accessed with the getResource() or getResourceAsStream() methods of java.lang.Class.


                      http://java.sun.com/blueprints/qanda/ejb_tier/restrictions.html

                      S,
                      ALR



                      • 8. Re: PostConstruct loadClass fails when porting to JBoss 5
                        jcstaff

                        Andrew,

                        * Thanks for the specifics on why the handling of deployment descriptor whitespace changed. Yes, with a more-compliant application server schema, "I was adding the extra whitespace" - point taken.

                        * We are all set on the reading of a read-only resource.

                        * Tom Marrs wrote about 3 classloaders; system, current, and thread. He ruled out use of the first 2 for known reasons and then stated why you should use the later. This is what my code is doing; access the classloader through the current Thread.


                        You gain access to the current Thread Context ClassLoader by calling Thread.currentThread().getContextClassLoader(). ... Use the Thread Context Class Loader for the following reasons:


                        * On the use of the reflection API and ClassLoader, the listed restrictions state that what I wanted to do with the ClassLoader was legal

                        Contrary to common belief, most of the Java Reflection API can be used from EJB components. For example, loadClass() and invoke() can both be used by enterprise beans. Only certain reflection methods are forbidden.


                        Thanks for taking the time on this.

                        jim