13 Replies Latest reply on Sep 3, 2002 12:34 PM by jwcarman

    ClassLoader Weirdness!

    jwcarman

      I think I've figured out my problem and a solution, I just want to run it by the experts to confirm. I have written a class called EnvironmentNamingContext that implements the Service Locator design pattern. That is in a jar file that holds all of my reusable components (let's call it components.jar). I placed components.jar in my deploy directory to make it visible. Now, I implement a couple of EJBs (call them Session and Entity) where the session bean declares an EJB reference to the entity bean. If I try to use the EnvironmentNamingContext to lookup the entity bean's home object, it's not found. But, if I instantiate my own InitialContext and look it up that way, it's there. Immediately this led me to believe it was some sort of classloader issue. So, from reading the documentation, it seems that the InitialContext that's instantiated by EnvironmentNamingContext is given a different java:comp context than that of the session bean (rightly so). So, my idea to fix it is to install everything in an EAR file and use an "ear-specific loader repository." However, before I go through all the trouble, will this fix my issue?

        • 1. Re: ClassLoader Weirdness!
          jwcarman

          Doesn't anyone ever reply to these things?

          • 2. Re: ClassLoader Weirdness!

            No, java:comp/env is per bean not per package.

            No, nobody ever replies :-)

            Regards,
            Adrian

            • 3. Re: ClassLoader Weirdness!
              jwcarman

              But, if I put a jar file that contains only classes (like log4j.jar or something like that). Then, it is handled by the UCL, correct? So, classes in that jar file will have access to a different java:comp context than classes in my EJB jar file. Well, whatever the problem, it works differently when I use another class, loaded from another jar file that's sitting in the deploy directory. So, if I try to do this...

              EnvironmentNamingContext.getInstance().getEjbLocalHome( "ejb/MyBean" );
              I get a name not bound exception. But, if I do this...

              new Context enc = ( Context )new InitialContext().lookup( "java:comp/env" );
              enc.lookup( "ejb/MyBean" );

              It all works fine. How do I make it so that I can use my EnvironmentNamingContext class? Basically, the class just hides all of the ugliness of instantiating a context and performing the lookup. So, I would like to be able to use it (since it is a J2EE design pattern).

              Thanks for the reply, by the way! :-)

              • 4. Re: ClassLoader Weirdness!
                jwcarman

                forget about that little "new" word in front of Context. Typo.

                • 5. Re: ClassLoader Weirdness!

                  I'm not sure what you are doing?

                  The pattern you mention is for client side, so
                  java:comp is invisible and you cannot use local
                  interfaces.

                  java:comp/env is per bean. It is classloader based,
                  but not the UnifiedClassLoader (UCL), it does have
                  the UCL as a parent.

                  The structure looks something like
                  UCL (At jar level)
                  +- java:comp classloader (bean1)
                  +- java:comp classloader (bean2)
                  +- java.comp classloader (bean3)

                  The java:comp classloader also called the
                  ENC (environment classloader)
                  is the thread context classloader during invocation of
                  the bean.

                  So bean1 cannot see java:comp/env/whatever
                  for bean2.

                  Regards,
                  Adrian

                  • 6. Re: ClassLoader Weirdness!
                    jwcarman

                    Okay, let's try to figure this out. I don't believe that they strictly mean client Java executable application when they refer to a "J2EE client." Any type of Java application can be a J2EE client (Applet, Webapp, Stand-alone application, another J2EE application). A J2EE client is any type of client that needs to use the J2EE application's services. Anyway, I have a class called EnvironmentNamingContext. It is in components.jar located in the deploy directory. It looks something like this...

                    public EnvironmentNamingContext
                    {
                    private static final EnvironmentNamingContext instance = new EnvironmentNamingContext();
                    private Context enc;

                    public static final getInstance()
                    {
                    return getInstance();
                    }

                    private Context getEnc() throws NamingException
                    {
                    if( enc == null )
                    {
                    enc = ( Context )new InitialContext().lookup( "java:comp/env" );
                    }
                    return enc;
                    }

                    public Object lookup( String name ) throws NamingException
                    {
                    return getEnc().lookup( name );
                    }

                    public EJBHome getEjbHome( String name, Class homeClass ) throws NamingException
                    {
                    return ( EJBHome )PortableRemoteObject.narrow( lookup( name ), homeClass );
                    }

                    public EJBLocalHome getEjbLocalHome( String name ) throws NamingException
                    {
                    return ( EJBLocalHome )lookup( name );
                    }

                    // More methods here for Queues, Topics, DataSources, environment entries, etc.
                    }

                    Let's not even worry about EJBs at this point. Let's just consider a simple environment entry. In a war file that's also located in the deploy directory, I have the following code (as a Cactus ServletTestCase)...

                    public void testEnvironmentNamingContext() throws Exception
                    {
                    getLogger().debug( EnvironmentNamingContext.getInstance().getString( "MyString" );
                    }

                    public void testDirectLookup() throws Exception
                    {
                    final Context enc = ( Context )new InitialContext().lookup( "java:comp/env" );
                    getLogger().debug( ( String )enc.lookup( "MyString" ) );
                    }

                    The first one will fail with a naming exception, but the second one will succeed. Now, when I rearranged things by placing components.jar inside an ear file with the war (and placing components.jar on the manifest classpath of the war) and putting a <loader-repository> tag inside my jboss-app.xml file inside the ear, all works fine. Both test cases pass. But, when I remove the <loader-repository> tag, it fails again. So, in the end, I got it working. I just wanted to know if this is the desired behavior of JBoss or if it's something weird going on?

                    • 7. Re: ClassLoader Weirdness!

                      You are very persistent :-)

                      Here is what is happening:

                      Step 1
                      app1 does getInstance();
                      This is the first use of your factory, so this constructs
                      the static instance object.
                      You then do getEnc() which does lookup("java:comp/env")
                      for app1 and this goes into the enc variable.

                      Step2
                      app2 does getInstance(), it already exists
                      it does getEnc() and gets app1's naming context
                      stored in the enc variable.
                      It is the wrong one!

                      By adding the <loader-repository> you create a
                      new classloader space. With commons.jar in that
                      classloader space you have a new version of your class,
                      step 2 now becomes.

                      app2 does getInstance(), it doesn't already exist
                      it is a different version of the class with a whole
                      new copy of the static variables.
                      it does getEnc(), enc isn't filled in yet so it
                      looks up app2's naming context
                      now it works.

                      Except if there was also an app3 in the same ear, it
                      would use app2's naming context.

                      Try adding the following lines to getEnc();
                      System.out.println("Got: " + enc);
                      System.out.println("Expected: " + new InitialContext().lookup("java:comp/env"));

                      You will see the difference between what is cached and
                      what should be retrieved.

                      Regards,
                      Adrian

                      • 8. Re: ClassLoader Weirdness!
                        jwcarman

                        Well, what I was hoping would happen is that everyone can use the same instance of the javax.naming.Context variable (called enc in my case). What I've seen in other application servers is that the implementation of the JNDI provider that's used to connect to the naming service has knowledge of the context (not naming, but "execution context" if you will). In Sun's RI, it works like that. For example...

                        <ejb-jar>
                        <enterprise-beans>

                        <ejb-name>A</ejb-name>
                        AHome
                        A
                        <ejb-class>ABean</ejb-class>
                        <session-type>Stateless</session-type>
                        <transaction-type>Container</transaction-type>
                        <ejb-ref>
                        <ejb-ref-name>ejb/Other</ejb-ref-name>
                        <ejb-ref-type>Session</ejb-ref-type>
                        BHome</local-home>
                        B
                        <ejb-link>B</ejb-link>
                        </ejb-ref>


                        <ejb-name>B</ejb-name>
                        BHome
                        B
                        <ejb-class>BBean</ejb-class>
                        <session-type>Stateless</session-type>
                        <transaction-type>Container</transaction-type>
                        <ejb-ref>
                        <ejb-ref-name>ejb/Other</ejb-ref-name>
                        <ejb-ref-type>Session</ejb-ref-type>
                        AHome</local-home>
                        A
                        <ejb-link>A</ejb-link>
                        </ejb-ref>

                        </enterprise-beans>
                        </ejb-jar>

                        Both beans in this case use the EnvironmentNamingContext class to lookup the references to each other using the name "ejb/Other". In Sun's RI, this works fine, and there is only one instance of javax.naming.Context shared among the two beans. So, somehow the JNDI provider's implementation is concious of which beans "execution context" is actually executing at the time the lookup is performed. In JBoss, from what you've said, if the A session bean does a lookup using the shared instance first (thus causing the creation of the instance) it will receive the BHome object. Then, when the B session bean does a lookup next, it will receive the BHome object also, since the javax.naming.Context object was created using the B session bean's java:comp naming context. I don't even think that changing the EnvironmentNamingContext class to instantiate a new InitialContext for each lookup will help.

                        What I think is happening in my case is that the naming context that is created when components.jar is just sitting by itself in the deploy directory is created using some sort of default view of the naming system, with no customizations at all, since regular jars (non ejb-jars, wars, or ears) don't really have the concept of an environment naming context. The static enc instance is obviously not being created using the WAR's environment naming context (as the WAR is the only application using EnvironmentNamingContext), since it cannot find the environment entries that I declared in the web.xml file. Correct me if I'm wrong, but I'm going to do some exploration now to confirm.

                        • 9. Re: ClassLoader Weirdness!

                          Print out the result of
                          Thread.currentThread().getContextClassLoader()
                          in getEnc().
                          This is what determines the naming context.

                          Are you saying that for the RI

                          Context ctx = new InitialContext().lookup("java:comp/env");
                          String s = ctx.lookup("MyString");

                          The second lookup, effectively repeats the first one.
                          Determining the execution context on every lookup
                          regardless of the context you start from?

                          Regards,
                          Adrian

                          • 10. Re: ClassLoader Weirdness!
                            jwcarman

                            What I'm saying is that in the RI, the same exact Context object will return different objects depending upon who (which J2EE client, whether that be an EBJ or a Java application) called it. So, the statically shared enc variable is created one time. However, when its lookup method is called somehow knows that it is being called from Session B, so it will return a reference to AHome. Check out the attached ear file (the source is in the ejb jar file).

                            • 11. Re: ClassLoader Weirdness!
                              jwcarman

                              Oh, you should have no problem just deploying this ear file in the Sun J2EE RI. Then, execute the application client to illustrate the EnvironmentNamingContext class working the way I had intended it.

                              • 12. Re: ClassLoader Weirdness!

                                Hi,

                                You seem to have found a difference in behaviour
                                between jboss and the RI.
                                The spec doesn't specify either mechanism.

                                Tomcat works the same way as JBoss and Tomcat IS the
                                reference implementation for a servlet container.

                                I would guess the J2ee RI is doing some thread local
                                processing on every lookup (inefficient).
                                Sorry, I'm not going to download, install, configure
                                the RI just to test this.

                                JBoss (and Tomcat) just does the thread local stuff when
                                it looks up "java:comp/env".
                                After that you've just got a normal naming context.
                                Storing the naming context for use outside the bean
                                that retrieved it is not defined by the spec.

                                If you don't like this behaviour, you can modify
                                org.jboss.naming. Post a patch that allows your
                                alternative behaviour to be configured in
                                jboss.xml/standardjboss.xml

                                Regards,
                                Adrian

                                • 13. Re: ClassLoader Weirdness!
                                  jwcarman

                                  Maybe I should just change my implementation of EnvironmentNamingContext to not be a singleton, as an environment naming context is not global in intent. It is specific to a particular component within a J2EE application. Maybe every component should create their own EnvironmentNamingContext object to support their own lookups. Maybe that would be sufficient, hopefully. Thank you again for your help, Adrian. I guess it's back to the drawing board for me! :-)