Package er.quartzscheduler.foundation

Source Code of er.quartzscheduler.foundation.ERQSJobListener

package er.quartzscheduler.foundation;

import java.text.MessageFormat;
import java.util.Date;

import javax.mail.MessagingException;
import javax.mail.internet.AddressException;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;

import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.eocontrol.EOEnterpriseObject;
import com.webobjects.eocontrol.EOGlobalID;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSNotificationCenter;
import com.webobjects.foundation.NSTimestamp;
import com.webobjects.foundation.NSValidation;

import er.extensions.foundation.ERXProperties;
import er.extensions.foundation.ERXStringUtilities;
import er.extensions.localization.ERXLocalizer;
import er.javamail.ERMailDeliveryPlainText;
import er.quartzscheduler.util.ERQSSchedulerServiceFrameworkPrincipal;

/**
* The job listener is called automatically before a job is executed and after it has been executed.<p>
* When a job is candidate to be executed, the job listener posts a notification JOB_WILL_RUN through the NSNotificationCenter.
* If you want to be notified, subscribe to the JOB_WILL_RUN notification name and read the notification userInfo to know which job
* will be executed.<p>
* When a job has been executed, the job listener posts a notification JOB_RAN through the NSNotificationCenter.
* Again, if you want to be notified, subscribe to the JOB_WILL_RUN notification name and read the notification userInfo to know which job.
* If the job fails, we can also get the exception from the userInfo with the key EXCEPTION_KEY.<p>
* Depending on the nature of the job description, you have to check the following keys when you access to the userInfo:
* <ul>
* <li> ERQSJob.ENTERPRISE_OBJECT_KEY if the isEnterpriseObject() method of the job description returns true
* <li> ERQSJob.NOT_PERSISTENT_OBJECT_KEY if the job description is not an enterprise object
* </ul>
*
* When the job has been executed, the listener logs information and can send an email. The content of the log and
* the email are identical.
*
* @see #jobToBeExecuted
* @see #jobWasExecuted
* @see #sendMail
* @see #logResult
*/
public class ERQSJobListener extends ERQSAbstractListener implements JobListener
{
 
  public static String JOB_WILL_RUN = "jobWillRun";
  public static String JOB_RAN = "jobRan";
  public static String EXCEPTION_KEY = "exceptionKey";
  public static final String DEFAULT_MAIL_SUBJECT_TEMPLATE = "Job info: {0} is done.";
  public static final String DEFAULT_MAIL_ERROR_MESSAGE_TEMPLATE = "Error message: {0}. It took {1}";
  public static final String DEFAULT_MAIL_SHORT_MESSAGE_TEMPLATE = "It took {0}.";
  public static final String DEFAULT_MAIL_MESSAGE_WITH_MORE_INFOS_TEMPLATE = "More informations: {0}. It took {1}.";

  public ERQSJobListener(final ERQSSchedulerServiceFrameworkPrincipal schedulerFPInstance)
  {
    super(schedulerFPInstance);
  }

  /**
   * This method is due to JobListener interface.
   * Get the name of the JobListener.
   */
  public String getName()
  {
    return this.getClass().getName();
  }
 
  /**
   * This method is due to JobListener interface.<p>
   * Called by the Scheduler when a JobDetail  was about to be executed (an associated Trigger has occured),
   * but a TriggerListener vetoed it's execution.<br>
   * The method is empty.
   */
  public void jobExecutionVetoed(final JobExecutionContext jobexecutioncontext)
  {

  }

