3 Replies Latest reply on Jan 14, 2008 3:54 PM by mjhammel

    Referencing EJBs from Scheduler w/JBOSS 4.2.2GA

    mjhammel

      I've searched (and read) all messages related to "schedulable" in the forums but I still don't quite understand how I acquire and use an EJB from within a schedulable.

      What I have that works:

      A web service session bean that can retrieve an EJB using EntityManager.
      A schedulable class included in the EAR file that does get launched at deploy time.

      My EJB classes are generated by Hibernate using reverse engineering from an existing DB. My schedulable looks like this (sans any EJB references):

      import java.util.Date;
      import org.jboss.varia.scheduler.Schedulable;
      import org.apache.log4j.Logger;
      
      public class CronSchedulable implements Schedulable
      {
       private static final Logger log = Logger.getLogger(CronSchedulable.class);
       private String name;
       private long value;
      
       public CronSchedulable(String name, long value)
       {
       this.name = name;
       this.value = value;
       log.info("ctor, name: " + name + ", value: " + value);
       }
      
       public void perform(Date now, long remainingRepetitions)
       {
       log.info("perform, now: " + now +
       ", remainingRepetitions: " + remainingRepetitions +
       ", name: " + name + ", value: " + value);
      
       }
      
      }


      This is packaged into a sar:

      mjhammel(tty1)$ jar tvf crunch-schedulers.sar
       0 Fri Jan 11 11:08:06 MST 2008 META-INF/
       106 Fri Jan 11 11:08:04 MST 2008 META-INF/MANIFEST.MF
       869 Fri Jan 11 10:54:48 MST 2008 META-INF/jboss-service.xml
       0 Fri Jan 11 11:08:04 MST 2008 com/
       0 Fri Jan 11 11:08:04 MST 2008 com/cei/
       0 Fri Jan 11 11:08:04 MST 2008 com/cei/crunch/
       0 Fri Jan 11 11:08:04 MST 2008 com/cei/crunch/schedulers/
       1359 Fri Jan 11 11:08:04 MST 2008 com/cei/crunch/schedulers/CronSchedulable.class


      and the jboss-service.xml for this sar looks like this:

      <?xml version="1.0" encoding="UTF-8"?>
      
      <!-- Example Scheduler MBean for .Crunch -->
      <server>
       <mbean code="org.jboss.varia.scheduler.Scheduler" name="com.cei.crunch.schedulers:service=Scheduler">
       <depends>jboss.jca:service=DataSourceBinding,name=CrunchDS</depends>
       <depends>jboss.j2ee:module=crunch-ejb.jar,service=EJB3</depends>
       <attribute name="StartAtStartup">true</attribute>
       <attribute name="SchedulableClass">com.cei.crunch.schedulers.CronSchedulable</attribute>
       <attribute name="SchedulableArguments">TheName,123456789</attribute>
       <attribute name="SchedulableArgumentTypes">java.lang.String,long</attribute>
      
       <attribute name="InitialStartDate">NOW</attribute>
       <attribute name="SchedulePeriod">60000</attribute>
       <attribute name="InitialRepetitions">-1</attribute>
       </mbean>
      </server>


      That's the correct datasource JNDI name but I'm not sure I've specified the EJB3 depends correctly. The sar file is then packaged into the EJB:

      mjhammel(tty1)$ jar tvf Crunch.ear
       0 Fri Jan 11 11:08:06 MST 2008 META-INF/
       106 Fri Jan 11 11:08:04 MST 2008 META-INF/MANIFEST.MF
       100 Fri Jan 11 11:08:06 MST 2008 META-INF/jboss-app.xml
       6165 Fri Jan 11 11:08:04 MST 2008 crunch-web.war
       11740 Fri Jan 11 11:07:58 MST 2008 crunch-ejb.jar
       2119 Fri Jan 11 11:08:04 MST 2008 crunch-schedulers.sar
       918 Fri Jan 11 11:08:06 MST 2008 META-INF/application.xml



      At deploy time, I get the simple scheduler message:

      11:08:08,310 INFO [WSDLFilePublisher] WSDL published to: file:/home/mjhammel/src/cei/jboss-4.2.2.GA-cei/server/default/data/wsdl/Crunch.ear/crunch-web.war/SubscriberServicesService59269.wsdl
      11:08:08,319 INFO [EARDeployer] Started J2EE application: file:/home/mjhammel/src/cei/jboss-4.2.2.GA-cei/server/default/deploy/Crunch.ear
      11:08:08,878 INFO [CronSchedulable] perform, now: Fri Jan 11 11:08:08 MST 2008, remainingRepetitions: -1, name: TheName, value: 123456789
      

      My Web Service looks like this:

      package com.cei.crunch.server.ws.SubscriberServices;
      
      import javax.ejb.*;
      import javax.jws.WebService;
      import javax.jws.WebMethod;
      import javax.jws.soap.SOAPBinding;
      import javax.persistence.*;
      import javax.naming.InitialContext;
      import javax.transaction.UserTransaction;
      import javax.annotation.Resource;
      
      import com.cei.crunch.ejb.Subscriber;
      
      /* Make this an EJB3 service endpoint. */
      @Stateless
      @Remote(SubscriberServices.class)
      
      /* Make this an Web Services endpoint. */
      @WebService(
       name = "SubscriberWS",
       endpointInterface = "com.cei.crunch.server.ws.SubscriberServices.SubscriberServicesEndpoint"
       )
      @SOAPBinding(style = SOAPBinding.Style.RPC)
      
      /**
       * The .Crunch interface to Subscriber Services
       */
      public class SubscriberServices implements SubscriberServicesEndpoint {
      
       private UserTransaction utx;
       private EntityManager em;
      
       @WebMethod
       public Subscriber findSubscriber(String guid)
       {
       Subscriber s = null;
      
       InitialContext ctx = null;
       EntityManager em = null;
       try {
       ctx = new InitialContext();
       em = (EntityManager) ctx.lookup("java:/CrunchPersistence");
       utx = (UserTransaction) ctx.lookup("java:/comp/UserTransaction");
       }
       catch (Exception e)
       {
       // throw new WebServiceException(ex);
       System.out.println("Subscriber.find(): context lookup failure.");
       e.printStackTrace();
       return null;
       }
      
       if ( em == null )
       {
       System.out.println("Subscriber.find(): em is null; can't find subscriber");
       }
       else
       {
       System.out.println("Subscriber.find(): em is not null; returning subscriber for guid = " + guid);
       try {
       utx.begin();
       s = em.find(Subscriber.class, guid);
       if ( s != null )
       {
       System.out.println("Subscriber name : " + s.getFirstname());
       System.out.println("Subscriber email: " + s.getEmailAddress());
       }
       utx.commit();
       }
       catch (Exception e)
       {
       System.out.println("Subscriber.find(): find problem.");
       e.printStackTrace();
       }
       }
       if ( s != null )
       return s;
       else
       return null;
       }
      }


      The Web Service works as shown with a remote client, but I've not been able to get injection to work for the context, entity manager or transaction. So these are all done manually within the Web Service session bean (as listed in the code here).

      As shown, everything works fine. I can query the Web Service from a remote client to get back my Subscriber object. How do I do the same thing from within the Schedulable class? I've found code snippets in the forums but they aren't clear to me how I should be approaching this. Does the schedulable do things just like the Web Service is doing it? Do I try to use injection in the schedulable? If so, what would the equivalent injection look like to perform the same thing the Web Service is doing? And how do I get a UserTransaction? When I tried to copy the code from the Web Services class to the Schedulable class and deployed it I got errors related to the UserTransaction lookup.

      In summary: how do I access an EJB from within my schedulable using JBOSS 4.2.2GA and EJB3.0?

      All pointers are appreciated. Thanks.

        • 1. Re: Referencing EJBs from Scheduler w/JBOSS 4.2.2GA
          mjhammel

          One other thing: eventually the schedulable will need to call back into the web service interface. How do I do that? Do I write, configure and compile the schedulable the same way I do my remote client? Even if the schedulable is running within the same application EAR (on a single JBOSS instance, not in a cluster) as the web service interface?

          • 2. Re: Referencing EJBs from Scheduler w/JBOSS 4.2.2GA
            mjhammel

            Okay, so it seems that injection doesn't work in anything other than session beans (that means not in Web Services or MBeans) according to this thread:
            http://www.jboss.com/index.html?module=bb&op=viewtopic&t=107353
            So at least I know I should stop looking for why injection doesn't work in 4.2.2GA.

            But if I try to use the same mechanism I used in the Web Service in my schedulable, I get errors because I can't get a UserTransaction. I *must* have a transaction to use the EntityManager. So how do I get a transaction? Or do I need to stop using the EntityManager with my schedulable? If so, how do I get to my EJBs and Entity Beans?

            • 3. Re: Referencing EJBs from Scheduler w/JBOSS 4.2.2GA
              mjhammel

              I still don't have injection working, but I've managed to get my Schedulable working using the same (slightly modified) technique that the Web Services class is using. In the Web Services class I retrieved a UserTransaction by doing a lookup of java:/comp/UserTransaction. This worked in the Web Services class but not in the the Schedulable. After much googling, I found that doing the lookup as simply UserTransaction (no java:/comp prefix) works in both places. So my Schedulable now looks like this:

              package com.cei.crunch.schedulers;
              
              import java.util.Date;
              import org.jboss.varia.scheduler.Schedulable;
              import org.apache.log4j.Logger;
              
              import javax.ejb.*;
              
              /* Crunch classes. */
              import com.cei.crunch.ejb.*;
              
              /**
               * A simple Schedulable example.
               * @author Michael J. Hammel
               * @version $Revision: 1.3 $
               */
              @Stateless
              public class CronSchedulable implements Schedulable
              {
               private static final Logger log = Logger.getLogger(CronSchedulable.class);
              
               private String name;
               private long value;
              
               public CronSchedulable(String name, long value)
               {
               this.name = name;
               this.value = value;
               log.info("ctor, name: " + name + ", value: " + value);
               }
              
               public void perform(Date now, long remainingRepetitions)
               {
               SubscriberBean sb = null;
               Subscriber s = null;
              
               log.info("perform, now: " + now +
               ", remainingRepetitions: " + remainingRepetitions +
               ", name: " + name + ", value: " + value);
              
               sb = new SubscriberBean();
               if ( sb == null )
               log.info("Failed to get SubscriberBean.");
               else
               {
               s = sb.findSubscriber("CRUNCH-DEFAULT-SUPERUSER");
               if ( s != null )
               {
               log.info("Subscriber info: \n" +
               "Username : " + s.getUsername() + "\n" +
               "First name: " + s.getFirstname() + "\n" +
               "Last name : " + s.getLastname() + "\n" +
               "Email : " + s.getEmailAddress()
               );
               }
               else
               log.info("Injection still not working - *sigh*.");
               }
               }
              
              }


              This, in turn, calls an EJB that looks like this:

              package com.cei.crunch.ejb;
              
              import javax.ejb.*;
              
              import com.cei.crunch.ejb.Subscriber;
              import com.cei.crunch.ejb.EJBUtil;
              import com.cei.crunch.ejb.EJBUtil.EJBInit;
              
              
              @Stateless
              public class SubscriberBean implements SubscriberRemote {
              
               /* Default Constructor */
               public SubscriberBean() {}
              
               public Subscriber findSubscriber(String guid)
               {
               Subscriber s = null;
               EJBInit ei = null;
               try {
               EJBUtil eu = new EJBUtil();
               ei = eu.ejbSetup();
               }
               catch (EJBException ee) {
               System.out.println("findSubscriber(): " + ee.getMessage());
               ee.printStackTrace();
               return null;
               }
              
               if ( ei.em == null )
               {
               System.out.println("SubscriberBean: em is null; can't find subscriber.");
               }
               else
               {
               try {
               ei.utx.begin();
               s = ei.em.find(Subscriber.class, guid);
               ei.utx.commit();
               }
               catch (Exception e) {
               System.out.println("SubscriberBean: exception in transaction: " + e.getMessage());
               e.printStackTrace();
               }
               }
               return s;
               }
              }


              whose interface looks like this:

              package com.cei.crunch.ejb;
              
              import javax.ejb.Remote;
              import com.cei.crunch.ejb.Subscriber;
              
              @Remote
              public interface SubscriberRemote {
               public Subscriber findSubscriber(String guid);
              }


              The EJB also calls an initialization class (that lives in the same package as the EJB) that gets the InitialContext, EntityManager and UserTransaction. This is where the switch from java:/comp/UserTransaction to UserTransction occurs, in the ctx.lookup() call.

              package com.cei.crunch.ejb;
              
              import javax.ejb.*;
              import javax.persistence.*;
              import javax.naming.InitialContext;
              import javax.transaction.UserTransaction;
              
              /**
               * .Crunch server side utilities
               */
              public class EJBUtil {
              
               public class EJBInit {
               public InitialContext ctx;
               public EntityManager em;
               public UserTransaction utx;
               }
              
               /**
               * Default constructor.
               */
               public EJBUtil() {}
              
               /**
               * Initialization for Web Services.
               */
               public EJBInit ejbSetup() throws EJBException
               {
               InitialContext ctx = null;
               EntityManager em = null;
               UserTransaction utx;
               try {
               ctx = new InitialContext();
               em = (EntityManager) ctx.lookup("java:/CrunchPersistence");
               utx = (UserTransaction) ctx.lookup("UserTransaction");
               }
               catch (Exception e)
               {
               EJBException ee = new EJBException(e);
               ee.setMessage("Context setup failed.");
               throw ee;
               }
               if ( em == null )
               {
               EJBException ee = new EJBException("Failed to initialize entity manager.");
               throw ee;
               }
               else
               {
               EJBInit ei = new EJBInit();
               ei.ctx = ctx;
               ei.em = em;
               ei.utx = utx;
               return (ei);
               }
               }
              
              }


              The EJBException class is a simple exception class I wrote and is included in the same com.cei.crunch.ejb package.

              While it doesn't use injection, it does appear to grab the correct row from the database, and that's about all I can ask for at the moment. Actually making changes to the database hasn't been tested. Hopefully, this helps someone else getting started with Schedulables and also can't get injection to work.