9 Replies Latest reply on Aug 31, 2010 5:50 AM by cbensemann

    Asynchronous Seam Mail with JbpmContext and EntityManager

    luiggitama.luiggitama.gmail.com

      Hi all...


      I need to know if there is a recommended way to access the JbomContext and the EntityManager from a Seam Mail component, like the AsynchronousMailProcessor provided in the examples.


      I need to send an e-mail message when a task is created, as a reminder each certain time. For that, I'm calling a method of a Seam component from an event in the jBPM workflow (for testing the reminder occurs each minute):


           <event type="task-create">
                <action expression='#{siplacadMails.sendMailNewTask(taskInstance)}'></action>
                <create-timer duedate="1 minute" name="reminder" repeat="1 minute">
                     <action expression="#{siplacadMails.sendMailTaskReminder(taskInstance)}"></action>
                </create-timer>
           </event>
      
           <event type="task-end">
                <cancel-timer name="reminder"/>
           </event>




      From the component, I need to resolve the users' mail names and addresses. The user is resolved based on the actorId or the pooledActors of the TaskInstance. The problem is that I can't get all the TaskInstance information, I guess it has something to do with lazy loading.


      Here's my mailer component:


      package siplacad.session;
      
      import java.io.Serializable;
      import java.util.ArrayList;
      import java.util.Date;
      import java.util.List;
      import java.util.Set;
      
      import org.jboss.seam.Component;
      import org.jboss.seam.ScopeType;
      import org.jboss.seam.annotations.AutoCreate;
      import org.jboss.seam.annotations.In;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Scope;
      import org.jboss.seam.annotations.Transactional;
      import org.jboss.seam.bpm.ManagedJbpmContext;
      import org.jboss.seam.contexts.Contexts;
      import org.jboss.seam.faces.Renderer;
      import org.jbpm.JbpmContext;
      import org.jbpm.taskmgmt.exe.PooledActor;
      import org.jbpm.taskmgmt.exe.TaskInstance;
      import siplacad.model.Estado;
      import siplacad.model.entity.Rol;
      import siplacad.model.entity.RolUsuario;
      import siplacad.model.entity.Usuario;
      
      /**
       * Mails
       * 
       * @since 2010-08-23
       */
      @Name("siplacadMails")
      @Scope(ScopeType.BUSINESS_PROCESS)
      @AutoCreate
      public class SiplacadMails implements Serializable {
      
           /**
            * 
            */
           private static final long serialVersionUID = -8900657821310010411L;
      
          @In JbpmContext jbpmContext;
      //    @In SiplacadDataHelper siplacadDataHelper;
      //    @In EntityManager entityManager;
      //    private SiplacadDataHelper siplacadDataHelper = ((SiplacadDataHelper)Component.getInstance("siplacadDataHelper"));
          
      //    @Asynchronous
      //    @Observer("sendMailTask")
          @Transactional
           public void sendMailTaskObserver(TaskInstance task, String template, boolean isReminder) {
                try {
                     JbpmContext jbpmContext = ManagedJbpmContext.instance();
      //               JbpmContext jbpmContext = (JbpmContext) Component.getInstance("jbpmContext");
                     task = jbpmContext.getTaskInstance(task.getId());
                     System.out.println(task.getPooledActors()); // here the pooledActors should not be null but it is - lazy problem?
                     // these vars are correctly injected into the process context when starting the process
                     Integer reminderStart = (Integer) task.getVariable("reminderStart");
                     Integer reminderRepeat = (Integer) task.getVariable("reminderRepeat");
                     if (isReminder) {
                          // conditions for not sending reminder
                          boolean isTooSoon = false;
                          long today = new Date().getTime();
                          long halfDay = 12*3600*1000;
                          long oneDay = 24*3600*1000;
                          // within first 12 hours of task creation
                         isTooSoon = isTooSoon || (today < task.getCreate().getTime() + halfDay);
                         // no due date
                         isTooSoon = isTooSoon || task.getDueDate() == null;
                          // before expected reminder start date
                         isTooSoon = isTooSoon || (today < task.getDueDate().getTime() - reminderStart * oneDay);
                         // out of reminder repeat interval (window 12 hours)
                         isTooSoon = isTooSoon || ((today - (task.getDueDate().getTime() - reminderStart * oneDay)) % reminderRepeat * oneDay > halfDay);
                          if (isTooSoon)
                               return;
                     }
                     List<Usuario> usuarios = this.getUsuariosForTask(task);
                     if (!usuarios.isEmpty()) {
                          Contexts.getEventContext().set("task", task);
                          Contexts.getEventContext().set("usuarios", usuarios);
                          Renderer.instance().render(template);
                     }
                } catch (Exception e) {
                    e.printStackTrace();
                }
           }
      
      //    @Asynchronous
           public void sendMailNewTask(TaskInstance task) {
      //          Events.instance().raiseAsynchronousEvent("sendMailTask", task, "/WEB-INF/pages/mailNewTask.xhtml", false);
                this.sendMailTaskObserver(task, "/WEB-INF/pages/mailNewTask.xhtml", false);
           }
           
      //    @Asynchronous
           public void sendMailTaskReminder(TaskInstance task) {
      //          Events.instance().raiseAsynchronousEvent("sendMailTask", task, "/WEB-INF/pages/mailTaskReminder.xhtml", true);
                this.sendMailTaskObserver(task, "/WEB-INF/pages/mailTaskReminder.xhtml", true);
           }
      
          @Transactional
           private List<Usuario> getUsuariosForTask(TaskInstance task) {
                SiplacadDataHelper siplacadDataHelper = (SiplacadDataHelper) Component.getInstance("siplacadDataHelper");
                // recipients
                String actorId = task.getActorId();
                Set<PooledActor> pooledActors = task.getPooledActors();
                List<Usuario> usuarios = new ArrayList<Usuario>();
                if (actorId != null) {
                     RolUsuario rolUsuario = siplacadDataHelper.getRolUsuarioByActorId(actorId);
                     Usuario usuario = rolUsuario.getUsuario();
                     if (rolUsuario.getEstado() == Estado.ACTIVO && usuario.getEstado() == Estado.ACTIVO) {
                          // No LIE
                          usuario = siplacadDataHelper.find(Usuario.class, usuario.getId());
                          usuario.getEmail(); // here the email should not be null but it is - lazy problem?
                          usuarios.add(usuario);
                     }
                } else {
                     // resolve the user based on the pooledActors
                     Rol rol = siplacadDataHelper.getRolByPooledActors(pooledActors);
      //               usuarios.addAll(rol.getUsuariosActivos());
                     for (Usuario usuario : rol.getUsuariosActivos()) {
                          // No LIE
                          usuario = siplacadDataHelper.find(Usuario.class, usuario.getId());
                          usuario.getEmail(); // here the email should not be null but it is - lazy problem?
                          usuarios.add(usuario);
                     }
                }
                return usuarios;
           }
           
      }



      The mailer component is not in the hot deployable dir, so it is visible from jBPM.


      As you can see, I have tried a lot of things with Asynchronous, Observer, Events API, etc... (some of them are commented out), but none seems to work right. I have checked a lot of forum posts, Seam documentation, jBPM documentation, Seam in Action, but do not completely find the right combination. In one of my code versions, the JbmContext seemed to work well, but not the EntityManager. In another version the EntityManager worked fine, but not the JbpmContext.


      I whould note that in one of my code versions everything seemed to work fine, but it was because I had breakpoints for debugging. Somehow, I had access to all neded contexts when debugging, but when I removed the breakpoints, nothing worked.


      I'm using Seam 2.2.0.GA and jBPM 3.2.6.SP1.


      Please help! Thanks in advance!


      Best regards,


      Luis Tama

        • 1. Re: Asynchronous Seam Mail with JbpmContext and EntityManager
          lvdberg

          Hi,


          keep in mind that Seam interceptors work on the method you call, so @Transactional should be on sendMailNewTask and sendMailTaskReminder. This is also a pre-requisite for jBPM to work as needed; There must be an active transactions, otherwise the jBPMContext will fail. The remderer is the really tricky part, because you want to call it async from an already async method.
          I tried this scenarion several times and finally made a email bean myself,


          Leo




           

          • 2. Re: Asynchronous Seam Mail with JbpmContext and EntityManager
            luiggitama.luiggitama.gmail.com

            Hi Leo, thanks a lot for your reply...


            I have read some of your posts related to jBPM in this forum (or maybe all of them). As you have pointed out, I have annotated sendMailNewTask as @Asynchronous and @Transactional. I also added FetchType.EAGER to the involved relationships for the objects I get from the EntityManager, so there is no chance for LIE. It apparently works, I'm still testing.


            From what you say, the reminder will be kind of hard.


            Maybe the solution is jBPM Mail. I personally think it is too limited, and I don't know if I can access the EntityManager from there. Did you try it also as an alternative before making your own email bean? Have you shared this bean? Could you?



            Luis



            • 3. Re: Asynchronous Seam Mail with JbpmContext and EntityManager
              cbensemann

              For my project I never wanted to use jBPM mail. I wanted to use Seam mail everywhere so that all the email templates were consistent and mail settings only had to be configured in one location (components.xml). What I did was extend the jBPM Mail class. In this class you have access to the taskInstance that you are going to be sending mail for. I also provided jBPM with my own AddressResolver to convert actor ids into email addresses. From the Mail class I then send a Seam asynchronous event which is observed by a seam bean which actually renders and sends the email.


              I'll include most of the code below. I hope it makes sense. The mail class could/should be cleaned up a bit more than it is to make it more generic/reusable but it is currently sufficient for my purposes.


              A node in my business process xml looks like:


              <task-node name="signoff">
                   <task duedate="2 business days" notify="true" description="#{description}">
                        <assignment pooled-actors="handsadvisor" />
                        <reminder duedate="1 business day" repeat="1 business hour" />
                   </task>
                   <event type="task-create">
                        <mail name="taskCreateMail" template="task-create" actors="handsadvisor" />
                   </event>
                   <transition to="end"></transition>
                   <transition to="has_corrective_actions" name="reject_action" />
                   <transition to="perform_review" name="reject_review" />
              </task-node>
              



              As you can see I make use of the built in reminder and mail element in jBPM.


              In my jbpm.cfg.xml I have added the following:


                <string name='mail.class.name' value='nz.co.softwarefactory.esafensound.jbpm.Mail' />
                
                
                <bean name='jbpm.mail.address.resolver'   
                      class='nz.co.softwarefactory.esafensound.jbpm.AddressResolver'   
                      singleton='true' /> 
              



              My extended Mail class looks like:


              /**
               * Copyright Software Factory - 2009
               */
              package nz.co.softwarefactory.esafensound.jbpm;
              
               import java.util.Collection;
              import java.util.HashMap;
              import java.util.Map;
              import java.util.Set;
              
              import nz.co.softwarefactory.esafensound.seam.Credentials;
              import nz.co.softwarefactory.esafensound.util.EmailEventListener;
              
              import org.jboss.seam.core.Events;
              import org.jboss.seam.log.Log;
              import org.jboss.seam.log.Logging;
              import org.jboss.seam.security.Identity;
              import org.jbpm.graph.exe.ExecutionContext;
              import org.jbpm.taskmgmt.exe.PooledActor;
              import org.jbpm.taskmgmt.exe.TaskInstance;
              
              /**
               * Overrides the jBPM mail service to use the seam mail system instead.
               *
               * @author craig
               */
              @SuppressWarnings("serial")
              public class Mail extends org.jbpm.mail.Mail {
              
                  private static final String TEMPLATE_PATH = "/WEB-INF/pages/occurrence/";
                  private static final String EMAIL_TASK_ASSIGNMENT = "taskAssignmentEmailWrapper.xhtml";
                  private static final String EMAIL_TASK_CREATE = "taskCreateEmailWrapper.xhtml";
                  private static final String EMAIL_TASK_REMINDER = "taskReminderEmailWrapper.xhtml";
              
                  private static final String TEMPLATE_TASK_ASSIGNMENT = "task-assign";
                  private static final String TEMPLATE_TASK_CREATE = "task-create";
                  private static final String TEMPLATE_TASK_REMINDER = "task-reminder";
              
                  public String template = null;
              
                  public String actors = null;
              
                  private ExecutionContext executionContext;
              
                  /**
                   * @see org.jbpm.mail.Mail#execute(org.jbpm.graph.exe.ExecutionContext)
                   */
                  @Override
                  public void execute(final ExecutionContext executionContext) {
                      this.executionContext = executionContext;
                      super.execute(executionContext);
                  }
              
                  @SuppressWarnings("unchecked")
                  @Override
                  public void send() {
                      final TaskInstance task = executionContext.getTaskInstance();
                      final Map<String, Object> parameters = new HashMap<String, Object>();
              
                      setActors(task);
              
                      parameters.put("task", task);
                      // TODO this probably isn't as flexible as it could be as does not all
                      // use of "to" field and will not evaluate
                      // expressions before attempting to resolve addresses. Was using
                      // getRecipients but am not able get it to use
                      // the actor Ids I wanted it to as actors is private and cannot be set.
                      final Collection<String> addresses = resolveAddresses(tokenize(actors));
                      if (addresses.isEmpty()) {
                          // we have no one to email so dont try to send one!
                          Log log = Logging.getLog(getClass());
                          log.info("No email addresses configured to send {0} emails to for organization {1}", template, ((Credentials) Identity.instance().getCredentials()).getOrganization());
                          return;
                      }
              
                      parameters.put("addresses", addresses);
              
                      if (Mail.TEMPLATE_TASK_ASSIGNMENT.equals(template)) {
                          Events.instance().raiseAsynchronousEvent(EmailEventListener.EVENT_SEND_ASYNCHRONOUS_JBPM_EMAIL,
                                  Mail.TEMPLATE_PATH + Mail.EMAIL_TASK_ASSIGNMENT, parameters);
                      }
                      else if (Mail.TEMPLATE_TASK_REMINDER.equals(template)) {
                          Events.instance().raiseAsynchronousEvent(EmailEventListener.EVENT_SEND_ASYNCHRONOUS_JBPM_EMAIL,
                                  Mail.TEMPLATE_PATH + Mail.EMAIL_TASK_REMINDER, parameters);
                      }
                      else if (Mail.TEMPLATE_TASK_CREATE.equals(template)) {
                          Events.instance().raiseAsynchronousEvent(EmailEventListener.EVENT_SEND_ASYNCHRONOUS_JBPM_EMAIL,
                                  Mail.TEMPLATE_PATH + Mail.EMAIL_TASK_CREATE, parameters);
                      }
                  }
              
                  private void setActors(final TaskInstance task) {
                      if (actors == null || actors.isEmpty()) {
                          final String actorId = task.getActorId();
                          if (actorId != null && !actorId.isEmpty()) {
                              actors = actorId;
                          }
                          else {
                              final Set<PooledActor> pooledActors = task.getPooledActors();
                              final StringBuilder sb = new StringBuilder();
                              for (final PooledActor pooledActor : pooledActors) {
                                  if (sb.length() > 0) {
                                      sb.append(";");
                                  }
                                  sb.append(pooledActor.getActorId());
                              }
                              actors = sb.toString();
                          }
                      }
                  }
              }
              



              As I said this class would need to be refactored to make it more easily reused in future projects but hopefully it gives you the idea.


              Next is the AddressResolver:


              /**
               * Copyright Software Factory - 2010
               */
              package nz.co.softwarefactory.esafensound.jbpm;
              
              import javax.persistence.EntityManager;
              
              import org.jboss.seam.Component;
              
              /**
               * @author craig
               *
               */
              @SuppressWarnings("serial")
              public class AddressResolver implements org.jbpm.mail.AddressResolver {
              
                  /**
                   * @see org.jbpm.mail.AddressResolver#resolveAddress(java.lang.String)
                   */
                  @Override
                  public Object resolveAddress(final String actorId) {
                      if (actorId.contains("@")) {
                          return actorId;
                      }
                      else {
                          final EntityManager entityManager = getEntityManager();
                          try {
                              // This works only because email is unique. I had a where org = credentials.org but reminder emails are
                              // send by the system so there is no logged in user so org is always null.
                              return entityManager
                                      .createQuery(
                                              "SELECT DISTINCT u.email FROM User u JOIN u.roles AS r WHERE r.rolename = 'handsadvisor'")
                                      .getResultList();
                          }
                          catch (Exception e) {
                              return null;
                          }
                      }
                  }
              
                  /**
                   * @return
                   */
                  private EntityManager getEntityManager() {
                      return (EntityManager) Component.getInstance("entityManager", true);
                  }
              }
              



              This is a jBPM address resolver so it is not a Seam component (neither is the Mail class for that matter). This means that the lifecycle of these beans is not managed by Seam and you cannot use injection. You can still access Seam components via the Component class as shown above.


              Finally you need an event listener:


                  @Observer(EmailEventListener.EVENT_SEND_ASYNCHRONOUS_JBPM_EMAIL)
                  public void sendEmailEventListener(final String template, final Map<String, Object> parameters) {
                      for (final Entry<String, Object> entry : parameters.entrySet()) {
                          Contexts.getEventContext().set(entry.getKey(), entry.getValue());
                      }
              
                      try {
                          renderer.render(template);
                      }
                      catch (final Exception e) {
                          log.error("Unable to send email. Template=[{0}]. Parameters=[{1}]", e, template,
                                  getParametersAsString(parameters));
                      }
                  }
              



              This method would need to be in a Seam component and will actually send the email using Seams mail.


              That probably looks like a lot but for me it was the cleanest way of extending jBPM mail.


              Hope it helps.


              Craig

              • 4. Re: Asynchronous Seam Mail with JbpmContext and EntityManager
                lvdberg

                Hi Craig,


                Speechless now, never came to my mind to extend the jBPM Mail class. My Todo list, is a bit shorter now...


                Leo

                • 5. Re: Asynchronous Seam Mail with JbpmContext and EntityManager
                  luiggitama.luiggitama.gmail.com

                  Hi again my friends, thanks for your responses...


                  I'm close to solveing this issue using the @Transactional and @Observer annotations only on the sendMailTaskObserver method. The other methods have no annotations, but raise asynchronous events with Seam's Events API. Now, on sendMailTaskObserver, I can get email addresses from my entity (proper access to EntityManager) based on the actorId or pooledActors in the TaskInstance (almost proper access to JbpmContext, more on this below...).


                  The new approach:


                  @Transactional
                  @Observer("sendMailTask")
                  public void sendMailTaskObserver(TaskInstance task, String template, boolean isReminder) {
                      System.out.println(task.getPooledActors()); // OK
                      System.out.println(task.getVariables()); // LIE when rendering mail or problems with transactions when testing access here
                      ...
                  }
                  
                  public void sendMailNewTask(TaskInstance task) {
                      System.out.println(task.getPooledActors()); // null, but later resolved
                      Events.instance().raiseAsynchronousEvent("sendMailTask", task, "/WEB-INF/pages/mailNewTask.xhtml", false);
                  }
                  
                  public void sendMailTaskReminder(TaskInstance task) {
                      System.out.println(task.getPooledActors()); // OK
                      Events.instance().raiseAsynchronousEvent("sendMailTask", task, "/WEB-INF/pages/mailTaskReminder.xhtml", true);
                  }



                  The email generated by sendMailNewTask gets sent properly. Good.


                  The emails generated by sendMailTaskReminder get sent, but there are LIE when accessing process and/or task variables from the email template, so rendering is incomplete. When I test access to these variables from the method with println, I'm having trouble with transactions with the JbpmContext. Bad.


                  Why would this happen?


                  Anyway, as I don't have a clue, I'll try Craig's approach. I think I had already read this somewhere on the forum, but didn't want to touch jBPM. I wanted to do this the Seam way only... But now, as I'm having so much trouble, maybe it's much cleaner to override jBPM's behaviour from the inside as Craig did, than trying to access the JbpmContext from a Seam component.


                  I'll tell you how this story continues... :-)


                  Luis Tama

                  • 6. Re: Asynchronous Seam Mail with JbpmContext and EntityManager
                    luiggitama.luiggitama.gmail.com

                    Hi all again! Good news!


                    This story has a happy ending thanks to your help!


                    First of all, let me point out some things I have learned during this journey.



                    1. The best approach for me was to extend the jBPM Mail class. AFAICS, as it has access to the current executionContext there won't be any problem with LIE or transaction locks, because everything is being handled by jBPM. No more @Transactional. Anyway, if one needs to access task and process variables when rendering the email (as it is my case), one should preload them calling task.getVariables() and task.getProcessInstance().getContextInstance().getVariables() (or just task.getContextInstance().getVariables() for preloading both), so there won't be any LIE which would lead to sending the email incompletely rendered. This is necessary because when rendering the email there is no active session in order to get these variables, which are stored in variable containers in current token's context.

                    2. As of jBPM 3.2.6.SP1 which I am using (and still in newest version 3.2.7), there is a bug in the XML schema that prevents proper use of the mail action inside a timer tag (in the designer and when deploying). One could remove all references to the schema in the JPDL, but that is kind of dirty. As the original mail class is basically an ActionHandler, a workaround is to call the extended mail class as an action from inside the timer. As I'm calling it that way for the reminder timer, then I could stop using the mail tag at all inside the event tags and stick to action tags. This way it would not be necessary to add any <string name="jbpm.mail.class.name" value="my.extended.Mail"/> to jbpm.cfg.xml. That's the approach I took.

                    3. Also, it's not really needed to implement an AddressResolver, as one can write the code inside the extended mail class. So there is no need to define it in jbpm.cfg.xml. This way there are no changes at all to jBPM configuration.

                    4. I don't need to define notifiers or reminders on each task. I can define only event nodes at process level for task-create, task-assign and task-end events. This works well because the current task is resolved depending on the current ExecutionContext.

                    5. I was making a mistake when trying to get the pooledActors on task creation. jBPM takes two steps to create tasks, first they are created, then they are assigned to the swimlanes. As I have defined my task assignments with swimlanes based on pooledActors, when the task-create event was triggered by jBPM, there was still no assignment defined for the task, so I could get the pooledActors only when the task-asign event was triggered. Something to be aware of.

                    6. It's also important to make sure that all the needed data from the model is made available and sent to the event observer, because, AFAIK, there won't be any active EntityManager when rendering the email. That's why I used FetchType.EAGER on the needed relationships of my model, which is easier than calling getters.



                    That's how my process definition section for mail sending now looks like this (no mail tags, as everything is resolved in the extended class and in the mail XHTML template):


                    <event type="task-create">
                         <action name="mailTaskCreate" class="org.ostion.siplacad.bpm.SiplacadMail">
                              <template>
                                   task-create
                              </template>
                         </action>
                         <create-timer duedate="2 minutes" name="mailTaskReminder" repeat="2 minutes">
                              <action name="mailTaskReminder" class="org.ostion.siplacad.bpm.SiplacadMail">
                                   <template>
                                        task-reminder
                                   </template>
                              </action>
                         </create-timer>
                    </event>
                    
                    <event type="task-assign">
                         <action name="mailTaskAssign" class="org.ostion.siplacad.bpm.SiplacadMail">
                              <template>
                                   task-assign
                              </template>
                         </action>
                    </event>
                    
                    <event type="task-end">
                         <cancel-timer name="mailTaskReminder"/>
                    </event>



                    Here is my extended mail ActionHandler, based on Craig's idea, where recipients are user entity instances from my model rather than plain email addressess, because I need the name, email and sex - in spanish it's important for the salutation :-)


                    package org.ostion.siplacad.bpm;
                    
                    import java.util.ArrayList;
                    import java.util.Date;
                    import java.util.HashMap;
                    import java.util.List;
                    import java.util.Map;
                    import java.util.Set;
                    import java.util.concurrent.atomic.AtomicBoolean;
                    
                    import org.jboss.seam.Component;
                    import org.jboss.seam.core.Events;
                    import org.jboss.seam.log.Log;
                    import org.jboss.seam.log.Logging;
                    import org.jbpm.graph.exe.ExecutionContext;
                    import org.jbpm.mail.Mail;
                    import org.jbpm.taskmgmt.exe.PooledActor;
                    import org.jbpm.taskmgmt.exe.TaskInstance;
                    import org.ostion.siplacad.model.Estado;
                    import org.ostion.siplacad.model.entity.Rol;
                    import org.ostion.siplacad.model.entity.RolUsuario;
                    import org.ostion.siplacad.model.entity.Usuario;
                    import org.ostion.siplacad.session.SiplacadDataHelper;
                    import org.ostion.util.ui.MailProcessor;
                    
                    /**
                     * Extends jBPM Mail ActionHandler to use send asynchronous mails
                     * through Seam Mail and Events API. Recipients are based on Usuario objects. 
                     * 
                     * Thanks to Craig Bensemann and Leo van den Berg
                     * See: http://seamframework.org/Community/AsynchronousSeamMailWithJbpmContextAndEntityManager
                     * 
                     */
                    @SuppressWarnings("serial")
                    public class SiplacadMail extends Mail {
                    
                         private static final String TEMPLATE_PATH = "/WEB-INF/pages/";
                         private static final String EMAIL_NEW_TASK = "mailNewTask.xhtml";
                         private static final String EMAIL_TASK_REMINDER = "mailTaskReminder.xhtml";
                    
                         private static final String TEMPLATE_TASK_CREATE = "task-create";
                         private static final String TEMPLATE_TASK_ASSIGN = "task-assign";
                         private static final String TEMPLATE_TASK_REMINDER = "task-reminder";
                    
                         private String template = null;
                    
                         private ExecutionContext executionContext;
                    
                         /**
                          * @see org.jbpm.mail.Mail#execute(org.jbpm.graph.exe.ExecutionContext)
                          */
                         @Override
                         public void execute(final ExecutionContext executionContext) {
                              this.executionContext = executionContext;
                              super.execute(executionContext);
                         }
                    
                         /**
                          * @see org.jbpm.mail.Mail#send()
                          */
                         @Override
                         public void send() {
                              Log log = Logging.getLog(getClass());
                              final Map<String, Object> parameters = new HashMap<String, Object>();
                              // task data
                              final TaskInstance task = executionContext.getTaskInstance();
                              // prevent LIE when accessing task variables or process variables from forked tokens
                              //task.getVariables(); // task variables
                              //task.getProcessInstance().getContextInstance().getVariables(); // process variables
                              task.getContextInstance().getVariables(); // both
                              parameters.put("task", task);
                              // recipients
                              AtomicBoolean isPooled = new AtomicBoolean();
                              AtomicBoolean wasAssigned = new AtomicBoolean();
                              final List<Usuario> usuarios = this.getUsuariosForTask(task, isPooled, wasAssigned);
                              if (usuarios.isEmpty()) {
                                   // we have no one to email so dont try to send one!
                                   log.info("No email addresses configured to send {0} emails to.", template);
                                   return;
                              }
                              parameters.put("usuarios", usuarios);
                              // raise asynchronous event for sending mail
                              if (SiplacadMail.TEMPLATE_TASK_CREATE.equals(template) ||
                                        (SiplacadMail.TEMPLATE_TASK_ASSIGN.equals(template) &&
                                        (isPooled.get() || !wasAssigned.get()))) {
                                   Events.instance().raiseAsynchronousEvent(
                                             MailProcessor.EVENT_SEND_MAIL,
                                             SiplacadMail.TEMPLATE_PATH + SiplacadMail.EMAIL_NEW_TASK,
                                             parameters);
                              } else if (SiplacadMail.TEMPLATE_TASK_REMINDER.equals(template)) {
                                   // check conditions for sending reminder
                                   Integer reminderStart = (Integer) task.getVariable("reminderStart");
                                   Integer reminderRepeat = (Integer) task.getVariable("reminderRepeat");
                                   boolean isTooEarly = false;
                                   long today = new Date().getTime();
                                   long halfDay = 12*3600*1000;
                                   long oneDay = 24*3600*1000;
                                   halfDay = 1*60*1000; // 1 minutes (for testing)
                                   oneDay = 2*60*1000; // 2 minutes (for testing)
                                   // within first 12 hours of task creation
                                  isTooEarly = isTooEarly || (today < task.getCreate().getTime() + halfDay);
                                  // no due date
                                  isTooEarly = isTooEarly || task.getDueDate() == null;
                                   // before expected reminder start date
                                  isTooEarly = isTooEarly || (today < task.getDueDate().getTime() - reminderStart * oneDay);
                                  // out of reminder repeat interval (window 12 hours)
                                  isTooEarly = isTooEarly || ((today - (task.getDueDate().getTime() - reminderStart * oneDay)) % reminderRepeat * oneDay > halfDay);
                                   if (isTooEarly) {
                                        log.info("It is too early to send {0} emails.", template);
                                        return;
                                   }
                                   Events.instance().raiseAsynchronousEvent(
                                             MailProcessor.EVENT_SEND_MAIL,
                                             SiplacadMail.TEMPLATE_PATH + SiplacadMail.EMAIL_TASK_REMINDER,
                                             parameters);
                              }
                         }
                         
                         private List<Usuario> getUsuariosForTask(TaskInstance task, AtomicBoolean isPooled, AtomicBoolean wasAssigned) {
                              SiplacadDataHelper siplacadDataHelper = (SiplacadDataHelper) Component.getInstance("siplacadDataHelper");
                              String actorId = task.getActorId();
                              Set<PooledActor> pooledActors = task.getPooledActors();
                              List<Usuario> usuarios = new ArrayList<Usuario>();
                              isPooled.set(false);
                              wasAssigned.set(false);
                              if (actorId != null) {
                                   wasAssigned.set(actorId.equals(task.getPreviousActorId()));
                                   RolUsuario rolUsuario = siplacadDataHelper.getRolUsuarioByActorId(actorId);
                                   Usuario usuario = rolUsuario.getUsuario();
                                   if (rolUsuario.getEstado() == Estado.ACTIVO && usuario.getEstado() == Estado.ACTIVO) {
                                        usuarios.add(usuario);
                                   }
                              } else if (pooledActors != null) {
                                   isPooled.set(true);
                                   Rol rol = siplacadDataHelper.getRolByPooledActors(pooledActors);
                                   usuarios.addAll(rol.getUsuariosActivos());
                              }
                              return usuarios;
                         }
                    
                    }



                    And the mail processor, which is the only Seam component, which observes the events triggered by the extended mail class. It's based on Creig's post. BTW, I just remembered where I saw your solution to extend jBPM mail. I didn't get the idea until you posted it here with more detail... :-)


                    package org.ostion.util.ui;
                    
                    import java.io.Serializable;
                    import java.util.Map;
                    import java.util.Map.Entry;
                    
                    import org.jboss.seam.annotations.AutoCreate;
                    import org.jboss.seam.annotations.Name;
                    import org.jboss.seam.annotations.Observer;
                    import org.jboss.seam.contexts.Contexts;
                    import org.jboss.seam.faces.Renderer;
                    import org.jboss.seam.log.Log;
                    import org.jboss.seam.log.Logging;
                    
                    /**
                     * Thanks to Craig Bensemann and Leo van den Berg
                     * See: http://seamframework.org/Community/AsynchronousSeamMailWithJbpmContextAndEntityManager
                     */
                    @Name("mailProcessor")
                    @AutoCreate
                    public class MailProcessor implements Serializable {
                    
                         private static final long serialVersionUID = 5001647628144517023L;
                         
                         public static final String EVENT_SEND_MAIL = "org.ostion.sendMail";
                         
                         private static final Log log = Logging.getLog(MailProcessor.class);
                    
                         /**
                          * Process send mail event
                          * @param template
                          * @param parameters
                          */
                         @Observer(EVENT_SEND_MAIL)
                         public void sendMailObserver(final String template,
                                   final Map<String, Object> parameters) {
                              for (final Entry<String, Object> entry : parameters.entrySet()) {
                                   Contexts.getEventContext().set(entry.getKey(), entry.getValue());
                              }
                    
                              try {
                                   Renderer.instance().render(template);
                              } catch (final Exception e) {
                                   log.error("Unable to send email. Template=[{0}]. Parameters=[{1}]",
                                             e, template, parameters.toString());
                              }
                         }
                    }



                    My mail XHTML templates must support multiple users as recipients and must be able to access more data about the user. Task data must be available because the localized messages are based on task name and description, as well as on task and process variables (see below). My reminder template looks like this:


                    <ui:repeat xmlns="http://www.w3.org/1999/xhtml"
                              xmlns:ui="http://java.sun.com/jsf/facelets"
                              xmlns:m="http://jboss.com/products/seam/mail"
                              xmlns:h="http://java.sun.com/jsf/html"
                              xmlns:f="http://java.sun.com/jsf/core"
                              xmlns:s="http://jboss.com/products/seam/taglib"
                              value="#{usuarios}" var="usuario">
                    <m:message>
                         <m:from name="SIPLACAD" address="siplacad@localhost.localdomain" />
                         <m:to name="#{usuario.nombre}" address="#{usuario.email}" />
                         <m:cc name="SIPLACAD" address="siplacad@localhost.localdomain" />
                         <m:subject>Recordatorio de Tarea #<h:outputText value="#{task.id}"><f:convertNumber minIntegerDigits="3" /></h:outputText> en SIPLACAD</m:subject>
                         <m:body>
                              <a href="http://localhost:8080/siplacad/" target="_blank">
                                   <img src="cid:#{logo.contentId}" width="325" height="65" border="0" alt="SIPLACAD" title="SIPLACAD" />
                              </a>
                              #{usuario.sexo.label == messages.enumSexoMasculino ? messages.labelEstimado : messages.labelEstimada}#{_}#{usuario.nombre}:<br /><br />
                              Recuerde su tarea en SIPLACAD.<br /><br />
                              <strong>#{messages.labelId}:</strong>#{_}<h:outputText value="#{task.id}"><f:convertNumber minIntegerDigits="3" /></h:outputText><br /><br />
                              <strong>#{messages.labelTarea}:</strong>#{_}#{messages[task.name]}<br /><br />
                              <strong>#{messages.labelDescripcion}:</strong>#{_}#{messages[task.description]}<br /><br />
                              <strong>#{messages.labelFechaLimite}:</strong>#{_}
                              <h:outputText value="#{task.dueDate}">
                                   <s:convertDateTime pattern="dd/MMMM/yyyy" />
                              </h:outputText><br /><br />
                              <br />Por favor ingrese a: <a
                                        href="http://localhost:8080/siplacad/" target="_blank">http://localhost:8080/siplacad/</a>.<br />
                              <br />Cordialmente,<br />SIPLACAD<br /><br />
                              <div style="text-align:right">Para mas informacion visite: <a href="http://localhost:8080/siplacad/" target="_blank">http://localhost:8080/siplacad/</a></div>
                         </m:body>
                    </m:message>
                    </ui:repeat>



                    Here are some of my messages (see how there are two ways of accessing task and process variables, which should be preloaded):


                    #taskUnidad=\#{comment \!\= null ? comment.taskInstance.processInstance.contextInstance.variables['codigoUnidad'] \: (task \!\= null ? task.processInstance.contextInstance.variables['codigoUnidad'] \: taskInstance.processInstance.contextInstance.variables['codigoUnidad'])}
                    #taskTermino=\#{comment \!\= null ? comment.taskInstance.processInstance.contextInstance.variables['termino'] \: (task \!\= null ? task.processInstance.contextInstance.variables['termino'] \: taskInstance.processInstance.contextInstance.variables['termino'])}
                    #taskArea=\#{comment \!\= null ? comment.taskInstance.variables['nombreArea'] \: (task \!\= null ? task.variables['nombreArea'] \: taskInstance.variables['nombreArea'])}
                    taskUnidad=\#{comment \!\= null ? comment.taskInstance.getVariable("codigoUnidad") \: (task \!\= null ? task.getVariable("codigoUnidad") \: taskInstance.getVariable("codigoUnidad"))}
                    taskTermino=\#{comment \!\= null ? comment.taskInstance.getVariable("termino") \: (task \!\= null ? task.getVariable("termino") \: taskInstance.getVariable("termino"))}
                    taskArea=\#{comment \!\= null ? comment.taskInstance.getVariable("nombreArea") \: (task \!\= null ? task.getVariable("nombreArea") \: taskInstance.getVariable("nombreArea"))}
                    taskUnidadTermino=\#{messages.taskUnidad} - \#{messages.taskTermino}
                    
                    taskPlanificarArea=\#{messages.taskUnidadTermino}\: Planificar Area \#{messages.taskArea}
                    taskDescriptionPlanificarArea=Elaborar la Planificaci\u00F3n Acad\u00E9mica de Docentes y Paralelos para las Materias del Area \#{messages.taskArea} dentro de la Planificaci\u00F3n Acad\u00E9mica de la Unidad \#{messages.taskUnidad} en el T\u00E9rmino \#{messages.taskTermino}, y solicitar al Subdecano su Revisi\u00F3n, aplicando las posibles correcciones, para su Aprobaci\u00F3n.
                    taskRevisarPlanificacionArea=\#{messages.taskUnidadTermino}\: Revisar Planificaci\u00F3n del Area \#{messages.taskArea}
                    taskDescriptionRevisarPlanificacionArea=Revisar la planificaci\u00F3n Acad\u00E9mica de Docentes y Paralelos para las Materias del Area \#{messages.taskArea} dentro de la Planificaci\u00F3n Acad\u00E9mica de la Unidad \#{messages.taskUnidad} en el T\u00E9rmino \#{messages.taskTermino}, y aprobarla o solicitar correcciones al Coordinador de Area correspondiente.



                    Below is one of my task nodes. Task name and description are generic strings which will serve as keys to messages (as shown above), so they will be rendered differently depending on variables in the current context. Remember, I don't need to define notifiers nor reminders here.


                    <task-node name="Revisar Planificacion del Area">
                         <description>
                              El Subdecano debe aprobar la Planificacion de cada Area
                         </description>
                         <task name="taskRevisarPlanificacionArea" duedate="#{taskDueDateRevisarPlanificacionArea}" swimlane="subdecano">
                              <description>
                                   taskDescriptionRevisarPlanificacionArea
                              </description>
                         </task>
                         <transition to="nodeJoinPlanificarAreas" name="Aprobar"></transition>
                         <transition to="Elaborar Planificacion Academica del Area" name="Devolver"></transition>
                    </task-node>



                    So, what else can I say? Thanks a lot and God bless you all!


                    Best regards,


                    Luis Tama

                    • 7. Re: Asynchronous Seam Mail with JbpmContext and EntityManager
                      luiggitama.luiggitama.gmail.com

                      I forgot to mention something. Maybe you already know this, but jBPM job executor must be enabled in web.xml so reminders work as expected. We don't need to add any asynchronous timing services in components.xml.


                        <!-- Job executor launcher -->
                        <listener>
                          <description>
                            Starts the job executor on initialization and stops it on destruction.
                          </description>
                          <listener-class>org.jbpm.web.JobExecutorLauncher</listener-class>
                        </listener>



                      Something else. If there are changes to task or process variables, or to the task itself (started, ended), and one of these mails is being sent, there could be some exceptions, which I consider a minor problem.


                      2010-08-30 18:27:29,140 ERROR [org.hibernate.event.def.AbstractFlushingEventListener] Could not synchronize database state with session
                      org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [org.jbpm.graph.exe.Token#1]
                           at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1765)
                           at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2407)
                           at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2307)
                           at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2607)
                           at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:92)
                           at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
                           at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:234)
                           at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:142)
                           at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
                           at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
                           at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
                           at org.jboss.seam.bpm.ManagedJbpmContext.beforeCompletion(ManagedJbpmContext.java:116)
                           at org.jboss.seam.transaction.SynchronizationRegistry.beforeTransactionCompletion(SynchronizationRegistry.java:62)
                           at org.jboss.seam.transaction.SeSynchronizations.beforeTransactionCommit(SeSynchronizations.java:49)
                           at org.jboss.seam.transaction.UTTransaction.commit(UTTransaction.java:49)
                           at org.jboss.seam.jsf.SeamPhaseListener.commitOrRollback(SeamPhaseListener.java:613)
                           at org.jboss.seam.jsf.SeamPhaseListener.commitOrRollback(SeamPhaseListener.java:604)
                           at org.jboss.seam.jsf.SeamPhaseListener.handleTransactionsAfterPhase(SeamPhaseListener.java:345)
                           at org.jboss.seam.jsf.SeamPhaseListener.afterServletPhase(SeamPhaseListener.java:245)
                           at org.jboss.seam.jsf.SeamPhaseListener.afterPhase(SeamPhaseListener.java:196)
                           at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:280)
                           at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:117)
                           at javax.faces.webapp.FacesServlet.service(FacesServlet.java:244)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.tuckey.web.filters.urlrewrite.RuleChain.handleRewrite(RuleChain.java:164)
                           at org.tuckey.web.filters.urlrewrite.RuleChain.doRules(RuleChain.java:141)
                           at org.tuckey.web.filters.urlrewrite.UrlRewriter.processRequest(UrlRewriter.java:90)
                           at org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:406)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:83)
                           at org.jboss.seam.web.ExceptionFilter.doFilter(ExceptionFilter.java:64)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.RedirectFilter.doFilter(RedirectFilter.java:45)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:206)
                           at org.ajax4jsf.webapp.BaseFilter.handleRequest(BaseFilter.java:290)
                           at org.ajax4jsf.webapp.BaseFilter.processUploadsAndHandleRequest(BaseFilter.java:388)
                           at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:515)
                           at org.jboss.seam.web.Ajax4jsfFilter.doFilter(Ajax4jsfFilter.java:56)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.LoggingFilter.doFilter(LoggingFilter.java:60)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.HotDeployFilter.doFilter(HotDeployFilter.java:53)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.IdentityFilter.doFilter(IdentityFilter.java:40)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.ostion.util.timing.TimingFilter.doFilter(TimingFilter.java:38)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.servlet.SeamFilter.doFilter(SeamFilter.java:158)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
                           at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
                           at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:179)
                           at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:432)
                           at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
                           at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
                           at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
                           at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157)
                           at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
                           at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:262)
                           at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
                           at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
                           at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:446)
                           at java.lang.Thread.run(Unknown Source)
                      2010-08-30 18:27:29,140 ERROR [org.jboss.seam.transaction.SynchronizationRegistry] Exception processing transaction Synchronization before completion
                      org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [org.jbpm.graph.exe.Token#1]
                           at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1765)
                           at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2407)
                           at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2307)
                           at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2607)
                           at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:92)
                           at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
                           at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:234)
                           at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:142)
                           at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
                           at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
                           at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
                           at org.jboss.seam.bpm.ManagedJbpmContext.beforeCompletion(ManagedJbpmContext.java:116)
                           at org.jboss.seam.transaction.SynchronizationRegistry.beforeTransactionCompletion(SynchronizationRegistry.java:62)
                           at org.jboss.seam.transaction.SeSynchronizations.beforeTransactionCommit(SeSynchronizations.java:49)
                           at org.jboss.seam.transaction.UTTransaction.commit(UTTransaction.java:49)
                           at org.jboss.seam.jsf.SeamPhaseListener.commitOrRollback(SeamPhaseListener.java:613)
                           at org.jboss.seam.jsf.SeamPhaseListener.commitOrRollback(SeamPhaseListener.java:604)
                           at org.jboss.seam.jsf.SeamPhaseListener.handleTransactionsAfterPhase(SeamPhaseListener.java:345)
                           at org.jboss.seam.jsf.SeamPhaseListener.afterServletPhase(SeamPhaseListener.java:245)
                           at org.jboss.seam.jsf.SeamPhaseListener.afterPhase(SeamPhaseListener.java:196)
                           at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:280)
                           at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:117)
                           at javax.faces.webapp.FacesServlet.service(FacesServlet.java:244)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.tuckey.web.filters.urlrewrite.RuleChain.handleRewrite(RuleChain.java:164)
                           at org.tuckey.web.filters.urlrewrite.RuleChain.doRules(RuleChain.java:141)
                           at org.tuckey.web.filters.urlrewrite.UrlRewriter.processRequest(UrlRewriter.java:90)
                           at org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:406)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:83)
                           at org.jboss.seam.web.ExceptionFilter.doFilter(ExceptionFilter.java:64)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.RedirectFilter.doFilter(RedirectFilter.java:45)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:206)
                           at org.ajax4jsf.webapp.BaseFilter.handleRequest(BaseFilter.java:290)
                           at org.ajax4jsf.webapp.BaseFilter.processUploadsAndHandleRequest(BaseFilter.java:388)
                           at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:515)
                           at org.jboss.seam.web.Ajax4jsfFilter.doFilter(Ajax4jsfFilter.java:56)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.LoggingFilter.doFilter(LoggingFilter.java:60)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.HotDeployFilter.doFilter(HotDeployFilter.java:53)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.IdentityFilter.doFilter(IdentityFilter.java:40)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.ostion.util.timing.TimingFilter.doFilter(TimingFilter.java:38)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.servlet.SeamFilter.doFilter(SeamFilter.java:158)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
                           at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
                           at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:179)
                           at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:432)
                           at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
                           at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
                           at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
                           at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157)
                           at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
                           at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:262)
                           at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
                           at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
                           at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:446)
                           at java.lang.Thread.run(Unknown Source)
                      
                      (two more of the above exceptions)
                      
                      2010-08-30 18:27:34,812 ERROR [org.hibernate.event.def.AbstractFlushingEventListener] Could not synchronize database state with session
                      org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [org.jbpm.graph.exe.Token#1]
                           at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1765)
                           at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2407)
                           at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2307)
                           at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2607)
                           at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:92)
                           at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
                           at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:234)
                           at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:142)
                           at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
                           at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
                           at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
                           at org.jbpm.persistence.db.DbPersistenceService.flushSession(DbPersistenceService.java:261)
                           at org.jbpm.persistence.db.DbPersistenceService.close(DbPersistenceService.java:200)
                           at org.jbpm.svc.Services.close(Services.java:243)
                           at org.jbpm.JbpmContext.close(JbpmContext.java:133)
                           at org.jboss.seam.bpm.ManagedJbpmContext.closeContext(ManagedJbpmContext.java:155)
                           at org.jboss.seam.bpm.ManagedJbpmContext.destroy(ManagedJbpmContext.java:148)
                           at sun.reflect.GeneratedMethodAccessor2425.invoke(Unknown Source)
                           at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
                           at java.lang.reflect.Method.invoke(Unknown Source)
                           at org.jboss.seam.util.Reflections.invoke(Reflections.java:22)
                           at org.jboss.seam.util.Reflections.invokeAndWrap(Reflections.java:144)
                           at org.jboss.seam.Component.callComponentMethod(Component.java:2275)
                           at org.jboss.seam.Component.callDestroyMethod(Component.java:2206)
                           at org.jboss.seam.Component.destroy(Component.java:1472)
                           at org.jboss.seam.contexts.Contexts.destroy(Contexts.java:251)
                           at org.jboss.seam.contexts.Contexts.flushAndDestroyContexts(Contexts.java:394)
                           at org.jboss.seam.contexts.FacesLifecycle.endRequest(FacesLifecycle.java:129)
                           at org.jboss.seam.jsf.SeamPhaseListener.afterRenderResponse(SeamPhaseListener.java:514)
                           at org.jboss.seam.jsf.SeamPhaseListener.afterServletPhase(SeamPhaseListener.java:249)
                           at org.jboss.seam.jsf.SeamPhaseListener.afterPhase(SeamPhaseListener.java:196)
                           at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:280)
                           at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:144)
                           at javax.faces.webapp.FacesServlet.service(FacesServlet.java:245)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.tuckey.web.filters.urlrewrite.RuleChain.handleRewrite(RuleChain.java:164)
                           at org.tuckey.web.filters.urlrewrite.RuleChain.doRules(RuleChain.java:141)
                           at org.tuckey.web.filters.urlrewrite.UrlRewriter.processRequest(UrlRewriter.java:90)
                           at org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:406)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:83)
                           at org.jboss.seam.web.ExceptionFilter.doFilter(ExceptionFilter.java:64)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.RedirectFilter.doFilter(RedirectFilter.java:45)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:206)
                           at org.ajax4jsf.webapp.BaseFilter.handleRequest(BaseFilter.java:290)
                           at org.ajax4jsf.webapp.BaseFilter.processUploadsAndHandleRequest(BaseFilter.java:388)
                           at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:515)
                           at org.jboss.seam.web.Ajax4jsfFilter.doFilter(Ajax4jsfFilter.java:56)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.LoggingFilter.doFilter(LoggingFilter.java:60)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.HotDeployFilter.doFilter(HotDeployFilter.java:53)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.IdentityFilter.doFilter(IdentityFilter.java:40)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.ostion.util.timing.TimingFilter.doFilter(TimingFilter.java:38)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.servlet.SeamFilter.doFilter(SeamFilter.java:158)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
                           at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
                           at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:179)
                           at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:432)
                           at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
                           at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
                           at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
                           at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157)
                           at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
                           at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:262)
                           at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
                           at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
                           at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:446)
                           at java.lang.Thread.run(Unknown Source)
                      2010-08-30 18:27:34,812 INFO  [org.jbpm.persistence.db.DbPersistenceService] problem flushing session: optimistic locking failed
                      2010-08-30 18:27:34,812 INFO  [org.jbpm.svc.Services] optimistic locking failed, could not close service: persistence
                      2010-08-30 18:27:34,812 WARN  [org.jboss.seam.Component] Exception calling component @Destroy method: org.jboss.seam.bpm.jbpmContext
                      org.jbpm.persistence.JbpmPersistenceException: hibernate flush session failed
                           at org.jbpm.persistence.db.DbPersistenceService.close(DbPersistenceService.java:207)
                           at org.jbpm.svc.Services.close(Services.java:243)
                           at org.jbpm.JbpmContext.close(JbpmContext.java:133)
                           at org.jboss.seam.bpm.ManagedJbpmContext.closeContext(ManagedJbpmContext.java:155)
                           at org.jboss.seam.bpm.ManagedJbpmContext.destroy(ManagedJbpmContext.java:148)
                           at sun.reflect.GeneratedMethodAccessor2425.invoke(Unknown Source)
                           at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
                           at java.lang.reflect.Method.invoke(Unknown Source)
                           at org.jboss.seam.util.Reflections.invoke(Reflections.java:22)
                           at org.jboss.seam.util.Reflections.invokeAndWrap(Reflections.java:144)
                           at org.jboss.seam.Component.callComponentMethod(Component.java:2275)
                           at org.jboss.seam.Component.callDestroyMethod(Component.java:2206)
                           at org.jboss.seam.Component.destroy(Component.java:1472)
                           at org.jboss.seam.contexts.Contexts.destroy(Contexts.java:251)
                           at org.jboss.seam.contexts.Contexts.flushAndDestroyContexts(Contexts.java:394)
                           at org.jboss.seam.contexts.FacesLifecycle.endRequest(FacesLifecycle.java:129)
                           at org.jboss.seam.jsf.SeamPhaseListener.afterRenderResponse(SeamPhaseListener.java:514)
                           at org.jboss.seam.jsf.SeamPhaseListener.afterServletPhase(SeamPhaseListener.java:249)
                           at org.jboss.seam.jsf.SeamPhaseListener.afterPhase(SeamPhaseListener.java:196)
                           at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:280)
                           at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:144)
                           at javax.faces.webapp.FacesServlet.service(FacesServlet.java:245)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.tuckey.web.filters.urlrewrite.RuleChain.handleRewrite(RuleChain.java:164)
                           at org.tuckey.web.filters.urlrewrite.RuleChain.doRules(RuleChain.java:141)
                           at org.tuckey.web.filters.urlrewrite.UrlRewriter.processRequest(UrlRewriter.java:90)
                           at org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:406)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:83)
                           at org.jboss.seam.web.ExceptionFilter.doFilter(ExceptionFilter.java:64)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.RedirectFilter.doFilter(RedirectFilter.java:45)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:206)
                           at org.ajax4jsf.webapp.BaseFilter.handleRequest(BaseFilter.java:290)
                           at org.ajax4jsf.webapp.BaseFilter.processUploadsAndHandleRequest(BaseFilter.java:388)
                           at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:515)
                           at org.jboss.seam.web.Ajax4jsfFilter.doFilter(Ajax4jsfFilter.java:56)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.LoggingFilter.doFilter(LoggingFilter.java:60)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.HotDeployFilter.doFilter(HotDeployFilter.java:53)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.web.IdentityFilter.doFilter(IdentityFilter.java:40)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.ostion.util.timing.TimingFilter.doFilter(TimingFilter.java:38)
                           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
                           at org.jboss.seam.servlet.SeamFilter.doFilter(SeamFilter.java:158)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
                           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                           at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
                           at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
                           at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:179)
                           at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:432)
                           at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
                           at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
                           at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
                           at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157)
                           at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
                           at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:262)
                           at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
                           at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
                           at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:446)
                           at java.lang.Thread.run(Unknown Source)
                      Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [org.jbpm.graph.exe.Token#1]
                           at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1765)
                           at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2407)
                           at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2307)
                           at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2607)
                           at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:92)
                           at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
                           at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:234)
                           at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:142)
                           at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
                           at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
                           at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
                           at org.jbpm.persistence.db.DbPersistenceService.flushSession(DbPersistenceService.java:261)
                           at org.jbpm.persistence.db.DbPersistenceService.close(DbPersistenceService.java:200)
                           ... 68 more



                      Should I worry about this?


                      Luis Tama

                      • 8. Re: Asynchronous Seam Mail with JbpmContext and EntityManager
                        lvdberg

                        Hi,


                        I.ve enabled the Jobexecutor as a Servlet, but I think it basically does the same. The jBPM documentation warns about this message and gives the hint about how to syurpress it.


                        In the jbpm-configuration file you add (or edit the following:



                        <boolean name="jbpm.hide.stale.object.exceptions" value="true" /> 



                        Your errors will vanish, but - to be honest - it still gives me the creeps, because the documentation specifies this when running in a cluster, I suspect the error comse from the fact that Seam is co-handling the jbpmContext and therefor is causing the stale object exceptions.


                        Leo

                        • 9. Re: Asynchronous Seam Mail with JbpmContext and EntityManager
                          cbensemann

                          Great news that you got it working. Yes that Seam tips website is mine. I mostly post things there to remind myself (and other developers in my compaay) how we did things. You made a good point though that I need to add more detail to a lot of those pages to make them more useful to others.


                          I have never seen the stale object exceptions you posted above. I'm not sure if it has anything to do with the way I have enabled the scheduler.


                          I didn't enable the scheduler through web.xml as you have. I extended the Seam Jbpm class and did it there. The details are available here I basically followed as per the instructions in that post (thanks to the original poster) and then changed the bits as per my post at the bottom. I didn't know you could enable it in web.xml. I have also mentioned it briefly on my seam tips site here


                          Craig