net.solarnetwork.central.dras.biz.alert.SimpleAlertBiz.java Source code

Java tutorial

Introduction

Here is the source code for net.solarnetwork.central.dras.biz.alert.SimpleAlertBiz.java

Source

/* ==================================================================
 * SimpleAlertBiz.java - Jun 18, 2011 7:42:39 PM
 * 
 * Copyright 2007-2011 SolarNetwork.net Dev Team
 * 
 * This program is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU General Public License as 
 * published by the Free Software Foundation; either version 2 of 
 * the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 
 * 02111-1307 USA
 * ==================================================================
 * $Id$
 * ==================================================================
 */

package net.solarnetwork.central.dras.biz.alert;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import net.solarnetwork.central.domain.FilterResults;
import net.solarnetwork.central.domain.Identity;
import net.solarnetwork.central.dras.biz.AlertBiz;
import net.solarnetwork.central.dras.dao.EventDao;
import net.solarnetwork.central.dras.dao.OutboundMailDao;
import net.solarnetwork.central.dras.dao.ProgramDao;
import net.solarnetwork.central.dras.dao.UserDao;
import net.solarnetwork.central.dras.domain.Event;
import net.solarnetwork.central.dras.domain.Match;
import net.solarnetwork.central.dras.domain.Member;
import net.solarnetwork.central.dras.domain.OutboundMail;
import net.solarnetwork.central.dras.domain.User;
import net.solarnetwork.central.dras.domain.UserContact;
import net.solarnetwork.central.dras.support.SimpleAlertProcessingResult;
import net.solarnetwork.central.dras.support.SimpleUserFilter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

/**
 * Default implementation of {@link AlertBiz}.
 * 
 * @author matt
 * @version $Revision$
 */
@Service
public class SimpleAlertBiz implements AlertBiz {

    @Autowired
    private EventDao eventDao;
    @Autowired
    private OutboundMailDao outboundMailDao;
    @Autowired
    private UserDao userDao;
    @Autowired
    private ProgramDao programDao;
    @Autowired
    private MailSender mailSender;

    @Autowired(required = false)
    private ExecutorService processor = Executors.newSingleThreadExecutor();

    @Autowired(required = false)
    private TransactionTemplate transactionTemplate;

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Override
    public Future<AlertProcessingResult> postAlert(Alert alert) {
        log.info("Alert received: {}", alert.getAlertType());
        AlertRunner runner = new AlertRunner(alert);
        return processor.submit(runner);
    }

