edu.duke.cabig.c3pr.domain.scheduler.runtime.job.CCTSNotificationMessageJob.java Source code

Java tutorial

Introduction

Here is the source code for edu.duke.cabig.c3pr.domain.scheduler.runtime.job.CCTSNotificationMessageJob.java

Source

/*******************************************************************************
 * Copyright Duke Comprehensive Cancer Center and SemanticBits
 * 
 * Distributed under the OSI-approved BSD 3-Clause License.
 * See http://ncip.github.com/c3pr/LICENSE.txt for details.
 ******************************************************************************/
/**
 * 
 */
package edu.duke.cabig.c3pr.domain.scheduler.runtime.job;

import java.io.CharArrayWriter;
import java.io.Serializable;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.Vector;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;

import org.apache.commons.lang.SerializationUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.log4j.Logger;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.StatefulJob;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;

import edu.duke.cabig.c3pr.esb.BroadcastException;
import edu.duke.cabig.c3pr.esb.CCTSMessageBroadcaster;
import edu.duke.cabig.c3pr.esb.ESBCommunicationException;
import edu.duke.cabig.c3pr.esb.Metadata;
import edu.duke.cabig.c3pr.esb.OperationNameEnum;
import gme.ccts_cabig._1_0.gov_nih_nci_cabig_ccts_domain.notifications.Application;
import gme.ccts_cabig._1_0.gov_nih_nci_cabig_ccts_domain.notifications.BusinessObjectID;
import gme.ccts_cabig._1_0.gov_nih_nci_cabig_ccts_domain.notifications.EventType;
import gme.ccts_cabig._1_0.gov_nih_nci_cabig_ccts_domain.notifications.Notification;

/**
 * We want this job to persist {@link JobDataMap} after each invocation (because
 * the map contains message queue) and to not run concurrently. Hence,
 * {@link StatefulJob}. <br>
 * <br>
 * The code in this class is written under the assumption that this job won't
 * run concurrently, since it's a {@link StatefulJob}, a singleton, and there is
 * only one {@link Scheduler}. However, if this assumption is somehow becomes
 * invalid, duplicate notification messages may be sent to iHub. <br>
 * <br>
 * The reason for not running this job concurrently is that we need ordered
 * delivery of messages to iHub.
 * 
 * @author Denis G. Krylov
 * 
 */
public final class CCTSNotificationMessageJob implements StatefulJob {

    public static final String IHUB_SERVICE_NAME = "NOTIFICATION_BROADCAST";
    public static final String CCTS_NOTIFICATIONS_NS = "gme://ccts.cabig/1.0/gov.nih.nci.cabig.ccts.domain/Notifications";
    private static final String CCTS_NOTIFICATIONS_QUEUE_KEY = edu.duke.cabig.c3pr.service.impl.SchedulerServiceImpl.CCTS_NOTIFICATIONS_QUEUE_KEY;
    private static final Logger log = Logger.getLogger(CCTSNotificationMessageJob.class);
    private static final long NOTIFICATION_TTL = DateUtils.MILLIS_PER_DAY * 7;

    /**
     * CCTSMessageBroadcaster. This cannot be DI-ed by Spring, unfortunately.
     */
    private CCTSMessageBroadcaster cctsMessageBroadcaster;

    /*
     * (non-Javadoc)
     * 
     * @see org.quartz.Job#execute(org.quartz.JobExecutionContext)
     */
    public void execute(JobExecutionContext context) throws JobExecutionException {
        log.debug("Starting execution of the CCTSNotificationMessageJob");
        setBroadcasterInstance(context);

        final JobDetail jobDetail = context.getJobDetail();
        Vector<CCTSNotification> queue;
        try {
            queue = (Vector<CCTSNotification>) jobDetail.getJobDataMap().get(CCTS_NOTIFICATIONS_QUEUE_KEY);
        } catch (ClassCastException e) {
            log.error(
                    "Queue implementation has changed. Indicates a programming error. Job cancelled. An attempt to re-create the job will be made again once a ccts event occurs.");
            JobExecutionException ex = new JobExecutionException(false);
            ex.setUnscheduleAllTriggers(true);
            ex.setUnscheduleFiringTrigger(true);
            throw ex;
        }

        if (queue != null) {
            try {
                process(queue);
            } finally {
                jobDetail.getJobDataMap().put(CCTS_NOTIFICATIONS_QUEUE_KEY, queue);
            }
        } else {
            log.error(
                    "Unexpected null queue: CCTS_NOTIFICATIONS_QUEUE_KEY. Indicates a programming error. Job cancelled. An attempt to re-create the job will be made again once a ccts event occurs.");
            JobExecutionException ex = new JobExecutionException(false);
            ex.setUnscheduleAllTriggers(true);
            ex.setUnscheduleFiringTrigger(true);
            throw ex;
        }
        log.debug("Ended execution of the CCTSNotificationMessageJob");
    }

    /**
     * @param context
     * @throws JobExecutionException
     * @throws SchedulerException
     * @throws BeansException
     */
    private void setBroadcasterInstance(JobExecutionContext context) throws JobExecutionException {
        try {
            final Scheduler scheduler = context.getScheduler();
            ApplicationContext applicationContext = (ApplicationContext) scheduler.getContext()
                    .get("applicationContext");
            this.cctsMessageBroadcaster = (CCTSMessageBroadcaster) applicationContext
                    .getBean("cctsNotificationsBroadcaster");
        } catch (Exception e) {
            log.error(
                    "Unable to retrieve cctsNotificationsBroadcaster instance. Indicates a configuration error. CCTS notifications are disabled!");
            JobExecutionException ex = new JobExecutionException(false);
            throw ex;
        }
    }