  /**
   * This method is due to JobListener interface.<p>
   * Called by the Scheduler when a JobDetail  is about to be executed (an associated Trigger has occurred).<p>
   * Posts the notification JOB_WILL_RUN and a userInfo with a global ID if the key is ERQSJob.ENTERPRISE_OBJECT_KEY
   * or directly the ERQSJobDescription object with the key ERQSJob.NOT_PERSISTENT_OBJECT_KEY
   */
  public void jobToBeExecuted(final JobExecutionContext jobexecutioncontext)
  {
    EOGlobalID id = null;
    ERQSJobDescription aJobDescription = null;
    try
    {
      NSDictionary<String, Object> userInfo = null;

      id = (EOGlobalID) jobexecutioncontext.getMergedJobDataMap().get(ERQSJob.ENTERPRISE_OBJECT_KEY);

      if (id != null)
        userInfo = new NSDictionary<String, Object>(id, ERQSJob.ENTERPRISE_OBJECT_KEY);
      else
      {
        aJobDescription = (ERQSJobDescription) jobexecutioncontext.getMergedJobDataMap().get(ERQSJob.NOT_PERSISTENT_OBJECT_KEY);
        if (aJobDescription != null)
          userInfo = new NSDictionary<String, Object>(aJobDescription, ERQSJob.NOT_PERSISTENT_OBJECT_KEY);
      }
      if (userInfo != null && userInfo.size() > 0)
        NSNotificationCenter.defaultCenter().postNotification(JOB_WILL_RUN, null, userInfo);

      if(log.isInfoEnabled())
      {
        log.info("************** Job '" + jobexecutioncontext.getJobDetail().getKey().getGroup() + "." + jobexecutioncontext.getJobDetail().getKey().getName() + "' is starting. FireTime: " + jobexecutioncontext.getFireTime() + " /previousFireTime: " + jobexecutioncontext.getPreviousFireTime() + " /nextFireTime: " + jobexecutioncontext.getNextFireTime() + " **************");
      }
    } catch (Exception e)
    {
      log.error("method: jobToBeExecuted: an error occured: EOGlobalID: " + id + " /jobDescription: " + aJobDescription, e);
   
  }

  /**
   * This method is due to JobListener interface.
   * Called by the Scheduler after a JobDetail  has been executed <p>
   * It retrieve the ERQSJobDescription object from the datamap and updates the object.<br>
   * It also send an email if <code>er.quartzscheduler.ERQSJobListener.sendingmail=true</code><p>
   * @see #recipients(JobExecutionContext, boolean)
   */
  public void jobWasExecuted(final JobExecutionContext jobexecutioncontext, final JobExecutionException jobexecutionexception)
  {
    NSMutableDictionary<String, Object> userInfo = new NSMutableDictionary<String, Object>();
    String errorMsg = null;

    if (log.isDebugEnabled())
      log.debug("method: jobWasExecuted: job: " + jobexecutioncontext.getJobDetail() + " /exception: " + jobexecutionexception);

    if (jobexecutionexception != null)
    {
      errorMsg = jobexecutionexception.getMessage();
      userInfo.setObjectForKey(jobexecutionexception, EXCEPTION_KEY);
      log.error("method: jobWasExecuted: jobexecutionexception: ", jobexecutionexception);
    }

    // Even if there is an exception, we continue to put the jobDescription object in the userInfo
    if (jobexecutioncontext.getMergedJobDataMap() != null)
    {
      ERQSJobDescription aJobDescription = null;
      aJobDescription = (ERQSJobDescription) jobexecutioncontext.getMergedJobDataMap().get(ERQSJob.NOT_PERSISTENT_OBJECT_KEY);

      if (aJobDescription != null)
      {
        userInfo.setObjectForKey(aJobDescription, ERQSJob.NOT_PERSISTENT_OBJECT_KEY);
        updateJobDescription(jobexecutioncontext, aJobDescription);
      }

      if (aJobDescription == null)
      {
        EOGlobalID id = (EOGlobalID) jobexecutioncontext.getMergedJobDataMap().get(ERQSJob.ENTERPRISE_OBJECT_KEY);

        // We save in database if there is no exception.
        if (id != null && jobexecutionexception == null)
        {
          userInfo.setObjectForKey(id, ERQSJob.ENTERPRISE_OBJECT_KEY);
          EOEditingContext ec = editingContext();
          ec.lock();
          try
          {
            // aJobDescription eo is refreshed because it can have been modified by the job.
            // The job can use a different EOF stack so this ec doesn't know that it has changed.
            // If we don't refresh it, we could get a updateValuesInRowDescribedByQualifier exception.
            // Trust me, we can get this exception easily.
            ec.setFetchTimestamp(System.currentTimeMillis());
            aJobDescription = (ERQSJobDescription) ec.faultForGlobalID(id, ec);
            ec.refreshObject((EOEnterpriseObject) aJobDescription);
           
            if (log.isDebugEnabled())
              log.debug("method: jobWasExecuted: aJobDescription: " + aJobDescription);

            if (aJobDescription != null && aJobDescription.isEnterpriseObject())
            {
              updateJobDescription(jobexecutioncontext, aJobDescription);
              ec.saveChanges();
            }
          }  catch (NSValidation.ValidationException eValidation)
          {
            errorMsg = eValidation.getMessage();
            userInfo.setObjectForKey(eValidation, EXCEPTION_KEY);
            log.error("method: jobWasExecuted: validationException: ", eValidation);
          } catch (Exception e)
          {
            errorMsg = e.getMessage();
            userInfo.setObjectForKey(e, EXCEPTION_KEY);
            log.error("method: jobWasExecuted: exception when saving job description: ", e);
          } finally
          {
            ec.unlock();
          }
        }
      }

      logResult(jobexecutioncontext, errorMsg);
      // We read the value each time because this value can be changed dynamically in development.
      boolean isSendingMail = ERXProperties.booleanForKeyWithDefault("er.quartzscheduler.ERQSJobListener.sendingmail", false);
      if (isSendingMail)
        sendMail(getMailSubject(jobexecutioncontext), getMailContent(jobexecutioncontext, errorMsg), recipients(jobexecutioncontext, jobexecutionexception == null));
    }
    if (userInfo != null && userInfo.size() > 0)
      NSNotificationCenter.defaultCenter().postNotification(JOB_RAN, null, userInfo.immutableClone());
  }

  /**
   * Return a list of recipients depending on the good or bad execution of the job.
   * If the job ran successfully, the recipients are:
   * <ul>
   * <li>the recipients returned by the method recipients() of ERQSJobDescription
   * <li>the email set by the property <code>er.quartzscheduler.ERQSJobListener.executionWithSuccess.to</code> if any
   * </ul>
   * If the job didn't run successfully, the recipients are:
   * <ul>
   * <li>the recipients returned by the method recipients() of ERQSJobDescription
   * <li>the email set by the property <code>er.quartzscheduler.ERQSJobListener.executionWithError.to</code> if any
   * </ul>
   * @see ERQSJobDescription#recipients(boolean)
   *
   * @param jobexecutioncontext
   * @param jobRanSuccessfully
   * @return a list of recipients
   */
  protected NSArray<String> recipients(final JobExecutionContext jobexecutioncontext, final boolean jobRanSuccessfully)
  {
    ERQSJobDescription aJobDescription = getJobDescription(jobexecutioncontext, editingContext());
    NSArray<String> recipients = aJobDescription != null ? aJobDescription.recipients(jobRanSuccessfully) : null;
    String toEmail;
    if (jobRanSuccessfully)
      toEmail = ERXProperties.stringForKeyWithDefault("er.quartzscheduler.ERQSJobListener.executionWithSuccess.to","");       
    else
      toEmail = ERXProperties.stringForKeyWithDefault("er.quartzscheduler.ERQSJobListener.executionWithError.to","");
   
    if (toEmail.length() > 0)
    {
      if (recipients == null)
        recipients = new NSArray<String>(toEmail);
      else
        recipients = recipients.mutableClone().arrayByAddingObject(toEmail);
    }
    return recipients;
  }
 
  /**
   * Update the first, last and next execution date attributes of jobDescription
   *
   * @param jobexecutioncontext
   * @param jobDescription
   */
  protected void updateJobDescription(final JobExecutionContext jobexecutioncontext, final ERQSJobDescription jobDescription)
  {
    if (jobDescription.firstExecutionDate() == null && jobexecutioncontext.getFireTime() != null)
      jobDescription.setFirstExecutionDate(dateToNSTimestamp(jobexecutioncontext.getFireTime()));

    jobDescription.setLastExecutionDate(dateToNSTimestamp(jobexecutioncontext.getFireTime()));
    // The next fire time can be null, mainly if it's a simple trigger when launched manually for example.
    if (jobexecutioncontext.getNextFireTime() != null)
      jobDescription.setNextExecutionDate(dateToNSTimestamp(jobexecutioncontext.getNextFireTime()));
  }
 
  /**
   * If log info is enabled, logResult logs informations about the job execution like the job duration. It can
   * also logs specific information if the job called the method setResult(message) before ending its duty.<p>
   * But if something wrong happened, the log displays the message <code>errorMsg</code>.
   *
   * @param jobexecutioncontext
   * @param errorMsg
   */
  protected void logResult(final JobExecutionContext jobexecutioncontext, final String errorMsg)
  {
    if(log.isInfoEnabled())
    {
      String jobFullName = jobexecutioncontext.getJobDetail().getKey().getGroup() + "." + jobexecutioncontext.getJobDetail().getKey().getName();
      String msg = (String) jobexecutioncontext.getResult();
      String duration = formattedDuration(jobexecutioncontext.getJobRunTime());
      if ((msg != null) && (msg.length() != 0))
        log.info("************** More informations about the job: '" + jobFullName + "' /Message: "+ msg  + " **************");
      if (errorMsg != null)
        log.info("************** Execution error about the job: '" + jobFullName + "' /Error message: "+ errorMsg  + " **************");
      else
        log.info("************** Job '" + jobFullName + "' is done and it took: " + duration + " **************");
    }
  }


  /**
   * Return the mail subject.<p>
   * An interesting improvement will be to use a localized template. Currently, the default message is:<br>
   * <i>Job info: JobGroup.MyBeautifullJob is done.</i>
   *
   * @param jobexecutioncontext (used to build the job full name)
   * @return subject
   */
  protected String getMailSubject(final JobExecutionContext jobexecutioncontext)
  {
    String subjectTemplate = (String) localizer().valueForKey("COScheduler.MailSubject");
    if (log.isDebugEnabled())
      log.debug("method: getMailSubject: subjectTemplate: " + subjectTemplate);
    if (subjectTemplate == null)
    {
      log.warn("method: getMailSubject: subjectTemplate is null but shouldn't be!!! / localizer: " + localizer());
      subjectTemplate = DEFAULT_MAIL_SUBJECT_TEMPLATE;
    }
    String jobFullName = jobexecutioncontext.getJobDetail().getKey().getGroup() + "." + jobexecutioncontext.getJobDetail().getKey().getName();
    return MessageFormat.format(subjectTemplate, jobFullName);
  }
 
  /**
   * Return the mail content.<p>
   * An interesting improvement will be to use a localized template. Currently, the default content is:<br>
   * <i>More informations:blabla. It took 90s.</i> if the job returns additional informations or just
   * <i>It took 90s.</i>
   *
   * @param jobexecutioncontext (used to get the job duration)
   * @param errorMsg
   * @return subject
   */
  protected String getMailContent(final JobExecutionContext jobexecutioncontext, final String errorMsg)
  {
    String duration = formattedDuration(jobexecutioncontext.getJobRunTime());
    if (errorMsg != null)
    {
      String mailErrorTemplate = (String) localizer().valueForKey("COScheduler.DefaultMailErrorMessage");
      if (log.isDebugEnabled())
        log.debug("method: getMailContent: mailErrorTemplate: " + mailErrorTemplate);
      if (mailErrorTemplate == null)
      {
        log.warn("method: getMailContent: mailErrorTemplate is null but shouldn't be!!! / localizer: " + localizer());
        mailErrorTemplate = DEFAULT_MAIL_ERROR_MESSAGE_TEMPLATE;
      }
      return MessageFormat.format(mailErrorTemplate, errorMsg, duration);
    }
    String message = (String) jobexecutioncontext.getResult();

    if (ERXStringUtilities.stringIsNullOrEmpty(message))
    {
      String mailTemplate = (String) localizer().valueForKey("COScheduler.DefaultMailShortMessage");
      if (log.isDebugEnabled())
        log.debug("method: getMailContent: DefaultMailShortMessage: mailTemplate: " + mailTemplate);
      if (mailTemplate == null)
      {
        log.warn("method: getMailContent: DefaultMailShortMessage is null but shouldn't be!!! / localizer: " + localizer());
        mailTemplate = DEFAULT_MAIL_SHORT_MESSAGE_TEMPLATE;
      }
      message = MessageFormat.format(mailTemplate, duration);
    }
    else
    {
      String mailTemplate = (String) localizer().valueForKey("COScheduler.DefaultMailMessageWithMoreInfos");
      if (log.isDebugEnabled())
        log.debug("method: getMailContent: DefaultMailMessageWithMoreInfos: mailTemplate: " + mailTemplate);
      if (mailTemplate == null)
      {
        log.warn("method: getMailContent: DefaultMailMessageWithMoreInfos is null but shouldn't be!!! / localizer: " + localizer());
        mailTemplate = DEFAULT_MAIL_MESSAGE_WITH_MORE_INFOS_TEMPLATE;
      }
      message = MessageFormat.format(mailTemplate, message, duration);
    }
    return message;
  }
 
  /**
   * Sends an plain text email to:
   * <ul>
   * <li> the recipients passed as parameters.
   * <li> the email stored in the properties file (er.quartzscheduler.ERQSJobListener.to=myEmail@domain.com)
   * </ul>
   *
   * The author is read from properties file (er.quartzscheduler.ERQSJobListener.from=myOtherEmail@domain.com)<p>
   *
   * @throws IllegalStateException if from email is empty and the is no recipient at all.
   * @param subject
   * @param textContent
   * @param recipients
   */
  protected void sendMail(final String subject, final String textContent, final NSArray<String> recipients)
  {
    try
    {
      String fromEmail = ERXProperties.stringForKeyWithDefault("er.quartzscheduler.ERQSJobListener.from","");
      if (fromEmail.length() == 0 || recipients == null || recipients.size() == 0)
        throw new IllegalStateException("method: sendMail: fromEmail or toEmail are empty: fromEmail: " + fromEmail + " /recipients: " + recipients);

      ERMailDeliveryPlainText plainText = new ERMailDeliveryPlainText();
      plainText.newMail();
      plainText.setFromAddress(fromEmail);
      plainText.setToAddresses(recipients);
      plainText.setSubject(subject);
      plainText.setTextContent(textContent);
      plainText.sendMail(false);
    }
    catch (AddressException e)
    {
      log.error("Method: sendMail: ", e);
    }
    catch (MessagingException e)
    {
      log.error("Method: sendMail: ", e);
    }
  }

  /**
   * Return a string used by the logger and the mail sending method.<p>
   * If the duration is less than 180s, the duration is expressed in seconds otherwise there is a conversion in mn.
   *
   * @param duration
   * @return the formatted duration
   */
  protected String formattedDuration(final long duration) {
    long durationInMinute = 0;
    long durationInSecond = (duration)/1000; //in seconds

    if (durationInSecond > 180)
    {
      durationInMinute = durationInSecond / 60;
      durationInSecond = durationInSecond % 60;
    }
    return durationInMinute == 0 ? durationInSecond + "s" : (durationInMinute + "mn " + durationInSecond+"s");
  }

  /**
   * Utility method.
   *
   * @param date
   * @return the date in NSTimestamp format
   */
  protected NSTimestamp dateToNSTimestamp(final Date date)
  {
    if (date !=null )
      return new NSTimestamp(date);
    return null;
  }
 
  protected ERXLocalizer localizer()
  {
    String language = ERXProperties.stringForKey("er.quartzscheduler.ERQSJobListener.defaultLanguage");
    if (log.isDebugEnabled())
      log.debug("method: localizer: language: " + language);
    ERXLocalizer localizer;
    if (ERXStringUtilities.stringIsNullOrEmpty(language))
      localizer =  ERXLocalizer.defaultLocalizer();
    else
      localizer = ERXLocalizer.localizerForLanguage(language);
    if (log.isDebugEnabled())
      log.debug("method: localizer: localizer: " + localizer + " /localizer.language: " + localizer.language());
    return localizer;
  }
}
TOP

Related Classes of er.quartzscheduler.foundation.ERQSJobListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.