Timed job notifier

Overview

The GroovyTimedExecutor executable contains the Groovy code that will be fired according to the interval setting, and it will check if there are any jobs that went into stalled state since the previous execution and send an email for each of those. The job name and last_start_date are saved in the handledJobs global user variable to prevent resending the same notification for the same job execution.

Groovy code

The following code shows an example of how the executable can be used so that the email/SMTP configuration can be localized to the DIH server. This example sends an email for every job that goes into a CHAIN_STALLED state, which typically means that it encountered an error and needs to be corrected and restarted, but the Groovy code can be modified to handle any condition and send out any notification.

import javax.mail.BodyPart
import javax.mail.Message
import javax.mail.Multipart
import javax.mail.Session
import javax.mail.Transport
import javax.mail.internet.InternetAddress
import javax.mail.internet.MimeBodyPart
import javax.mail.internet.MimeMessage
import javax.mail.internet.MimeMultipart    

import com.jcabi.jdbc.SetOutcome    
import com.ria.core.db.Transaction    
import com.ria.converter.shared.context.MailProperties    

class JobNotifier {    

    def execute() {    
        // Select all jobs in CHAIN_STALLED status
        Set<String> stalledJobs = Transaction.getSession()
            .sql("select job_name, to_char(last_start_date,'yyyymmddhh24misssss') from user_scheduler_jobs where state = 'CHAIN_STALLED'")
            .select(new SetOutcome<>(rs -> rs.getString(1) + '~' + rs.getString(2)))

        // Get/create map of previously handled jobs
        // NOTE: handledJobs is a global user variable that will be saved between executions of this method
        Set<String> handledJobs = TimedExecutorAction.getUserVariable('handledJobs')
        if (handledJobs == null) {
            handledJobs = new HashSet()
        }

        // Check if any of the previous jobs are no longer stalled and remove them from the stored set
        Set<String> tempJobSet = new HashSet(handledJobs)
        for (String jobEntry : tempJobSet) {
            if (!stalledJobs.contains(jobEntry)) {
                handledJobs.remove(jobEntry)
            }
        }

        // For each job, if a notification has not been sent, send it now and mark it as sent
        for (String stalledJob in stalledJobs) {
            // Enable log4j2 debugging on class com.ria.converter.api.groovy.CommonEnv to see the debug messages
            CommonEnv.logDebug("Found ${stalledJob}")
            if (!handledJobs.contains(stalledJob)) {
                handledJobs.add(stalledJob)
                sendEmail(stalledJob)
            }
        }

        TimedExecutorAction.updateExecutorMessage(
            "Job notifier executed at ${CommonEnv.getCurrentDateTime()}"
        )
        TimedExecutorAction.setUserVariable('handledJobs', handledJobs)
    }

    def sendEmail(String jobAndTime) {

        CommonEnv.logDebug("Attempting to send email notification for ${jobAndTime}")

        String[] jt = jobAndTime.split('~')
        String jobName = jt[0]
        String jobTime = jt[1]

        // NOTE: MODIFY the following as required
        String from = 'noreply@nt.com'
        String sendTo = 'user1@nt.com,user2@nt.com'
        String subject = "Job ${jobName} ended in error"
        String message = "Job ${jobName} ended in error. Please correct the problem and restart the job."

        CommonEnv.logInfo(
            "Sending email notification for job ${jobName} with last start date ${jobTime}"
        )

        Session session = Session.getInstance(
            MailProperties.getProperties(),
            MailProperties.getAuthenticator()
        )

        MimeMessage msg = new MimeMessage(session)
        msg.setFrom(new InternetAddress(from))
        sendTo.split(',').each { item ->
            msg.addRecipient(Message.RecipientType.TO, new InternetAddress(item))
        }
        msg.setSubject(subject)

        BodyPart bodyPart = new MimeBodyPart()
        bodyPart.setContent(message, "text/html")

        Multipart multipart = new MimeMultipart()
        multipart.addBodyPart(bodyPart)

        msg.setContent(multipart)
        Transport.send(msg)
    }
}

SMTP configuration

In the Groovy code provided, the sendEmail method is initialized by establishing a connection to the SMTP server using the following statement:

Session session = Session.getInstance(MailProperties.getProperties(), MailProperties.getAuthenticator())

The SMTP properties needed for this connection are sourced from the com.ria.mail properties defined in the ria-boot.properties file. This setup ensures that all mail-related configurations are systematically organized. The tool recognizes any property that begins with com.ria.mail as a JavaMail property. For more information, see Create email server for notifications.

Examples

Executable

Task