6 Replies Latest reply on Jun 16, 2009 8:03 AM by swd847

    Remote EJB invocations and Seam's Session Context

      So I've been banging my head against my desk for the past few days on this.


      Quick explanation. I have a SLSB deployed to JBoss which looks like this:


      @Remote
      public interface ClientSession {
        
        void login(String username, String password) throws LoginException;
      
        String whoAmI();
      
      }
      
      @Name("clientSession")
      @Scope(SESSION)
      @Stateless
      public class ClientSessionBean implements ClientSession {
      
        @Log
        private Logger log;
      
        @In
        private Identity identity;
      
        public void login(String username, String password) throws LoginException {
          log.info("loggedIn? {0}", identity.isLoggedIn()); // prints 'loggedIn? false'
          Credentials credentials = identity.getCredentials();
          credentials.setUsername(username);
          credentials.setPassword(password);
          identity.login();
          log.info("loggedIn? {0}", identity.isLoggedIn()); // prints 'loggedIn? true'
        }
      
        public String whoAmI() {
          log.info("loggedIn? {0}", identity.isLoggedIn()); // prints 'loggedIn? false'
          return identity.getPrincipal().getName(); // throws a NPE
        }
      
      }
      



      Pretty straight-forward. In my plain java client, I simply get a remote instance using JNDI...


      public static void main(String[] args) throws Exception {
        Properties env = new Properties();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
        env.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
        env.put(Context.PROVIDER_URL, "jnp://localhost:1099");
      
        InitialContext ctx = new InitialContext(env);
        ClientSession session = (ClientSession) ctx.lookup("app-ear/ClientSessionBean/remote");
        session.login("foo", "bar"); // valid credentials as far as Seam Identity is concerned
        System.out.println(session.whoAmI());
      }
      



      The problem is across the 2 invocations of session.login(...) and session.whoAmI() the Seam Identity Component that's associated with the Session context is not the same instance.


      What I can make of this is Seam must not be associating the 2 invocations of the remote EJB within the same Session context.


      Please somebody help!


      Thanks,


      --mgbowman

        • 1. Re: Remote EJB invocations and Seam's Session Context
          pgmjsd

          The second example might be working by accident.   If Seam is not injecting 'identity' from the Session (how could it when there is no Session), then it might be working because you are just lucking out and getting the same instance of the SFSB every time.


          Is there some reason you are not using the security features of Seam (i.e. <security:identity authenticate-method="#{authenticator.authenticate}"
                                     security-rules="#{securityRules}"
                                        remember-me="true"/>
          in components.xml)?

          • 2. Re: Remote EJB invocations and Seam's Session Context

            Internally (from within the EJB) I am using the seam security features, however this problem goes much deeper than I originally thought.


            The issue stems from the fact that the container (in my case JBoss AS 5) is solely responsible for the remote EJB functionality - it has nothing to do with Seam. Internally, JBoss has some notion of a client session (I mean it isn't stateless/stateful session bean for nothing). The problem is in the SeamInterceptor - it doesn't integrate with the container's state management in order to be able to save/restore the Session context across multiple invocations of the remote EJB.


            Here's the relevant code from org.jboss.seam.intercept.RootInterceptor (the root parent class of org.jboss.seam.ejb.SeamInterceptor)


               protected Object invoke(InvocationContext invocation, EventType invocationType) throws Exception
               {
                  if ( !isSeamComponent || !Lifecycle.isApplicationInitialized())
                  {
                     //not a Seam component
                     return invocation.proceed();
                  }
                  else if ( Contexts.isEventContextActive() || Contexts.isApplicationContextActive() ) //not sure about the second bit (only needed at init time!)
                  {
            
                     // a Seam component, and Seam contexts exist
                     return createInvocationContext(invocation, invocationType).proceed();
                  }
                  else
                  {            
                     //if invoked outside of a set of Seam contexts,
                     //set up temporary Seam EVENT and APPLICATION
                     //contexts just for this call
                     
                     Lifecycle.beginCall();         
                     try
                     {
                        return createInvocationContext(invocation, invocationType).proceed();
                     }
                     finally
                     {
                        Lifecycle.endCall();
                     }
                  }
               }
            



            Notice the 3 logical cases in the above invoke method:



            1. Not a Seam component

            2. Seam component inside of a Seam context

            3. Seam component outside of a Seam context <-- this is the case for Remote EJB invocations



            Basically it's creating the Session for the lifetime of the invocation. After which, the context is destroyed and the next invocation will be in a context all it's own


            Personally I would love to Seam integrate this functionality, although I'm not quite sure how feasible since I believe it would be container specific. If I have the time later on down the road, I might revisit this topic but until then, I'm settling for a REST approach since the new Seam 2.1.2.GA has much improved support for RESTeasy.


            Hope this helps anyone else out there who has encountered this problem and doesn't understand. If anyone has any pointers about how this could be integrated, I would love to look into it / help with it.


            Thanks,


            --mgbowman

            • 3. Re: Remote EJB invocations and Seam's Session Context
              swd847

              You could probably accomplish this by adding your own interceptor to manage session state. Try something like this, and make sure it runs outside of all the other interceptors:


              
              public class SessionInterceptor{
              
                 MyMockHttpRequest session = new MyMockHttpRequest();
              
                 @AroundInvoke
                 public Object doStuff(InvocationContext invocation) throws Exception {
                 
                    ServletLifecycle.beginRequest(session);
                    Object result = invocation.proceed();
                    ServletLifecycle.endRequest(session);
                    return result;
                 }
              
              }
              
              
              



              You can use any implementation of a MockHttpRequest you like, as long as it maintains the session state. Because the interceptor is stateful it should maintain the session scope for the life of the bean. If you were to actually create a patch to add this sort of funcationally to seam I would add a method to LifeCycle that allows you to pass in a session map when starting up the contexts.


              If you want more info let me know. Depending on the lengths I go to to aviod studying tomorrow I may do up a patch.


              • 4. Re: Remote EJB invocations and Seam's Session Context
                swd847

                You probably also want to check to make sure that the application context is not active before doing the LifeCycle stuff, otherwise it will not work in normal seam calls.


                Also this will only maintain the session for a single bean, if you want to share the session it becomes considerably more complicated.

                • 5. Re: Remote EJB invocations and Seam's Session Context

                  In response to:



                  Also this will only maintain the session for a single bean, if you want to share the session it becomes considerably more complicated.

                  Wouldn't one expect remote EJB invocations (on seam-managed components) to react the exact same way as local EJB invocations from JSF? (i.e. share the session context)


                  For example:


                  @Name("foo")
                  @Scope(ScopeType.STATELESS)
                  @Stateless
                  @Local(FooLocal.class)
                  @Remote(Foo.class)
                  public class FooBean implements FooLocal, Foo {
                  
                    @In
                    private Identity identity;
                  
                    ...
                  
                  }
                  
                  @Name("bar")
                  @Scope(ScopeType.STATELESS)
                  @Stateless
                  @Local(BarLocal.class)
                  @Remote(Bar.class)
                  public class BarBean implements BarLocal, Bar {
                  
                    @In
                    private Identity identity;
                  
                    ...
                  
                  }
                  



                  If these two EJBs were accessed from JSF, then the Identity component which gets injected by Seam would reflect the same component. However with remote EJB invocations, they are not.


                  You're saying the solution of intercepting the invocation and managing the session state would fail in this example?


                  If so, how considerably more complicated would this be? Is there not enough information in the remote EJB invocations for this to be possible? Can we not somehow hook-into / intercept the lifecycle of a SLSB or SFSB in a container-independent fashion?


                  Any thoughts?


                  --mgbowman

                  • 6. Re: Remote EJB invocations and Seam's Session Context
                    swd847

                    The only workable solution that I can come up with is to use some kind of a session handle that gets passed around as a parameter in remote EJB calls.
                    As I see it you probably need to do something like this:



                    • Create an application scoped session store component that associates a session handle with a Map of some kind. This probably needs some kind of session timeout support to prevent memory leaks.

                    • Create a EJB with a remote interface to create and destory sessions.

                    • Apply an intercetor that looks for a SessionHandle parameter in the arguments of the method it is intercepting, if it finds one it gets the session from the session store and associates it with the current LifeCycle.



                    This would have the unfortunate side effect that you need to pass in a SessionHandle as a parameter to all you remote methods, but I can't really see any other way of doing things, especially not for stateless session beans which may need a different session for every call. For stateful beans you could make them remeber the session they are attached to, so you just have to call a method when they are created to initally attach the session.


                    Also you need to call ServletLifecycle.beginSession when the session is created to initilise any @statup session scoped components.


                    Please note that this is all just speculation, I have not actually tried to do any of this stuff.


                    Stuart