    /**
     * Shutdown the thread processing alerts.
     * 
     * @param secs the maximum number of seconds to wait
     */
    public void shutdown(long secs) {
        processor.shutdown();
        try {
            processor.awaitTermination(secs, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.debug("Interrupted waiting for ExecutorService to shut down");
        }
    }

    // internal method called on background thread
    private void handleAlert(Alert alert, SimpleAlertProcessingResult result, Long creatorId) {
        Identity<Long> regarding = alert.getRegardingIdentity();
        Set<Identity<Long>> alerted = new LinkedHashSet<Identity<Long>>();
        if (regarding instanceof Event) {
            handleEventAlert(alert, (Event) regarding, alerted, creatorId);
        }
        result.setAlertedUsers(alerted);
    }

    // TODO: need localized message template support
    private static final String DEFAULT_EVENT_CREATED_MESSAGE_TEMPLATE = "Event ID %d (%s) created. Event date: %ta, %<te %<tb %<tY, %<tR %<tZ";

    private static final String DEFAULT_EVENT_MODIFIED_MESSAGE_TEMPLATE = "Event ID %d (%s) modified. Event date: %ta, %<te %<tb %<tY, %<tR %<tZ";

    private static final String DEFAULT_EVENT_UNKNOWN_MESSAGE_TEMPLATE = "Event ID %d (%s) alert: %s";

    private static final String DEFAULT_EVENT_CREATED_SUBJECT_TEMPLATE = "SolarADR event ID %d (%s) created";

    private static final String DEFAULT_EVENT_MODIFIED_SUBJECT_TEMPLATE = "SolarADR event ID %d (%s) modified";

    private static final String DEFAULT_EVENT_UNKNOWN_SUBJECT_TEMPLATE = "SolarADR event ID %d (%s) alert";

    private void handleEventAlert(final Alert alert, final Event event, final Set<Identity<Long>> alerted,
            final Long creatorId) {
        Set<Member> users;
        if (AlertBiz.ALERT_TYPE_ENTITY_CREATED.equals(alert.getAlertType())) {
            // when creating, send alert to all users in program
            users = programDao.resolveUserMembers(event.getProgramId(), null);
        } else {
            // get all users owning participants of given event, send alert to each one
            users = eventDao.resolveUserMembers(event.getId(), null);
        }
        for (Member m : users) {
            User user = userDao.get(m.getId());
            String message = null;
            String subject = null;
            String eventName = event.getName() == null ? "Unnamed" : event.getName();
            if (AlertBiz.ALERT_TYPE_ENTITY_CREATED.equals(alert.getAlertType())) {
                subject = String.format(DEFAULT_EVENT_CREATED_SUBJECT_TEMPLATE, event.getId(), eventName);
                message = String.format(DEFAULT_EVENT_CREATED_MESSAGE_TEMPLATE, event.getId(), eventName,
                        event.getEventDate().toDate());
            } else if (AlertBiz.ALERT_TYPE_ENTITY_MODIFIED.equals(alert.getAlertType())) {
                subject = String.format(DEFAULT_EVENT_MODIFIED_SUBJECT_TEMPLATE, event.getId(), eventName);
                message = String.format(DEFAULT_EVENT_MODIFIED_MESSAGE_TEMPLATE, event.getId(), eventName,
                        event.getEventDate().toDate());
            } else {
                // unknown type
                subject = String.format(DEFAULT_EVENT_UNKNOWN_SUBJECT_TEMPLATE, event.getId(), eventName);
                message = String.format(DEFAULT_EVENT_UNKNOWN_MESSAGE_TEMPLATE, event.getId(), event.getName(),
                        alert.getAlertType());
            }
            if (handleAlert(user, alert, subject, message, creatorId)) {
                alerted.add(user);
            }
        }
    }

    private boolean handleAlert(final User user, final Alert alert, final String subject, final String message,
            final Long creatorId) {
        List<UserContact> contacts = user.getContactInfo();
        if (contacts == null) {
            return false;
        }

        // get the user's preferred contact method
        UserContact contact = null;
        for (UserContact aContact : contacts) {
            if (aContact.getPriority() == null) {
                continue;
            }
            if (contact == null || (aContact.getPriority() < contact.getPriority())) {
                contact = aContact;
            }
        }
        if (contact == null) {
            log.debug("User {} has no preferred contact method, not sending alert {}", user.getUsername(),
                    alert.getAlertType());
            return false;
        }

        // send the user an alert... only email supported currently
        SimpleMailMessage msg = new SimpleMailMessage();
        msg.setTo(contact.getContact());
        msg.setText(message);
        switch (contact.getKind()) {
        case MOBILE:
            // treat as an email to their mobile number
            // TODO: extract out mobile SMS handling to configurable service
            msg.setTo(contact.getContact().replaceAll("\\D", "") + "@isms.net.nz");
            msg.setFrom("escalation@econz.co.nz");

            break;

        case EMAIL:
            msg.setSubject(subject);
            msg.setFrom("solar-adr@solarnetwork.net");
            break;

        default:
            log.debug("User {} contact type {} not supported in alerts", user.getUsername(), contact.getKind());
            return false;
        }
        sendMailMessage(msg, creatorId);
        return true;
    }

    private void sendMailMessage(final SimpleMailMessage msg, final Long creatorId) {
        OutboundMail out = new OutboundMail();
        out.setCreator(creatorId);
        out.setMessageBody(msg.getText());
        out.setTo(msg.getTo());
        sendMailMessage(msg, out);
    }

    private void sendMailMessage(SimpleMailMessage msg, OutboundMail out) {
        mailSender.send(msg);
        Long id = outboundMailDao.store(out);
        log.debug("Saved OutboundMail {} to {}", id, out.getToAddress());
    }

    private Long getCurrentUserId() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null) {
            log.info("No Authentication available, cannot tell current user ID");
            return null;
        }
        String currentUserName = auth.getName();
        SimpleUserFilter filter = new SimpleUserFilter();
        filter.setUniqueId(currentUserName);
        FilterResults<Match> results = userDao.findFiltered(filter, null, null, null);
        if (results.getReturnedResultCount() < 1) {
            return null;
        }
        return results.getResults().iterator().next().getId();
    }

    /**
     * Simple Runnable to off load event processing to another thread.
     */
    private class AlertRunner implements Callable<AlertProcessingResult> {

        private final Alert alert;
        private final Long userId;

        private AlertRunner(Alert alert) {
            this.alert = alert;
            userId = (alert.getActorId() != null ? alert.getActorId() : getCurrentUserId());
        }

        @Override
        public AlertProcessingResult call() {
            final SimpleAlertProcessingResult result = new SimpleAlertProcessingResult(alert);
            if (transactionTemplate != null) {
                transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus status) {
                        handleAlert(alert, result, userId);
                    }
                });
            } else {
                handleAlert(alert, result, userId);
            }
            return result;
        }

    }

    public void setEventDao(EventDao eventDao) {
        this.eventDao = eventDao;
    }

    public void setOutboundMailDao(OutboundMailDao outboundMailDao) {
        this.outboundMailDao = outboundMailDao;
    }

    public void setProgramDao(ProgramDao programDao) {
        this.programDao = programDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void setMailSender(MailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }

    public void setProcessor(ExecutorService processor) {
        this.processor = processor;
    }

}