    /**
     * @param queue
     */
    private void process(final Vector<CCTSNotification> queue) {
        boolean done;
        do {
            done = true;
            CCTSNotification notification = null;
            try {
                notification = queue.firstElement();
            } catch (NoSuchElementException e) {
            }
            if (notification == null) {
                log.debug("CCTS notification queue is empty; messages, if any, have been processed.");
            } else
                try {
                    process(notification);
                    queue.remove(notification);
                    done = false;
                } catch (ESBCommunicationException e) {
                    log.error(ExceptionUtils.getFullStackTrace(e));
                    log.error("Processing of a CCTS notification failed: " + notification);
                    log.error("Failed notification will be put back into the queue for another re-try later.");
                } catch (Exception e) {
                    log.error(ExceptionUtils.getFullStackTrace(e));
                    log.error("Processing of a CCTS notification failed: " + notification);
                    log.error(
                            "This error indicates a problem with the message itself. It will be removed from the queue. No further delivery attempts will be made.");
                    queue.remove(notification);
                    done = false;
                }
        } while (!done);
    }

    private void process(CCTSNotification notification) throws BroadcastException {
        if (notification.getTimestamp() != null
                && new Date().getTime() - notification.getTimestamp().getTime() > NOTIFICATION_TTL) {
            log.error("CCTS notification has been in queue for too long and has expired: " + notification);
            return;
        }
        Notification jaxbObj = convert(notification);
        String xml = serialize(jaxbObj);
        log.debug("XML message to be sent to iHub: " + xml);
        Metadata metadata = new Metadata();
        metadata.setOperationName(OperationNameEnum.NA.getName());
        metadata.setServiceType(IHUB_SERVICE_NAME);
        metadata.setExternalIdentifier(notification.getExternalId());
        cctsMessageBroadcaster.broadcast(xml, metadata);
    }

    private String serialize(Notification notification) {
        try {
            JAXBContext context = JAXBContext.newInstance(Notification.class);
            Marshaller m = context.createMarshaller();
            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            JAXBElement<Notification> jaxbElement = new JAXBElement<Notification>(
                    new QName(CCTS_NOTIFICATIONS_NS, "notification"), Notification.class, notification);
            CharArrayWriter writer = new CharArrayWriter();
            m.marshal(jaxbElement, writer);
            return String.valueOf(writer.toCharArray());
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }

    }

    private Notification convert(CCTSNotification notification) {
        try {
            Notification jaxbNotification = new Notification();
            jaxbNotification.setApplication(Application.C_3_PR);
            jaxbNotification.setEventType(EventType.fromValue(notification.getEventType()));
            if (notification.getTimestamp() != null) {
                GregorianCalendar gc = new GregorianCalendar();
                gc.setTime(notification.getTimestamp());
                DatatypeFactory df = DatatypeFactory.newInstance();
                XMLGregorianCalendar xmlDate = df.newXMLGregorianCalendar(gc);
                jaxbNotification.setTimestamp(xmlDate);
            }
            BusinessObjectID businessObjectID = new BusinessObjectID();
            businessObjectID.setValue(notification.getIdentifierValue());
            businessObjectID.setType(notification.getIdentifierType());
            jaxbNotification.setObjectId(businessObjectID);

            return jaxbNotification;
        } catch (DatatypeConfigurationException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * Lightweight class that stores CCTS notification details. The reason for
     * using this class, and not using {@link Notification} directly, is because
     * this class is lighter (it does need to serialize into database), less
     * volatile, and serializable ({@link Notification} is not serializable by
     * default).
     * 
     * @author Denis G. Krylov
     * 
     */
    public static final class CCTSNotification implements Serializable {

        /**
         * 
         */
        private static final long serialVersionUID = -3180003573480810064L;

        public CCTSNotification() {
        }

        private String identifierValue;
        private String eventType;
        private String identifierType;
        private Date timestamp = new Date();
        private final String externalId = UUID.randomUUID().toString();

        /**
         * @return the identifierValue
         */
        public final String getIdentifierValue() {
            return identifierValue;
        }

        /**
         * @param identifierValue
         *            the identifierValue to set
         */
        public final void setIdentifierValue(String identifierValue) {
            this.identifierValue = identifierValue;
        }

        /**
         * @return the eventType
         */
        public final String getEventType() {
            return eventType;
        }

        /**
         * @param eventType
         *            the eventType to set
         */
        public final void setEventType(String eventType) {
            this.eventType = eventType;
        }

        /**
         * @return the identifierType
         */
        public final String getIdentifierType() {
            return identifierType;
        }

        /**
         * @param identifierType
         *            the identifierType to set
         */
        public final void setIdentifierType(String identifierType) {
            this.identifierType = identifierType;
        }

        /**
         * @return the timestamp
         */
        public final Date getTimestamp() {
            return timestamp;
        }

        /**
         * @param timestamp
         *            the timestamp to set
         */
        public final void setTimestamp(Date timestamp) {
            this.timestamp = timestamp;
        }

        public String getExternalId() {
            return externalId;
        };

        /*
         * (non-Javadoc)
         * 
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return "CCTSNotification [identifierValue=" + identifierValue + ", eventType=" + eventType
                    + ", identifierType=" + identifierType + ", timestamp=" + timestamp + ", externalId="
                    + externalId + "]";
        }

    }

    /**
     * @return the cctsMessageBroadcaster
     */
    public final CCTSMessageBroadcaster getCctsMessageBroadcaster() {
        return cctsMessageBroadcaster;
    }

    public static void main(String[] args) {
        Vector v = new Vector();
        v.add(new CCTSNotification());
        System.out.println(v);
        byte[] b = SerializationUtils.serialize(v);
        Vector v2 = (Vector) SerializationUtils.deserialize(b);
        System.out.println(v2);
    }

}