/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2006-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.archive; import java.beans.PropertyVetoException; import java.io.IOException; import java.lang.reflect.UndeclaredThrowableException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import org.apache.log4j.Category; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.exolab.castor.xml.MarshalException; import org.exolab.castor.xml.ValidationException; import org.opennms.core.utils.ThreadCategory; import org.opennms.core.utils.TimeConverter; import org.opennms.netmgt.config.DataSourceFactory; import org.opennms.netmgt.config.EventsArchiverConfigFactory; /** * <pre> * * The EventsArchiver is responsible for archiving and removing events * from the 'events' table. * * The archival/deletion depends on the 'eventLog' and the 'eventDisplay' * values for the event - * * If the 'eventLog == N and the eventDisplay == N', * the event is simply deleted from the events table * * If the 'eventLog == N and the eventDisplay == Y', * the event is deleted ONLY if the event has been acknowledged * * If the 'eventLog == Y and the eventDisplay == N', * the event is sent to the archive file and deleted from the table * * If the 'eventLog == Y and the eventDisplay == Y', * the event is sent to the archive file and deleted from the table * ONLY if the event has been acknowledged * * An event is considered acknowledged if the 'eventAckUser' column has * a non-null value * * An EventsArchiver run depends on attributes in the events archiver * configuration file. The following are the properties that govern a run - * * - archiveAge * This determines which events are to be removed - i.e events older * than current time minus this time are removed * * - separator * This is the separator used in between event table column values when an * event is written to the archive file * * The EventsArchiver uses Apache's log4j both for its output logs and for * the actual archiving itself - the set up for the log4j appenders for * this archiver are all doneexclusively in the 'events.archiver.properties' * property file * * A {@link org.apache.log4j.RollingFileAppender RollingFileAppender} is used * for the archive file with the defaults for this being to roll when the * size is 100KB with the number of backups set to 4. * * @author <A HREF="mailto:sowmya@opennms.org">Sowmya Nataraj</A> * @author <A HREF="http://www.opennms.org">OpenNMS</A> * @author <A HREF="mailto:sowmya@opennms.org">Sowmya Nataraj</A> * @author <A HREF="http://www.opennms.org">OpenNMS</A> * @version $Id: $ */ public class EventsArchiver { /** * The SQL statement to select events that have their eventCreateTime * earlier than a specified age */ private static final String DB_SELECT_EVENTS_TO_ARCHIVE = "SELECT * " + "FROM events " + "WHERE (eventcreatetime < ?)"; /** * The SQL statement to delete events based on their eventID */ private static final String DB_DELETE_EVENT = "DELETE " + "FROM events " + "WHERE (eventID = ?)"; /** * The column name for eventID in the events table */ private static final String EVENT_ID = "eventID"; /** * The column name for 'eventLog' in the events table */ private static final String EVENT_LOG = "eventLog"; /** * The column name for 'eventDisplay' in the events table */ private static final String EVENT_DISPLAY = "eventDisplay"; /** * The column name for 'eventAckUser' in the events table */ private static final String EVENT_ACK_USER = "eventAckUser"; /** * The value for the event log or display field if set to true */ private static final String MSG_YES = "Y"; /** * The value for the event log or display field if set to false */ private static final String MSG_NO = "N"; /** * The log4j category for this class' output logs */ private ThreadCategory m_logCat; /** * The log4j category for the archive file */ private Category m_archCat; /** * The archive age in milliseconds. Events created before this are * archived/deleted. */ private long m_archAge; /** * The separator to be used when writing events into the archive */ private String m_archSeparator; /** * The database connection */ private Connection m_conn; /** * The prepared statement to select the events */ private PreparedStatement m_eventsGetStmt; /** * The prepared statement to delete the events */ private PreparedStatement m_eventDeleteStmt; /** * Read the required properties and set up the logs, the archive etc. * @throws ArchiverException Thrown if a required property is not specified or is incorrect */ private void init() throws ArchiverException { String oldPrefix = ThreadCategory.getPrefix(); try { // The general logs from the events archiver go to this category ThreadCategory.setPrefix("OpenNMS.Archiver.Events"); m_logCat = ThreadCategory.getInstance(this.getClass()); // The archive logs go to this category m_archCat = Logger.getLogger("EventsArchiver"); /* * Set additivity for this to false so that logs from here * do not go to the root category. */ m_archCat.setAdditivity(false); EventsArchiverConfigFactory eaFactory; try { EventsArchiverConfigFactory.init(); eaFactory = EventsArchiverConfigFactory.getInstance(); } catch (MarshalException ex) { m_logCat.fatal("MarshalException", ex); throw new UndeclaredThrowableException(ex); } catch (ValidationException ex) { m_logCat.fatal("ValidationException", ex); throw new UndeclaredThrowableException(ex); } catch (IOException ex) { m_logCat.fatal("IOException", ex); throw new UndeclaredThrowableException(ex); } // get archive age String archAgeStr = eaFactory.getArchiveAge(); long archAge; try { archAge = TimeConverter.convertToMillis(archAgeStr); } catch (NumberFormatException nfe) { throw new ArchiverException("Archive age: " + archAgeStr + "- Incorrect format " + nfe.getMessage()); } /* * Set actual time that is to be used for the select from the * database. */ m_archAge = System.currentTimeMillis() - archAge; // get the separator to be used between column names in the archive String separator = eaFactory.getSeparator(); if (separator == null) { m_archSeparator = "#"; } else { m_archSeparator = separator; } // info logs if (m_logCat.isInfoEnabled()) { // get this in readable format archAgeStr = new java.util.Date(m_archAge).toString(); m_logCat.info("Events archive age specified = " + archAgeStr); m_logCat.info("Events archive age in millisconds = " + archAge); m_logCat.info("Events created before \'" + archAgeStr + " \' will be deleted"); m_logCat.info("Separator to be used in archive: " + m_archSeparator); } // Make sure we can connect to the database try { DataSourceFactory.init(); m_conn = DataSourceFactory.getInstance().getConnection(); } catch (IOException e) { m_logCat.fatal("IOException while initializing database", e); throw new UndeclaredThrowableException(e); } catch (MarshalException e) { m_logCat.fatal("MarshalException while initializing database", e); throw new UndeclaredThrowableException(e); } catch (ValidationException e) { m_logCat.fatal("ValidationException while initializing database", e); throw new UndeclaredThrowableException(e); } catch (PropertyVetoException e) { m_logCat.fatal("PropertyVetoException while initializing database", e); throw new UndeclaredThrowableException(e); } catch (SQLException e) { m_logCat.fatal("SQLException while initializing database", e); throw new UndeclaredThrowableException(e); } catch (ClassNotFoundException e) { m_logCat.fatal("ClassNotFoundException while initializing database", e); throw new UndeclaredThrowableException(e); } // XXX should we be throwing ArchiverException instead? } finally { ThreadCategory.setPrefix(oldPrefix); } } /** * Remove event with eventID from events table. NOTE: Postgres does not have * the ResultSet.deleteRow() implemented! - so use the eventID to delete! */ private boolean removeEvent(String eventID) { try { m_eventDeleteStmt.setString(1, eventID); m_eventDeleteStmt.executeUpdate(); } catch (SQLException sqle) { m_logCat.error("Unable to delete event \'" + eventID + "\': " + sqle.getMessage()); return false; } // debug logs if (m_logCat.isDebugEnabled()) { m_logCat.debug("EventID: " + eventID + " removed from events table"); } return true; } /** * Select the events created before 'age', log them to the archive file if * required and delete these events. * * NOTE: Postgres does not have the ResultSet.deleteRow() implemented! - so * use the eventID to delete! */ private void archiveEvents() { // number of events sent to the archive file int archCount = 0; // number of events deleted from the events table int remCount = 0; ResultSet eventsRS = null; try { m_eventsGetStmt.setTimestamp(1, new Timestamp(m_archAge)); eventsRS = m_eventsGetStmt.executeQuery(); int colCount = eventsRS.getMetaData().getColumnCount(); String eventID; String eventUEI; String eventLog; String eventDisplay; String eventAckUser; boolean ret; while (eventsRS.next()) { // get the eventID for the event eventID = eventsRS.getString(EVENT_ID); // get uei for event eventUEI = eventsRS.getString("eventUei"); // get eventLog for this row eventLog = eventsRS.getString(EVENT_LOG); // get eventDisplay for this row eventDisplay = eventsRS.getString(EVENT_DISPLAY); // eventAckUser for this event eventAckUser = eventsRS.getString(EVENT_ACK_USER); m_logCat.debug("Event id: " + eventID + " uei: " + eventUEI + " log: " + eventLog + " display: " + eventDisplay + " eventAck: " + eventAckUser); if (eventLog.equals(MSG_NO) && eventDisplay.equals(MSG_NO)) { // log = N, display = N, delete event ret = removeEvent(eventID); if (ret) { remCount++; } } else if (eventLog.equals(MSG_YES) && eventDisplay.equals(MSG_NO)) { // log = Y, display = N, archive event, then delete ret = removeEvent(eventID); if (ret) { sendToArchive(eventsRS, colCount); if (m_logCat.isDebugEnabled()) { m_logCat.debug("eventID " + eventID + " archived"); } archCount++; remCount++; } } else if (eventLog.equals(MSG_NO) && eventDisplay.equals(MSG_YES)) { /* * log = N, display = Y, delete event only if event has been * acknowledged. */ if (eventAckUser != null) { ret = removeEvent(eventID); if (ret) { remCount++; } } } else { /* * log = Y, display = Y, log and delete event only if event * has been acknowledged. */ if (eventAckUser != null) { ret = removeEvent(eventID); if (ret) { sendToArchive(eventsRS, colCount); if (m_logCat.isDebugEnabled()) { m_logCat.debug("eventID " + eventID + " archived"); } archCount++; remCount++; } } } } m_logCat.info("Number of events removed from the event table: " + remCount); m_logCat.info("Number of events sent to the archive: " + archCount); } catch (Throwable oe) { m_logCat.error("EventsArchiver: Error reading events for archival: "); m_logCat.error(oe.getMessage()); } finally { try { eventsRS.close(); } catch (Throwable e) { m_logCat.info("EventsArchiver: Exception while events result " + "set: message -> " + e.getMessage()); } } } /** * Archive the current row of the result set * * @exception SQLException * thrown if there is an error getting column values from the * result set */ private void sendToArchive(ResultSet eventsRS, int colCount) throws SQLException { StringBuffer outBuf = new StringBuffer(); for (int index = 1; index <= colCount; index++) { String colValue = eventsRS.getString(index); if (index == 1) { outBuf.append(colValue); } else { outBuf.append(m_archSeparator + colValue); } } String outBufStr = outBuf.toString(); m_archCat.fatal(outBufStr); } /** * Close the database statements and the connection and close log4j * Appenders and categories */ private void close() { try { m_eventsGetStmt.close(); } catch (SQLException e) { m_logCat.warn("Unable to close get statement", e); } try { m_eventDeleteStmt.close(); } catch (SQLException e) { m_logCat.warn("Unable to close delete statement", e); } try { m_conn.close(); } catch (SQLException e) { m_logCat.warn("Unable to close connection", e); } /* * log4j related - for now a shutdown() on the categories * will do - however if these categories are ever configured * with 'SocketAppender's or 'AsyncAppender's, these appenders * should be closed explictly before shutdown() */ LogManager.shutdown(); // m_logCat.shutdown(); } /** * The events archiver constructor - reads required properties, initializes * the database connection and the prepared statements to select and delete * events * * @throws org.opennms.netmgt.archive.ArchiverException if any. */ public EventsArchiver() throws ArchiverException { // call init init(); // initialize the prepared statements try { m_eventsGetStmt = m_conn.prepareStatement(DB_SELECT_EVENTS_TO_ARCHIVE); m_eventDeleteStmt = m_conn.prepareStatement(DB_DELETE_EVENT); } catch (SQLException e) { m_logCat.error("EventsArchiver: Exception in opening the database " + "connection or in the prepared statement for the " + "get events"); m_logCat.error(e.getMessage()); throw new ArchiverException("EventsArchiver: " + e.getMessage()); } } /** * <p>main</p> * * @param args an array of {@link java.lang.String} objects. */ public static void main(String[] args) { try { // create the archiver EventsArchiver ea = new EventsArchiver(); /* * Remove events. This method sends removed events * to archive file if configured for archival. */ ea.archiveEvents(); // close the archiver ea.close(); } catch (ArchiverException ae) { System.err.println(ae.getMessage()); } } }