10 Replies Latest reply on Nov 18, 2012 11:11 AM by heyw

    Remote clustered EJB invocation via JNDI

    dweil

      In our projects we have to use the JNDI style invocations of EJBs as described in https://docs.jboss.org/author/display/AS71/Remote+EJB+invocations+via+JNDI+-+EJB+client+API+or+remote-naming+project.

       

      So we connect to the server and call EJBs like that:

          Properties prop = new Properties();
          prop.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
          prop.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
          prop.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_DISALLOWED_MECHANISMS", "JBOSS-LOCAL-USER");
          prop.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_POLICY_NOPLAINTEXT", false);
          prop.put("jboss.naming.client.ejb.context", true);
          prop.setProperty(Context.PROVIDER_URL, "remote://localhost:4447");
          prop.setProperty(Context.SECURITY_PRINCIPAL, "user");
          prop.setProperty(Context.SECURITY_CREDENTIALS, "pwd");
          InitialContext ctx = new InitialContext(prop);

          Counter counter = (Counter) ctx.lookup("/showServer/CounterBean!de.gedoplan.seminar.demo.ejb.Counter");
          counter.doSomething();

      It works without problems for nonclusterd EJBs.

       

      If I make the EJB clustered (i. e. use @Clustered or the equivalent jboss-ejb3.xml entry) the code above fails with

      Exception in thread "main" java.lang.IllegalStateException: No EJB receiver contexts available in cluster ejb
           at org.jboss.ejb.client.EJBClientContext.requireClusterEJBReceiverContext(EJBClientContext.java:751)
           at org.jboss.ejb.client.ReceiverInterceptor.handleInvocation(ReceiverInterceptor.java:87)
           at org.jboss.ejb.client.EJBClientInvocationContext.sendRequest(EJBClientInvocationContext.java:181)
           at org.jboss.ejb.client.EJBInvocationHandler.doInvoke(EJBInvocationHandler.java:136)
           at org.jboss.ejb.client.EJBInvocationHandler.doInvoke(EJBInvocationHandler.java:121)
           at org.jboss.ejb.client.EJBInvocationHandler.invoke(EJBInvocationHandler.java:104)
           at $Proxy0.getCounterAndServerDescription(Unknown Source)
           at de.gedoplan.seminar.demo.ejb.CounterClient.main(CounterClient.java:26)
      

       

      I tried to add the file jboss-ejb-client.properties to the classpath as shown in https://docs.jboss.org/author/display/AS71/Clustered+EJBs - same result.

       

      The JBoss version used is 7.1.3.Final.

       

      So: what properties exactly have to be specified for constructing InitialContext and what exactly has to be in jboss-ejb-client.properties to enable clustered EJB calls when using the JNDI client API approach?

       

      Thank you

      Dirk

        • 1. Re: Remote clustered EJB invocation via JNDI
          jaikiran

          remote-naming doesn't support EJB specific cluster configuration properties. You can instead use the jboss-ejb-client.properties with the appropriate properties including the clustering ones and then change the jboss.naming.client.ejb.context property to false while creating the JNDI context. That way, the EJB client context will be created using the jboss-ejb-client.properties in the classpath.

          • 2. Re: Remote clustered EJB invocation via JNDI
            heyw

            The remote-naming project delegates the EJB invcation to the EJB client library, but without the various optimisations.

             

            Jaikiran, please correct me if I'm wrong.

             

             

            The configuration of the InitialContext should look like the following:

             

             

            Properties prop = new Properties();
            prop.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
            
            // default is false, if true the ejb receiver will be created directly during the creation of the InitialContext
            //prop.put("jboss.naming.client.ejb.context", false);
            prop.setProperty(Context.PROVIDER_URL, "remote://localhost:4447");
            prop.setProperty(Context.SECURITY_PRINCIPAL, "admin");
            prop.setProperty(Context.SECURITY_CREDENTIALS, "secret");
            
            Context context = new InitialContext(prop);
            

             

            In addition a ejb client configuration is required for the EJB invocation.

             

            A minimal example configuration (also for a clustered environment) of the jboss-ejb-client.properties:

             

            remote.connections=default
            remote.connection.default.host=localhost
            remote.connection.default.port=4447
            
            // enable support for failover and additional cluster configuration
            // name of the infinispan cache container
            remote.clusters=ejb
            

             

            The important part is to use the ejb namespace to lookup the proxy:

             

            context.lookup(“ejb:app-name/module-name/bean-name!bean-interface”);
            

             

            or for a SFSB:

             

            context.lookup(“ejb:app-name/module-name/bean-name!bean-interface?stateful”);
            

             

             

            More about  cluster support of the EJB client library, please refer to our blog:

            http://blog.akquinet.de/2012/11/09/load-balancing-and-failover-of-remote-ejb-clients-in-eap6-and-jboss-as7/

            • 3. Re: Remote clustered EJB invocation via JNDI
              wdfink

              Hi Dirk and Heinz,

               

              as Jaikiran said the remote naming did not support cluster and also not the full features of the ejb-client. Also it can not be used inside a server side application.

               

              You can find some examples in the following quickstarts:

              multi-server, a domain with a cluster and non-clustered servers

              https://github.com/wfink/jboss-as-quickstart/tree/ejb-multi-server

               

              and WIP:

              ejb-clients, different clients using properties, api, remote-naming and the new EJBCLIENT-34

              https://github.com/wfink/jboss-as-quickstart/tree/ejb-clients

               

              you can have a look, feedback is always welcome.

              • 4. Re: Remote clustered EJB invocation via JNDI
                heyw

                Hi Wolf-Dieter,

                 

                yes, I'm completely agreed. The remote-naming project does not support a cluster context. Thus, it is necessary to setup a context for the cluster as shown in the above example and disable the jboss.naming.client.ejb.context or remove the property because it is the default.

                 

                Regards, Heinz

                • 5. Re: Remote clustered EJB invocation via JNDI
                  jaikiran

                  Heinz Wilming wrote:

                   

                  The remote-naming project delegates the EJB invcation to the EJB client library, but without the various optimisations.

                   

                  Jaikiran, please correct me if I'm wrong.

                   

                   

                  The configuration of the InitialContext should look like the following:

                   

                   

                  Properties prop = new Properties();
                  prop.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
                   
                  // default is false, if true the ejb receiver will be created directly during the creation of the InitialContext
                  //prop.put("jboss.naming.client.ejb.context", false);
                  prop.setProperty(Context.PROVIDER_URL, "remote://localhost:4447");
                  prop.setProperty(Context.SECURITY_PRINCIPAL, "admin");
                  prop.setProperty(Context.SECURITY_CREDENTIALS, "secret");
                   
                  Context context = new InitialContext(prop);
                  

                   

                  In addition a ejb client configuration is required for the EJB invocation.

                   

                  A minimal example configuration (also for a clustered environment) of the jboss-ejb-client.properties:

                   

                  remote.connections=default
                  remote.connection.default.host=localhost
                  remote.connection.default.port=4447
                  
                  // enable support for failover and additional cluster configuration
                  // name of the infinispan cache container
                  remote.clusters=ejb
                  

                   

                  The important part is to use the ejb namespace to lookup the proxy:

                   

                  context.lookup(“ejb:app-name/module-name/bean-name!bean-interface”);
                  

                   

                  or for a SFSB:

                   

                  context.lookup(“ejb:app-name/module-name/bean-name!bean-interface?stateful”);
                  

                   

                   

                  More about  cluster support of the EJB client library, please refer to our blog:

                  http://blog.akquinet.de/2012/11/09/load-balancing-and-failover-of-remote-ejb-clients-in-eap6-and-jboss-as7/

                  What you mentioned is mostly correct.

                   

                  remote-naming project just looks up the relevant object and returns it to the client side. That includes EJB proxies too. You don't need the ejb: namespace when using remote-naming. It will just look for the jndi names in the java:jboss/exported namespace on server side and return the remote proxy. When the proxy is then used on the client side for invocation, the EJB client API comes into picture. It looks for a EJB client context to use.

                   

                  If the InitialContext for remote-naming was created using the jboss.naming.client.ejb.context=true then the connection used by remote-naming will be used to create a EJB client context and use it. However, in such cases you won't be able to control/set the EJB cluster related configurations on the client, since remote-naming has no knowledge about it. For such cases, you can set jboss.naming.client.ejb.context=false and then have a jboss-ejb-client.properties in your classpath will all the relevant connection configurations and cluster configurations, so that the EJB client context is created out of it.

                  • 6. Re: Remote clustered EJB invocation via JNDI
                    heyw

                    Hi Jaikiran,

                     

                    yes, the ejb namespace is not necessary. But in my scenario: a JBoss AS 7.1.3 cluster with two nodes and the client setup with the above configuration does not support load balancing for SFSBs without the use of the ejb namespace.

                     

                    In case of an SLSB the DeploymentNodeSelector choose an eligible node. In case of an SFSB the DeploymentNodeSelector will never asked for an eligible node.

                     

                     

                    Scenario without the ejb namespace:

                     

                    // lookup SFSB without ejb namespace 
                    // DeploymentNodeSelector is not called, even after the first contact.
                    SFSBProxy sfsbProxy = (SFSBProxy) context.lookup(“app-name/module-name/bean-name!bean-interface”);
                    
                    sfsbProxy.doSomething();  // invocation on node one
                    sfsbProxy.doSomething();  // invocation on node one
                    
                    // node one crashes and failover occurs
                    // ClusterNodeSelector choose a node of the ejb cluster for failover
                    // Failover works as expected
                    sfsbProxy.doSomething();  // invocation on node two
                    
                    sfsbProxy.destroy();
                    

                     

                     

                     

                    Lookup with the ejb namespace (after the first invocation and reception of the cluster topology):

                     

                    // lookup SFSB with ejb namespace 
                    // DeploymentNodeSelector is choosing an eligible node, e.g. node two
                    // on each new session the load will be distributed between the eligible nodes
                    SFSBProxy sfsbProxy = (SFSBProxy) context.lookup(“ejb:app-name/module-name/bean-name!bean-interface?stateful”);
                    
                    sfsbProxy.doSomething();  // invocation on node two
                    sfsbProxy.doSomething();  // invocation on node two
                    
                    // node one crashes and failover occurs
                    sfsbProxy.doSomething();  // invocation on node one
                    
                    sfsbProxy.destroy();
                    

                     

                     

                    I tried this with the version 1.0.4.Final of the remote naming project and the version 1.0.11.Final of the ejb client project and also with the latest master branch from the github repositories of both projects.

                     

                    Is this an expected behavior?

                     

                    Regards, Heinz

                    • 7. Re: Remote clustered EJB invocation via JNDI
                      dweil

                      In the example above to server host and port are redundantly mentioned as InitialContext properties (prop.setProperty(Context.PROVIDER_URL, "remote://localhost:4447");) and jboss-ejb-client properties (remote.connection.default.host=localhost and remote.connection.default.port=4447).

                       

                      Is it possible to specify / overwrite jboss-ejb-client properties dynamically when constructing InitialContext?

                       

                      Regards, Dirk

                      • 8. Re: Remote clustered EJB invocation via JNDI
                        heyw

                        Dirk Weil schrieb:

                         

                        In the example above to server host and port are redundantly mentioned as InitialContext properties (prop.setProperty(Context.PROVIDER_URL, "remote://localhost:4447");) and jboss-ejb-client properties (remote.connection.default.host=localhost and remote.connection.default.port=4447).

                         

                        Is it possible to specify / overwrite jboss-ejb-client properties dynamically when constructing InitialContext?

                         

                        Regards, Dirk

                         

                        It is also possible to setup the client context programmatically and do something like this:


                        public class RemoteEJBClient {
                            private static final String DEFAULT_HOST = "localhost";
                            private static final String DEFAULT_REMOTING_PORT = "4447";
                            private static final String DEFAULT_USER = "admin";
                            private static final String DEFAULT_PASSWORD = "secret";
                            private static final String CONETXT_PROVIDER_URL = "remote://" + DEFAULT_HOST + ":" + DEFAULT_REMOTING_PORT;
                        
                           ...
                             private final Context context;
                        
                            public RemoteEJBClient()  throws NamingException {
                        
                                Properties prop = new Properties();
                                prop.put(Context.INITIAL_CONTEXT_FACTORY,
                                        "org.jboss.naming.remote.client.InitialContextFactory");
                        
                                prop.put("jboss.naming.client.ejb.context", false);
                                prop.setProperty(Context.PROVIDER_URL, CONETXT_PROVIDER_URL);
                                prop.setProperty(Context.SECURITY_PRINCIPAL, DEFAULT_USER);
                                prop.setProperty(Context.SECURITY_CREDENTIALS, DEFAULT_PASSWORD);
                        
                                context = new InitialContext(prop);
                        
                                Properties ejbProp = new Properties();
                        
                                ejbProp.put("remote.connections", "default");
                                ejbProp.put("remote.connection.default.host", DEFAULT_HOST);
                                ejbProp.put("remote.connection.default.port", DEFAULT_REMOTING_PORT);
                                ejbProp.put("remote.connection.default.username", DEFAULT_USER);
                                ejbProp.put("remote.connection.default.password", DEFAULT_PASSWORD);
                        
                                ejbProp.put("remote.clusters", "ejb");
                                ejbProp.put(
                                        "remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED",
                                        "false");
                        
                                final PropertiesBasedEJBClientConfiguration configuration = new PropertiesBasedEJBClientConfiguration(
                                        ejbProp);
                        
                                final ContextSelector<EJBClientContext> ejbClientContextSelector = new ConfigBasedEJBClientContextSelector(
                                        configuration);
                        
                                EJBClientContext.setSelector(ejbClientContextSelector);
                            }
                        
                            ...
                        }
                        

                         

                         

                        Note that are two different things and both are using the remoting connector!

                         

                        For more setup examples, see the Quickstarts, as mentioned here:

                         

                        Wolf-Dieter Fink schrieb:

                         

                        ...

                        You can find some examples in the following quickstarts:

                        multi-server, a domain with a cluster and non-clustered servers

                        https://github.com/wfink/jboss-as-quickstart/tree/ejb-multi-server

                         

                        and WIP:

                        ejb-clients, different clients using properties, api, remote-naming and the new EJBCLIENT-34

                        https://github.com/wfink/jboss-as-quickstart/tree/ejb-clients

                         

                        you can have a look, feedback is always welcome.

                        • 9. Re: Remote clustered EJB invocation via JNDI
                          jaikiran

                          Heinz Wilming wrote:

                           

                          Hi Jaikiran,

                           

                          yes, the ejb namespace is not necessary. But in my scenario: a JBoss AS 7.1.3 cluster with two nodes and the client setup with the above configuration does not support load balancing for SFSBs without the use of the ejb namespace.

                           

                          In case of an SLSB the DeploymentNodeSelector choose an eligible node. In case of an SFSB the DeploymentNodeSelector will never asked for an eligible node.

                           

                           

                          Scenario without the ejb namespace:

                           

                          // lookup SFSB without ejb namespace 
                          // DeploymentNodeSelector is not called, even after the first contact.
                          SFSBProxy sfsbProxy = (SFSBProxy) context.lookup(“app-name/module-name/bean-name!bean-interface”);
                           
                          sfsbProxy.doSomething();  // invocation on node one
                          sfsbProxy.doSomething();  // invocation on node one
                           
                          // node one crashes and failover occurs
                          // ClusterNodeSelector choose a node of the ejb cluster for failover
                          // Failover works as expected
                          sfsbProxy.doSomething();  // invocation on node two
                           
                          sfsbProxy.destroy();
                          

                           

                           

                           

                          That's a very important but a simple semantic to understand. There's a section in the https://docs.jboss.org/author/display/AS71/Remote+EJB+invocations+via+JNDI+-+EJB+client+API+or+remote-naming+project document where we explain how the stateful bean lookups work and how they relate to the session being created for the stateful bean.

                           

                          As explained in that document, remember that a lookup of a stateful bean by the client triggers a session creation on the server. Now when you are using the ejb: namespace (which effectively means direct EJB client API usage), with the ?stateful JNDI name, the EJB client API instantly knows that the client is looking for a stateful bean. The EJB client API then goes through the process of finding a target EJB receiver to handle that invocation. During this process, it uses the DeploymentNodeSelector if multiple EJB receivers can handle it.

                           

                          Now consider this with the remote-naming project. Like we have emphasised in that article, the remote-naming project has no knowledge about "what" is being looked up. So it literally takes the JNDI name and uses the PROVIDER_URL as the destination server on which to do the lookup. So as a result, when you are using the JNDI name of the stateful bean, you always end up on the server which the remote-naming project decides to use. Once the invocation reaches that server, the session is created for that SFSB on that server and the proxy for that bean returned back. As a result, the EJB client API has no chance to play a role in the initial node selection for the SFSB session creation. The EJB client API only comes into picture later after the proxy has been returned to the client and an invocation done on it. But since the SFSB proxy will be associated with a affinity which binds it to a *specific* server/cluster on which the session was created, further invocations on that proxy will *not* involve the use of DeploymentNodeSelector (since we already know which server/cluster to use).

                           

                          It's similar for Stateless beans but since the returned proxy doesn't have any affinity to a specific server (since no session is involved), further invocations on that returned proxy use the DeploymentNodeSelector to decide which server to use.

                          • 10. Re: Remote clustered EJB invocation via JNDI
                            heyw

                            Of course, now, the different behaviours are clear when the lookup is done through the remote-naming project.

                             

                            Thanks a lot!