8 Replies Latest reply on Apr 6, 2010 3:38 AM by Nikolay Elenkov

    Failed to inject entityManager into Quartz job listener

    Shane Sarver Newbie
      >>>Goal: Inject entityManager into Quartz job listener, so I can modify the RemoteJob entity when Quartz invokes the job listener,
      and persist the change.

      >>>Using:
        * jboss-5.1.0.GA  (jboss-ejb3-plugin-1.0.19-installer.jar)
        * Quartz quartz-1.7.3.jar

      >>>Flow:
      RemoteHome.scheduleJob() --> RemoteJobManager.scheduleJob(RemoteJob) --> RemoteJobListener.jobWasExecuted(...)
      ``
      >>>Detail:
      I want the job listener to reschedule the job and modify the RemoteJob entity.
      Unfortunately, the injected @In EntityManager entityManager is null in the listener.
      When I inject the log using @Logger, it is also null.
      Why is injection failing in the listener?

      Alternatively, I when I did a lookup on the entityManager by doing

            Context ctx = new InitialContext();
            em = (EntityManager) ctx.lookup(ENTITY_MANAGER);

      it was not null.  I think it failed when merge was called.
      The exception led me to believe that I had to wrap the call in
      a transaction.  That did not work either.
      I think I need to stay within the seam context to get it to work.
      How should I proceed to reach my goal?


      >>>Source code:

      RemoteJobHome.java
      ///////////////////////////////////////////////////////////////////////////////
      @Name("remoteJobHome")
      public class RemoteJobHome extends EntityHome<RemoteJob> {
        @Logger private Log log;
        @RequestParameter Long remoteJobId;
        @In RemoteJobManager manager;

        @In(create = true)
        HostHome hostHome;

        public void setRemoteJobId(Long id) {
          setId(id);
        }

        public Long getRemoteJobId() {
          return (Long) getId();
        }

        @Override
        protected RemoteJob createInstance() {
          RemoteJob remoteJob = new RemoteJob();
          return remoteJob;
        }

        public void load() {
          if (isIdDefined()) {
            wire();
          }
        }

        public void wire() {
          getInstance();
          Host host = hostHome.getDefinedInstance();
          if (host != null) {
            getInstance().setHost(host);
          }
        }

        public boolean isWired() {
          return true;
        }

        public RemoteJob getDefinedInstance() {
          return isIdDefined() ? getInstance() : null;
        }

        @Override
        public Object getId() {
          return remoteJobId;
        }

        // Schedule job
        public String scheduleJob() throws Exception {
          String result = persist();
          RemoteJob remoteJob = getInstance();
          manager.scheduleJob(remoteJob);
          return result;
        }
      }
      /////////////////////////////////////////////////////////////////

      File: RemoteJobManager.java
      /////////////////////////////////////////////////////////////////
      @Name("manager")
      @AutoCreate
      public class RemoteJobManager {
        @Logger Log log;
        @In EntityManager entityManager;

        public void scheduleJob(RemoteJob remoteJob) {
          try {
            InitialContext ctx = new InitialContext();
            // Lookup scheduler
            StdScheduler scheduler = (StdScheduler) ctx.lookup("Quartz");
            //  Create job
            JobDetail jobDetail = new JobDetail(
              remoteJob.getId().toString(), 
              Scheduler.DEFAULT_GROUP,
              NoOpJob.class
            );
            SimpleTrigger trigger = new SimpleTrigger(
              "trigger" + remoteJob.getId().toString(),
              Scheduler.DEFAULT_GROUP,
              remoteJob.getStart(),
              null,
              0,
              0);
            RemoteJobListener listener = new RemoteJobListener();
            // Set up the listener
            scheduler.addJobListener(listener);
            // Make sure the listener is associated with the job
            jobDetail.addJobListener(listener.getName());       
            // Schedule the job to run
            scheduler.scheduleJob(jobDetail, trigger);
            // Update entity
            remoteJob.setNext(remoteJob.getStart());
            entityManager.merge(remoteJob);
          }
          catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
      /////////////////////////////////////////////////////////////////////////////////////////

      File: RemoteJobListener.java
      /////////////////////////////////////////////////////////////////////
      public class RemoteJobListener implements JobListener {
        @In
        EntityManager entityManager;

        public void jobWasExecuted(JobExecutionContext jec, JobExecutionException jee) {
          try {
            JobDetail jobDetail = jec.getJobDetail();
            RemoteJob remoteJob = entityManager.find(RemoteJob.class, new Long(jobDetail.getName()));   
            // Compute delay (ms)
            Long delay = 10000;
            Date date = new Date(System.currentTimeMillis() + delay);
            // Only schedule a job if before end date.
            if (date.before(remoteJob.getStop())) {  
              // Update entity
              remoteJob.setNext(date);
              entityManager.merge(remoteJob);
              // Create trigger
              SimpleTrigger trigger = new SimpleTrigger(
                "trigger" + jobDetail.getName().toString(),
                Scheduler.DEFAULT_GROUP,
                date,
                null,
                0,
                0
              );
              // Schedule the job to run!
              try {
                jec.getScheduler().scheduleJob(jobDetail, trigger);
              }
              catch (SchedulerException e) {
                e.printStackTrace();
              }
            }
            else {
              // Deactivate job
              remoteJob.setActive(false);
              entityManager.merge(remoteJob);
            }
          }
          catch (Exception e) {
            // >>> NPE: true == (null == entityManager)
            log.error(e);
            e.printStackTrace();
          }
        }
      }
      ////////////////////////////////////////////////////////////////////
        • 1. Re: Failed to inject entityManager into Quartz job listener
          Gabor Jakab Newbie

          Is your RemoteJobListener a Seam Component? I cannot see the @Name annotation in this class. Seam Interceptors work only with registered Seam Components... you can also use jboss.org.seam.Component.getInstance... with either the name or the class...

          • 2. Re: Failed to inject entityManager into Quartz job listener
            Shane Sarver Newbie

            I have tried using


            import org.jboss.seam.annotations.Name;


            on all of the classes with no success.

            • 3. Re: Failed to inject entityManager into Quartz job listener
              Nikolay Elenkov Master

              Use async methods and the Quartz dispatcher, much easier. You will get access to most Seam features, including injection.

              • 4. Re: Failed to inject entityManager into Quartz job listener
                Shane Sarver Newbie

                I do not think I can use asyc, because I need to set @IntervalDuration to a different value to
                produce different scheduling dynamically.
                Async works great if the configuration parameters do not change between job runs.
                I need a little more flexibility.

                • 5. Re: Failed to inject entityManager into Quartz job listener
                  Shane Sarver Newbie

                  SOLVED:


                  Using @Name annotion registers the Seam component:


                  @Name(foo)
                  public class Cat { ... }


                  To inject foo, the handle needs to be the same name as is registered:


                  public class Bar {
                    @In
                    private Cat foo;
                  ...
                  }



                  • 6. Re: Failed to inject entityManager into Quartz job listener
                    Nikolay Elenkov Master

                    Sure you can, you search for your trigger and change its parameters.

                    • 7. Re: Failed to inject entityManager into Quartz job listener
                      Shane Sarver Newbie

                      It does not look like the Quartz API likes to be accessed from the QuartzTriggerHandle.
                      Please suggest course of action based on your feedback.  Thanks.



                      Caused by: java.lang.IllegalAccessError: tried to access method org.jboss.seam.async.QuartzTriggerHandle.getScheduler()Lorg/quartz/Scheduler; from class org.jboss.seam.example.quartz.PaymentProcessor




                      Full stack trace:




                      08:05:32,713 INFO  [JobStoreCMT] Handling 1 trigger(s) that missed their scheduled fire-time.
                      08:06:00,106 ERROR [AsynchronousExceptionHandler] Exeception thrown whilst executing asynchronous call
                      java.lang.RuntimeException: exception invoking: schedulePayment
                           at org.jboss.seam.util.Reflections.invokeAndWrap(Reflections.java:154)
                           at org.jboss.seam.async.AsynchronousInvocation$1.process(AsynchronousInvocation.java:62)
                           at org.jboss.seam.async.Asynchronous$ContextualAsynchronousRequest.run(Asynchronous.java:80)
                           at org.jboss.seam.async.AsynchronousInvocation.execute(AsynchronousInvocation.java:44)
                           at org.jboss.seam.async.QuartzDispatcher$QuartzJob.execute(QuartzDispatcher.java:243)
                           at org.quartz.core.JobRunShell.run(JobRunShell.java:199)
                           at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:546)
                      Caused by: java.lang.reflect.InvocationTargetException
                           at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                           at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
                           at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                           at java.lang.reflect.Method.invoke(Method.java:597)
                           at org.jboss.seam.util.Reflections.invoke(Reflections.java:22)
                           at org.jboss.seam.intercept.RootInvocationContext.proceed(RootInvocationContext.java:32)
                           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:56)
                           at org.jboss.seam.transaction.RollbackInterceptor.aroundInvoke(RollbackInterceptor.java:28)
                           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
                           at org.jboss.seam.core.BijectionInterceptor.aroundInvoke(BijectionInterceptor.java:77)
                           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
                           at org.jboss.seam.transaction.TransactionInterceptor$1.work(TransactionInterceptor.java:97)
                           at org.jboss.seam.util.Work.workInTransaction(Work.java:47)
                           at org.jboss.seam.transaction.TransactionInterceptor.aroundInvoke(TransactionInterceptor.java:91)
                           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
                           at org.jboss.seam.core.MethodContextInterceptor.aroundInvoke(MethodContextInterceptor.java:44)
                           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
                           at org.jboss.seam.async.AsynchronousInterceptor.aroundInvoke(AsynchronousInterceptor.java:52)
                           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
                           at org.jboss.seam.intercept.RootInterceptor.invoke(RootInterceptor.java:107)
                           at org.jboss.seam.intercept.JavaBeanInterceptor.interceptInvocation(JavaBeanInterceptor.java:185)
                           at org.jboss.seam.intercept.JavaBeanInterceptor.invoke(JavaBeanInterceptor.java:103)
                           at org.jboss.seam.example.quartz.PaymentProcessor_$$_javassist_seam_6.schedulePayment(PaymentProcessor_$$_javassist_seam_6.java)
                           at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                           at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
                           at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                           at java.lang.reflect.Method.invoke(Method.java:597)
                           at org.jboss.seam.util.Reflections.invoke(Reflections.java:22)
                           at org.jboss.seam.util.Reflections.invokeAndWrap(Reflections.java:144)
                           ... 6 more
                      Caused by: java.lang.IllegalAccessError: tried to access method org.jboss.seam.async.QuartzTriggerHandle.getScheduler()Lorg/quartz/Scheduler; from class org.jboss.seam.example.quartz.PaymentProcessor
                           at org.jboss.seam.example.quartz.PaymentProcessor.schedulePayment(PaymentProcessor.java:71)
                           ... 35 more
                      08:07:00,054 ERROR [AsynchronousExceptionHandler] Exeception thrown whilst executing asynchronous call
                      java.lang.RuntimeException: exception invoking: schedulePayment
                           at org.jboss.seam.util.Reflections.invokeAndWrap(Reflections.java:154)
                           at org.jboss.seam.async.AsynchronousInvocation$1.process(AsynchronousInvocation.java:62)
                           at org.jboss.seam.async.Asynchronous$ContextualAsynchronousRequest.run(Asynchronous.java:80)
                           at org.jboss.seam.async.AsynchronousInvocation.execute(AsynchronousInvocation.java:44)
                           at org.jboss.seam.async.QuartzDispatcher$QuartzJob.execute(QuartzDispatcher.java:243)
                           at org.quartz.core.JobRunShell.run(JobRunShell.java:199)
                           at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:546)
                      Caused by: java.lang.reflect.InvocationTargetException
                           at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                           at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
                           at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                           at java.lang.reflect.Method.invoke(Method.java:597)
                           at org.jboss.seam.util.Reflections.invoke(Reflections.java:22)
                           at org.jboss.seam.intercept.RootInvocationContext.proceed(RootInvocationContext.java:32)
                           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:56)
                           at org.jboss.seam.transaction.RollbackInterceptor.aroundInvoke(RollbackInterceptor.java:28)
                           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
                           at org.jboss.seam.core.BijectionInterceptor.aroundInvoke(BijectionInterceptor.java:77)
                           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
                           at org.jboss.seam.transaction.TransactionInterceptor$1.work(TransactionInterceptor.java:97)
                           at org.jboss.seam.util.Work.workInTransaction(Work.java:47)
                           at org.jboss.seam.transaction.TransactionInterceptor.aroundInvoke(TransactionInterceptor.java:91)
                           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
                           at org.jboss.seam.core.MethodContextInterceptor.aroundInvoke(MethodContextInterceptor.java:44)
                           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
                           at org.jboss.seam.async.AsynchronousInterceptor.aroundInvoke(AsynchronousInterceptor.java:52)
                           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
                           at org.jboss.seam.intercept.RootInterceptor.invoke(RootInterceptor.java:107)
                           at org.jboss.seam.intercept.JavaBeanInterceptor.interceptInvocation(JavaBeanInterceptor.java:185)
                           at org.jboss.seam.intercept.JavaBeanInterceptor.invoke(JavaBeanInterceptor.java:103)
                           at org.jboss.seam.example.quartz.PaymentProcessor_$$_javassist_seam_6.schedulePayment(PaymentProcessor_$$_javassist_seam_6.java)
                           at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                           at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
                           at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                           at java.lang.reflect.Method.invoke(Method.java:597)
                           at org.jboss.seam.util.Reflections.invoke(Reflections.java:22)
                           at org.jboss.seam.util.Reflections.invokeAndWrap(Reflections.java:144)
                           ... 6 more
                      



                      Modified Source code from Seam quartz example:


                      import java.math.BigDecimal;
                      import java.util.Date;
                      import javax.persistence.EntityManager;
                      import org.jboss.seam.annotations.AutoCreate;
                      import org.jboss.seam.annotations.In;
                      import org.jboss.seam.annotations.Logger;
                      import org.jboss.seam.annotations.Name;
                      import org.jboss.seam.annotations.Transactional;
                      import org.jboss.seam.annotations.async.Asynchronous;
                      import org.jboss.seam.annotations.async.Expiration;
                      import org.jboss.seam.annotations.async.FinalExpiration;
                      import org.jboss.seam.annotations.async.IntervalCron;
                      import org.jboss.seam.annotations.async.IntervalDuration;
                      import org.jboss.seam.async.QuartzTriggerHandle;
                      import org.jboss.seam.log.Log;
                      import java.util.List;
                      
                      import org.jboss.seam.async.QuartzDispatcher;
                      import org.jboss.seam.example.quartz.Payment;
                      import org.jboss.seam.ScopeType;
                      import org.jboss.seam.annotations.Scope;
                      
                      import java.util.Date;
                      import org.quartz.Job;
                      import org.quartz.JobDataMap;
                      import org.quartz.JobDetail;
                      import org.quartz.JobExecutionContext;
                      import org.quartz.JobExecutionException;
                      import org.quartz.Scheduler;
                      import org.quartz.SchedulerException;
                      import org.quartz.SimpleTrigger;
                      import org.quartz.Trigger;
                      import org.quartz.impl.StdSchedulerFactory;
                      
                      
                      @Name("processor")
                      @AutoCreate
                      public class PaymentProcessor {
                          @Logger Log log;
                          @In EntityManager entityManager;
                          @In QuartzTriggerHandle timer;
                      
                          @Asynchronous
                          @Transactional
                          public QuartzTriggerHandle schedulePayment(
                                                       @Expiration Date when, 
                                                       @IntervalDuration Long interval, 
                                                       @FinalExpiration Date stoptime, 
                                                       Long paymentId) throws Exception {
                              Scheduler scheduler = timer.getScheduler();          
                              log.info("AFTER: shed: #0", scheduler);
                              log.info("***TIMER: timer: #0", timer);
                              SimpleTrigger t = (SimpleTrigger) timer.getTrigger();
                              log.info("***TIMER: trigger.getJobName(): #0", t.getJobName());
                              log.info("***OLD TIMER: trigger: #0, getNextFireTime(): #1", t, t.getNextFireTime());
                      
                              SimpleTrigger trigger = new SimpleTrigger(t.getName(), t.getGroup(), t.getStartTime(), t.getEndTime(), SimpleTrigger.REPEAT_INDEFINITELY, t.getRepeatInterval());
                              trigger.setJobName(t.getJobName());
                              trigger.setJobGroup(t.getJobGroup());
                              //Date d = new Date(t.getNextFireTime().getTime() + (60 * 1000));
                              Date now = new Date ();
                              now.setTime(now.getTime() + (60 * 1000));
                              log.info("***now: #0", now);
                      
                              trigger.setNextFireTime(now);
                              //Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
                              scheduler.rescheduleJob(t.getJobName(), t.getJobGroup(), trigger);
                              log.info("***NEW TIMER: trigger: #0, getNextFireTime(): #1", trigger, trigger.getNextFireTime());
                      
                              // Get payment
                              Payment p = entityManager.find(Payment.class, paymentId);      
                              log.info(p);
                              log.info("[#0] Processing payment #1", System.currentTimeMillis(), p.getId());
                              if (p.getActive()) {
                                  // Sub from source account
                                  BigDecimal value = p.getAmount();
                                  BigDecimal sub = value.negate();
                                  BigDecimal balance = p.getAccount().adjustBalance(sub);
                                  p.setLastPaid(new Date());
                                  log.info("src acct #0 balance is now #1", p.getAccount().getAccountNumber(), balance);
                      
                                  // Cancel if it is only supposed to run once. 
                                  if (p.getPaymentFrequency().equals(Payment.Frequency.ONCE)) {
                                      p.setActive(false);
                                  }
                                  // ssarver: Do last:
                                  entityManager.merge(p);
                      
                                  // Add to payee account
                                  // Where payee == account.getAccountNumber()
                                  List list = entityManager.createQuery("FROM Account a WHERE a.accountNumber = :an").setParameter("an", p.getPayee()).getResultList();
                                  if (null == list || 0 >= list.size()) {
                                    log.error("No dest account number matches given payee #0", p.getPayee());
                                  }
                                  else {
                                    log.info("Found dist acct");
                                    Account a = (Account) list.get(0);
                                    //Account account = entityManager.find(Account.class, paymentId);
                                    BigDecimal dstBalance = a.adjustBalance(value);
                                    log.info("dst acct #0 balance is now #1", a.getAccountNumber(), dstBalance);
                                    entityManager.merge(a);
                                  }
                              }
                              return null;
                          }
                      ...
                      }
                      




                      • 8. Re: Failed to inject entityManager into Quartz job listener
                        Nikolay Elenkov Master

                        Caused by: java.lang.IllegalAccessError: tried to access method org.jboss.seam.async.QuartzTriggerHandle.getScheduler()Lorg/quartz/Scheduler; from class org.jboss.seam.example.quartz.PaymentProcessor




                        This looks like a classloading problem. Make sure you have only one copy of the quartz jar.