4 Replies Latest reply on Apr 6, 2005 3:53 PM by Andy Nguyen

    How to mark SLSB as unavailable?

    Andy Nguyen Newbie

      I have a SLSB that I am deploying to all nodes in a cluster. The load-balancing works fine. This EJB depends on a license that is only available on certain nodes in the cluster. Is there a way to mark this bean as unavailable on the nodes that don't have the required license? I have tried throwing a CreateException and an EJBException in the ejbCreate() method, but that ends up propogating to the remote caller. I'd like the client to failover to another node until it finds a node that has a valid license. I've also tried throwing an EJBException in the EJBs constructor, but that has the same behavior as throwing it in ejbCreate().

      According the EJB 2.1 specs, a RuntimeException (such as EJBException) thrown from any EJB method should cause the bean to enter state does not exist. Shouldn't the clustering automatically failover to another node if a bean does not exist on one node?

      Please help!

      Andy Nguyen

        • 1. Re: How to mark SLSB as unavailable?
          Andy Nguyen Newbie

          Forgot to mention, I'm using JBoss 3.2.6 on Linux JDK 1.4.2_07.

          Looking at JRMPInvokerProxyHA.invoke(), it looks like the invocation must throw one of the following exceptions in order for fail-over to happen: java.net.ConnectException, java.net.UnknownHostException, java.rmi.ConnectException, java.rmi.ConnectIOException, java.rmi.NoSuchObjectException, java.rmi.UnmarshalException, java.rmi.UnknownHostException or GenericClusteringException (with completionStatus == GenericClusteringException.COMPLETED_NO). It doesn't look my EJB can throw any of these exceptions directly, so my question then would be, what can I do in my EJB to make the container or one of the interceptors throw one of these exceptions? Do I need to write my own interceptor? Should I modify JRMPInvokerProxyHA.invoke() to catch EJBException and failover when this happens?

          Andy Nguyen

          • 2. Re: How to mark SLSB as unavailable?
            Andy Nguyen Newbie

            I guess I'm alone in this.

            I've done some more digging and found this post http://www.jboss.org/index.html?module=bb&op=viewtopic&t=61675 and this bug http://jira.jboss.com/jira/browse/JBAS-1600 which says it has been fixed in 4.0.2. I haven't checked to see what the behavior is now in 4.0.2, but I'm hoping that if a CreateException is thrown from ejbCreate, the behavior of the client proxy will be to fail-over.

            I will look into 4.0.2 and see what happens. Hopefully, I can back port this fix to 3.2.6.

            • 3. Re: How to mark SLSB as unavailable?
              Andy Nguyen Newbie

              Well, it looks like I found a solution to my problem.

              Problem: I have a stateless session EJB that throws a CreateException in ejbCreate(). In a clustered environment, I believe this should cause the client proxy to fail-over to another node. The current behavior in JBoss 3.2.6 is to throw the CreateException back to the client wrapped in an UndeclaredThrowableException. The client proxy just propogates the UndeclaredThrowableException all the way back up the call chain without failing over.

              Solution: I wrote my own interceptor that catches CreateExceptions and throws back a GenericClusteringException. The client proxy will fail-over when it receives a GenericClusteringException with status set to not completed.

              Here is the code to my interceptor, in case anyone is interested.

              package com.itgssi.util.jboss.ejb;
              
              import javax.ejb.CreateException;
              
              import org.jboss.ejb.plugins.AbstractInterceptor;
              import org.jboss.ha.framework.interfaces.GenericClusteringException;
              import org.jboss.invocation.Invocation;
              
              /**
               * <p>
               * <code>CreateExceptionHandlerInterceptor</code> handles CreateExceptions
               * during invocations to an EJB. In order for these exceptions to cause the
               * client to fail-over in a clustered environment, they get converted to
               * <code>GenericClusteringException</code>s.
               * </p>
               * <p>
               * <em>NOTE:</em> for this interceptor to work propertly, it must come BEFORE
               * <code>org.jboss.ejb.plugins.CleanShutdownInterceptor</code>.
               * <code>CleanShutdownInterceptor</code> will change the status on
               * <code>GenericClusteringException</code> s that pass through it to MAYBE,
               * which will not cause the client to fail-over. This is the behavior in JBoss
               * 3.2.6; I'm not sure about other versions.
               * </p>
               *
               * @author anguyen
               */
              public class CreateExceptionHandlerInterceptor extends AbstractInterceptor {
              
               /**
               * This invocation handles <code>CreateException</code>s thrown by
               * other invokers further down the chain. The current behavior is
               * to log the <code>CreateException</code>'s message as a warning
               * and throw a <code>GenericClusteringException</code> with a
               * completed status of NO. Clients of a clustered EJB should
               * interpret this exception as an indication that the EJB
               * is unavailable on this node and fail-over to another node.
               */
               public Object invoke(Invocation mi) throws Exception {
               try {
               return getNext().invoke(mi);
               }
               catch (CreateException e) {
               handleCreateException(e);
               // if handle create exception doesn't throw an exception....
               throw e;
               }
               catch (Exception e) {
               Throwable rootCause = e;
               while ((rootCause = rootCause.getCause()) != null) {
               // loop until we get to the root cause
               }
               if (rootCause instanceof CreateException) {
               handleCreateException((CreateException) rootCause);
               }
               // if the root cause is not a CreateException, rethrow
               // the original exception
               throw e;
               }
               }
              
               void handleCreateException(CreateException e) throws Exception {
               String message = "EJB creation failed on node";
               log.warn(message);
               log.debug("Stack Trace", e);
               throw new GenericClusteringException(GenericClusteringException.COMPLETED_NO, message);
               }
              }
              


              To use this interceptor, I created a custom container for my EJB in jboss.xml.
              <?xml version="1.0" encoding="UTF-8"?>
              <!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 3.0//EN" "http://www.jboss.org/j2ee/dtd/jboss_3_0.dtd">
              
              <jboss>
              
               <enterprise-beans>
              
               <!--
               To add beans that you have deployment descriptor info for, add
               a file to your XDoclet merge directory called jboss-beans.xml that contains
               the <session></session>, <entity></entity> and <message-driven></message-driven>
               markup for those beans.
               -->
              
               <session>
               <ejb-name>MySLSB</ejb-name>
               <jndi-name>ejb/MySLSB</jndi-name>
               <configuration-name>My Clustered Stateless SessionBean</configuration-name>
               <clustered>true</clustered>
               <cluster-config>
               <partition-name>DefaultPartition</partition-name>
               <home-load-balance-policy>org.jboss.ha.framework.interfaces.RoundRobin</home-load-balance-policy>
               <bean-load-balance-policy>org.jboss.ha.framework.interfaces.RoundRobin</bean-load-balance-policy>
               </cluster-config>
              
               </session>
              
               </enterprise-beans>
              
               <resource-managers>
               </resource-managers>
              
               <!--
               | for container settings, you can merge in jboss-container.xml
               | this can contain <invoker-proxy-bindings/> and <container-configurations/>
               -->
               <container-configurations>
               <container-configuration>
               <container-name>My Clustered Stateless SessionBean</container-name>
               <call-logging>false</call-logging>
               <invoker-proxy-binding-name>clustered-stateless-rmi-invoker</invoker-proxy-binding-name>
               <container-interceptors>
               <!-- This interceptor must come before CleanShutdownInterceptor to work properly -->
               <interceptor>com.itgssi.util.jboss.ejb.CreateExceptionHandlerInterceptor</interceptor>
               <interceptor>org.jboss.ejb.plugins.ProxyFactoryFinderInterceptor</interceptor>
               <interceptor>org.jboss.ejb.plugins.CleanShutdownInterceptor</interceptor>
               <interceptor>org.jboss.ejb.plugins.LogInterceptor</interceptor>
               <interceptor>org.jboss.ejb.plugins.SecurityInterceptor</interceptor>
               <!-- CMT -->
               <interceptor transaction="Container">org.jboss.ejb.plugins.TxInterceptorCMT</interceptor>
               <interceptor transaction="Container" metricsEnabled="true">org.jboss.ejb.plugins.MetricsInterceptor</interceptor>
               <interceptor transaction="Container">org.jboss.ejb.plugins.StatelessSessionInstanceInterceptor</interceptor>
               <!-- BMT -->
               <interceptor transaction="Bean">org.jboss.ejb.plugins.StatelessSessionInstanceInterceptor</interceptor>
               <interceptor transaction="Bean">org.jboss.ejb.plugins.TxInterceptorBMT</interceptor>
               <interceptor transaction="Bean" metricsEnabled="true">org.jboss.ejb.plugins.MetricsInterceptor</interceptor>
               <interceptor>org.jboss.resource.connectionmanager.CachedConnectionInterceptor</interceptor>
               </container-interceptors>
               <instance-pool>org.jboss.ejb.plugins.StatelessSessionInstancePool</instance-pool>
               <instance-cache></instance-cache>
               <persistence-manager></persistence-manager>
               <container-pool-conf>
               <MaximumSize>100</MaximumSize>
               </container-pool-conf>
               </container-configuration>
              
               </container-configurations>
              </jboss>
              

              I used the standard Clustered Stateless SessionBean container as a template. You could also modify the default Clustered Stateless SessionBean container and just add the interceptor there.

              Kudos to the JBoss developers for giving us the hooks necessary to come up with these kinds of solutions.


              • 4. Re: How to mark SLSB as unavailable?
                Andy Nguyen Newbie

                Oops, the catch block for Exception should look like this:

                 catch (Exception e) {
                 Throwable rootCause = e;
                 Throwable nextCause = null;
                 while ((nextCause = rootCause.getCause()) != null) {
                 rootCause = nextCause;
                 }
                 if (rootCause instanceof CreateException) {
                 handleCreateException((CreateException) rootCause);
                 }
                 // if the root cause is not a CreateException, rethrow
                 // the original exception
                 throw e;
                 }