/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2009-2011 The OpenNMS Group, Inc.
* OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc.
*
* OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
*
* OpenNMS(R) 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 3 of the License,
* or (at your option) any later version.
*
* OpenNMS(R) 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 OpenNMS(R). If not, see:
* http://www.gnu.org/licenses/
*
* For more information contact:
* OpenNMS(R) Licensing <license@opennms.org>
* http://www.opennms.org/
* http://www.opennms.com/
*******************************************************************************/
package org.opennms.netmgt.ackd.readers;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.mail.Header;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Flags.Flag;
import javax.mail.internet.InternetAddress;
import org.opennms.core.utils.StringUtils;
import org.opennms.core.utils.ThreadCategory;
import org.opennms.javamail.JavaMailerException;
import org.opennms.javamail.JavaReadMailer;
import org.opennms.netmgt.config.ackd.Parameter;
import org.opennms.netmgt.config.javamail.ReadmailConfig;
import org.opennms.netmgt.dao.AckdConfigurationDao;
import org.opennms.netmgt.dao.JavaMailConfigurationDao;
import org.opennms.netmgt.model.AckAction;
import org.opennms.netmgt.model.AckType;
import org.opennms.netmgt.model.OnmsAcknowledgment;
import org.opennms.netmgt.model.acknowledgments.AckService;
/**
* This class uses the JavaMail API to connect to a mail store and retrieve messages, using
* the configured host and user details, and detects replies to notifications that have
* an acknowledgment action: acknowledge, unacknowledge, clear, escalate.
*
* @author <a href="mailto:david@opennms.org">David Hustace</a>
*
*/
class MailAckProcessor implements AckProcessor {
private static final int LOG_FIELD_WIDTH = 128;
private AckdConfigurationDao m_ackdDao;
private AckService m_ackService;
private volatile JavaMailConfigurationDao m_jmConfigDao;
/**
* <p>afterPropertiesSet</p>
*
* @throws java.lang.Exception if any.
*/
@Override
public void afterPropertiesSet() throws Exception {
}
private MailAckProcessor() {
}
/**
* Retrieve the messages in the configured mail folder, searches for notification replies,
* and creates and processes the acknowledgments.
*/
protected void findAndProcessAcks() {
log().debug("findAndProcessAcks: checking for acknowledgments...");
Collection<OnmsAcknowledgment> acks;
try {
List<Message> msgs = retrieveAckMessages(); //TODO: need a read *new* messages feature
acks = createAcks(msgs);
if (acks != null) {
log().debug("findAndProcessAcks: Found "+acks.size()+" acks. Processing...");
m_ackService.processAcks(acks);
log().debug("findAndProcessAcks: acks processed.");
}
} catch (JavaMailerException e) {
log().error("findAndProcessAcks: Exception thrown in JavaMail: "+e, e);
}
log().debug("findAndProcessAcks: completed checking for and processing acknowledgments.");
}
private static ThreadCategory log() {
return ThreadCategory.getInstance(MailAckProcessor.class);
}
//should probably be static
/**
* Creates <code>OnmsAcknowledgment</code>s for each notification reply email message determined
* to have an acknowledgment action.
*
* @param msgs a {@link java.util.List} object.
* @return a {@link java.util.List} object.
*/
protected List<OnmsAcknowledgment> createAcks(final List<Message> msgs) {
log().info("createAcks: Detecting and possibly creating acknowledgments from "+msgs.size()+" messages...");
List<OnmsAcknowledgment> acks = null;
if (msgs != null && msgs.size() > 0) {
acks = new ArrayList<OnmsAcknowledgment>();
Iterator<Message> it = msgs.iterator();
while (it.hasNext()) {
Message msg = (Message) it.next();
try {
log().debug("createAcks: detecting acks in message: "+msg.getSubject());
Integer id = detectId(msg.getSubject(), m_ackdDao.getConfig().getNotifyidMatchExpression());
if (id != null) {
final OnmsAcknowledgment ack = createAck(msg, id);
ack.setAckType(AckType.NOTIFICATION);
ack.setLog(createLog(msg));
acks.add(ack);
msg.setFlag(Flag.DELETED, true);
log().debug("createAcks: found notification acknowledgment: "+ack);
continue;
}
id = detectId(msg.getSubject(), m_ackdDao.getConfig().getAlarmidMatchExpression());
if (id != null) {
final OnmsAcknowledgment ack = createAck(msg, id);
ack.setAckType(AckType.ALARM);
ack.setLog(createLog(msg));
acks.add(ack);
msg.setFlag(Flag.DELETED, true);
log().debug("createAcks: found alarm acknowledgment."+ack);
continue;
}
} catch (MessagingException e) {
log().error("createAcks: messaging error: "+e);
} catch (IOException e) {
log().error("createAcks: IO problem: "+e);
}
}
} else {
log().debug("createAcks: No messages for acknowledgment processing.");
}
log().info("createAcks: Completed detecting and possibly creating acknowledgments. Created "+
(acks == null? 0 : acks.size())+" acknowledgments.");
return acks;
}
/**
* <p>detectId</p>
*
* @param subject a {@link java.lang.String} object.
* @param expression a {@link java.lang.String} object.
* @return a {@link java.lang.Integer} object.
*/
protected static Integer detectId(final String subject, final String expression) {
log().debug("detectId: Detecting aknowledgable ID from subject: "+subject+" using expression: "+expression);
Integer id = null;
//TODO: force opennms config '~' style regex attribute identity because this is the only way for this to work
String ackExpression = null;
if (expression.startsWith("~")) {
ackExpression = expression.substring(1);
} else {
ackExpression = expression;
}
Pattern pattern = Pattern.compile(ackExpression);
Matcher matcher = pattern.matcher(subject);
if (matcher.matches() && matcher.groupCount() > 0) {
id = Integer.valueOf(matcher.group(1));
log().debug("detectId: found acknowledgable ID: "+id);
} else {
log().debug("detectId: no acknowledgable ID found.");
}
return id;
}
/**
* <p>createAck</p>
*
* @param msg a {@link javax.mail.Message} object.
* @param refId a {@link java.lang.Integer} object.
* @return a {@link org.opennms.netmgt.model.OnmsAcknowledgment} object.
* @throws javax.mail.MessagingException if any.
* @throws java.io.IOException if any.
*/
protected OnmsAcknowledgment createAck(final Message msg, final Integer refId) throws MessagingException, IOException {
String ackUser = ((InternetAddress)msg.getFrom()[0]).getAddress();
Date ackTime = msg.getReceivedDate();
OnmsAcknowledgment ack = new OnmsAcknowledgment(ackTime, ackUser);
ack.setAckType(AckType.NOTIFICATION);
ack.setAckAction(determineAckAction(msg));
ack.setRefId(refId);
return ack;
}
/**
* <p>determineAckAction</p>
*
* @param msg a {@link javax.mail.Message} object.
* @return a {@link org.opennms.netmgt.model.AckAction} object.
* @throws java.io.IOException if any.
* @throws javax.mail.MessagingException if any.
*/
protected AckAction determineAckAction(final Message msg) throws IOException, MessagingException {
log().info("determineAckAcktion: evaluating message looking for user specified acktion...");
List<String> messageText = JavaReadMailer.getText(msg);
AckAction action = AckAction.UNSPECIFIED;
if (messageText != null && messageText.size() > 0) {
log().debug("determineAction: message text: "+messageText);
if (m_ackdDao.acknowledgmentMatch(messageText)) {
action = AckAction.ACKNOWLEDGE;
} else if (m_ackdDao.clearMatch(messageText)) {
action = AckAction.CLEAR;
} else if (m_ackdDao.escalationMatch(messageText)) {
action = AckAction.ESCALATE;
} else if (m_ackdDao.unAcknowledgmentMatch(messageText)) {
action = AckAction.UNACKNOWLEDGE;
} else {
action = AckAction.UNSPECIFIED;
}
} else {
String concern = "determineAckAction: a reply message to a notification has no text to evaluate. " +
"No action can be determined.";
log().warn(concern);
throw new MessagingException(concern);
}
log().info("determineAckAcktion: evaluated message, "+action+" action determined from message.");
return action;
}
/**
* <p>retrieveAckMessages</p>
*
* @return a {@link java.util.List} object.
* @throws org.opennms.javamail.JavaMailerException if any.
*/
protected List<Message> retrieveAckMessages() throws JavaMailerException {
log().debug("retrieveAckMessages: Retrieving messages...");
ReadmailConfig readMailConfig = determineMailReaderConfig();
log().debug("retrieveAckMessages: creating JavaReadMailer with config: " +
"host: " + readMailConfig.getReadmailHost().getHost() +
" port: " + readMailConfig.getReadmailHost().getPort() +
" ssl: " + readMailConfig.getReadmailHost().getReadmailProtocol().getSslEnable() +
" transport: " + readMailConfig.getReadmailHost().getReadmailProtocol().getTransport() +
" user: "+readMailConfig.getUserAuth().getUserName() +
" password: "+readMailConfig.getUserAuth().getPassword());
//TODO: make flag for folder open mode
//TODO: Make sure configuration supports flag for deleting acknowledgments
JavaReadMailer readMailer = new JavaReadMailer(readMailConfig, true);
String notifRe = m_ackdDao.getConfig().getNotifyidMatchExpression();
notifRe = notifRe.startsWith("~") ? notifRe.substring(1) : notifRe;
String alarmRe = m_ackdDao.getConfig().getAlarmidMatchExpression();
alarmRe = alarmRe.startsWith("~") ? alarmRe.substring(1) : alarmRe;
Pattern notifPattern = Pattern.compile(notifRe);
Pattern alarmPattern = Pattern.compile(alarmRe);
List<Message> msgs = readMailer.retrieveMessages();
log().info("retrieveAckMessages: Iterating "+msgs.size()+" messages with notif expression: "+notifRe+
" and alarm expression: "+alarmRe);
for (Iterator<Message> iterator = msgs.iterator(); iterator.hasNext();) {
Message msg = iterator.next();
try {
String subject = msg.getSubject();
Matcher alarmMatcher = alarmPattern.matcher(subject);
Matcher notifMatcher = notifPattern.matcher(subject);
log().debug("retrieveAckMessages: comparing the subject: "+subject);
if (!(notifMatcher.matches() || alarmMatcher.matches())) {
log().debug("retrieveAckMessages: Subject doesn't match either expression.");
iterator.remove();
} else {
//TODO: this just looks wrong
//delete this non-ack message because the acks will get deleted later and the config
//indicates delete all mail from mailbox
log().debug("retrieveAckMessages: Subject matched, setting deleted flag");
if (readMailConfig.isDeleteAllMail()) {
msg.setFlag(Flag.DELETED, true);
}
}
} catch (Throwable t) {
log().error("retrieveAckMessages: Problem processing message: "+t);
}
}
return msgs;
}
@SuppressWarnings("unchecked")
private static String createLog(final Message msg) {
StringBuilder bldr = new StringBuilder();
Enumeration<Header> allHeaders;
try {
allHeaders = msg.getAllHeaders();
} catch (MessagingException e) {
return null;
}
while (allHeaders.hasMoreElements()) {
Header header = allHeaders.nextElement();
String name = header.getName();
String value = header.getValue();
bldr.append(name);
bldr.append(":");
bldr.append(value);
bldr.append("\n");
}
return StringUtils.truncate(bldr.toString(), LOG_FIELD_WIDTH);
}
/**
* <p>run</p>
*/
public void run() {
try {
log().info("run: Processing mail acknowledgments (opposed to femail acks ;)..." );
findAndProcessAcks();
log().info("run: Finished processing mail acknowledgments." );
} catch (Throwable e) {
log().debug("run: threw exception: "+e, e);
} finally {
log().debug("run: method completed.");
}
}
/**
* <p>determineMailReaderConfig</p>
*
* @return a {@link org.opennms.netmgt.config.javamail.ReadmailConfig} object.
*/
public ReadmailConfig determineMailReaderConfig() {
log().info("determineMailReaderConfig: determining mail reader configuration...");
List<Parameter> parms = m_ackdDao.getParametersForReader("JavaMailReader");
ReadmailConfig config = m_jmConfigDao.getDefaultReadmailConfig();
for (Parameter parameter : parms) {
if ("readmail-config".equalsIgnoreCase(parameter.getKey())) {
config = m_jmConfigDao.getReadMailConfig(parameter.getValue());
}
}
log().info("determinedMailReaderConfig: "+config);
return config;
}
/**
* <p>setAckdConfigDao</p>
*
* @param configDao a {@link org.opennms.netmgt.dao.AckdConfigurationDao} object.
*/
public synchronized void setAckdConfigDao(final AckdConfigurationDao configDao) {
m_ackdDao = configDao;
}
/**
* <p>setAckService</p>
*
* @param ackService a {@link org.opennms.netmgt.model.acknowledgments.AckService} object.
*/
public synchronized void setAckService(final AckService ackService) {
m_ackService = ackService;
}
/**
* <p>reloadConfigs</p>
*/
public synchronized void reloadConfigs() {
log().debug("reloadConfigs: lock acquired; reloading configuration...");
m_jmConfigDao.reloadConfiguration();
log().debug("reloadConfigs: configuration reloaded");
}
/**
* <p>getJmConfigDao</p>
*
* @return a {@link org.opennms.netmgt.dao.JavaMailConfigurationDao} object.
*/
protected JavaMailConfigurationDao getJmConfigDao() {
return m_jmConfigDao;
}
/**
* <p>setJmConfigDao</p>
*
* @param jmConfigDao a {@link org.opennms.netmgt.dao.JavaMailConfigurationDao} object.
*/
public void setJmConfigDao(final JavaMailConfigurationDao jmConfigDao) {
m_jmConfigDao = jmConfigDao;
}
}