/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2010-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.tools.spectrum;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringBufferInputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.ValidationException;
import org.opennms.core.utils.LogUtils;
import org.opennms.netmgt.xml.eventconf.AlarmData;
import org.opennms.netmgt.xml.eventconf.Event;
import org.opennms.netmgt.xml.eventconf.Events;
import org.opennms.netmgt.xml.eventconf.Logmsg;
import org.opennms.netmgt.xml.eventconf.Mask;
import org.opennms.netmgt.xml.eventconf.Maskelement;
import org.opennms.netmgt.xml.eventconf.Varbindsdecode;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* @author Jeff Gehlbach <jeffg@opennms.org>
* @author OpenNMS <http://www.opennms.org/>
*
*/
public class SpectrumTrapImporter {
private List<AlertMapping> m_alertMappings;
private List<EventDisposition> m_eventDispositions;
private Map<String,EventFormat> m_eventFormats;
private String m_modelTypeAssetField;
/**
* Something like uei.opennms.org/import/Spectrum/
*/
private String m_baseUei = null;
private Resource m_customEventsDir = null;
private PrintWriter m_outputWriter = null;
private String m_reductionKeyBody = null;
private SpectrumUtils m_utils;
private Map<String,String> m_alarmCreators;
private static final String COMMAND_HELP =
"Reads a set of Spectrum event definitions, as described by a directory tree\n" +
"with the following general layout:\n\n" +
" ./AlertMap\n" +
" ./EventDisp\n" +
" ./CsEvFormat/EventNNNNNNNN...\n" +
" ./CsEvFormat/EventTables/enumFoo...\n\n" +
"Produces XML describing a corresponding set of OpenNMS event definitions.\n\n" +
"Syntax: java -jar spectrum-trap-importer.jar [--dir <input directory>] \\\n" +
" --model-type-asset-field OpenNMS asset field containing model type\n" +
" --base-uei Directory containing Spectrum event definitions\n" +
" --output-file Filename for output (defaults to stdout)\n" +
" --key Body of clear- / reduction-key (advanced)\n";
public static void main(String[] argv) {
SpectrumTrapImporter importer = new SpectrumTrapImporter();
importer.configureArgs(argv);
try {
importer.afterPropertiesSet();
} catch (Throwable e) {
importer.printHelp("Fatal exception caught at startup: " + e.getMessage());
System.exit(1);
}
Events events = new Events();
try {
events = importer.makeEvents();
} catch (IOException e) {
importer.printHelp("Fatal exception caught at runtime: " + e.getMessage());
}
try {
//events.marshal(importer.getOutputWriter());
StringWriter sw = new StringWriter();
events.marshal(sw);
importer.prettyPrintXML(sw.toString());
} catch (MarshalException e) {
importer.printHelp("Fatal exception while marshaling output: " + e.getMessage());
} catch (ValidationException e) {
importer.printHelp("Fatal exception while validating output: " + e.getMessage());
} catch (IOException e) {
importer.printHelp("Fatal I/O exception: " + e.getMessage(), e);
} catch (SAXException e) {
importer.printHelp("Fatal SAX exception: " + e.getMessage());
} catch (ParserConfigurationException e) {
importer.printHelp("Fatal parser configuration exception: " + e.getMessage());
}
}
private void initialize() throws Exception {
LogUtils.logToConsole();
Resource alertMapResource = new FileSystemResource(m_customEventsDir.getFile().getPath() + File.separator + "AlertMap");
Resource eventDispResource = new FileSystemResource(m_customEventsDir.getFile().getPath() + File.separator + "EventDisp");
m_alertMappings = new AlertMapReader(alertMapResource).getAlertMappings();
m_eventDispositions = new EventDispositionReader(eventDispResource).getEventDispositions();
loadEventFormats();
m_utils = new SpectrumUtils();
m_utils.setModelTypeAssetField(m_modelTypeAssetField);
m_alarmCreators = new HashMap<String,String>();
}
private void loadEventFormats() throws Exception {
String csEvFormatDirName = m_customEventsDir.getFile().getPath() + File.separator + "CsEvFormat";
Map<String,EventFormat> formats = new HashMap<String,EventFormat>();
Map<String,String> unformattedEvents = new LinkedHashMap<String,String>();
for (AlertMapping mapping : m_alertMappings) {
if (formats.containsKey(mapping.getEventCode())) {
LogUtils.debugf(this, "Already have read an event-format for event-code [%s], not loading again", mapping.getEventCode());
continue;
}
String formatFileName = csEvFormatDirName + "/Event" + (mapping.getEventCode().substring(2));
try {
EventFormatReader reader = new EventFormatReader(new FileSystemResource(formatFileName));
formats.put(mapping.getEventCode(), reader.getEventFormat());
} catch (FileNotFoundException fnfe) {
unformattedEvents.put(mapping.getEventCode(), mapping.getEventCode());
continue;
}
}
LogUtils.infof(this, "Loaded %d event-formats from files in [%s]", formats.size(), csEvFormatDirName);
if (unformattedEvents.keySet().size() > 0) {
StringBuilder uelBuilder = new StringBuilder("");
for (String ec : unformattedEvents.keySet()) {
if (uelBuilder.length() > 0) {
uelBuilder.append(" ");
}
uelBuilder.append(ec);
}
LogUtils.infof(this, "Unable to load an event-format for %d event-codes [%s]. Continuing without them.", unformattedEvents.keySet().size(), uelBuilder.toString());
}
m_eventFormats = formats;
}
@Override
public void afterPropertiesSet() throws Exception {
if (m_baseUei == null) {
throw new IllegalStateException("The baseUei property must be set");
}
if (m_customEventsDir == null) {
throw new IllegalStateException("The customEventsDir property must be set");
}
if (m_modelTypeAssetField == null) {
throw new IllegalStateException("The modelTypeAssetField property must be set");
}
if (m_reductionKeyBody == null) {
throw new IllegalStateException("The reductionKeyBody property must be set");
}
if (m_outputWriter == null) {
throw new IllegalStateException("The outputStream property must be set");
}
initialize();
}
private void configureArgs(String[] argv) {
Options opts = new Options();
opts.addOption("d", "dir", true, "Directory where Spectrum custom events are located");
opts.addOption("t", "model-type-asset-field", true, "Name of asset field containing equivalent of Spectrum model type. Defaults to 'manufacturer'.");
opts.addOption("u", "base-uei", true, "Base value for UEI of generated OpenNMS events. Defaults to 'uei.opennms.org/import/Spectrum'.");
opts.addOption("f", "output-file", true, "File to which OpenNMS events will be written. Defaults to standard output.");
opts.addOption("k", "key", true, "Middle part of reduction- and clear-key, after UEI and before discriminators. Defaults to '%dpname%:%nodeid%:%interface%'.");
CommandLineParser parser = new GnuParser();
try {
CommandLine cmd = parser.parse(opts, argv);
if (cmd.hasOption('d')) {
m_customEventsDir = new FileSystemResource(cmd.getOptionValue('d'));
}
if (cmd.hasOption('t')) {
m_modelTypeAssetField = cmd.getOptionValue('t');
} else {
m_modelTypeAssetField = "manufacturer";
}
if (cmd.hasOption('u')) {
m_baseUei = cmd.getOptionValue('u');
} else {
m_baseUei = "uei.opennms.org/import/Spectrum";
}
if (cmd.hasOption('f')) {
m_outputWriter = new PrintWriter(new FileSystemResource(cmd.getOptionValue('f')).getFile());
} else {
m_outputWriter = new PrintWriter(System.out);
}
if (cmd.hasOption('k')) {
m_reductionKeyBody = cmd.getOptionValue('k');
} else {
m_reductionKeyBody = "%dpname%:%nodeid%:%interface%";
}
} catch (ParseException pe) {
printHelp("Failed to parse command line options");
System.exit(1);
} catch (FileNotFoundException fnfe) {
printHelp("Custom events input directory does not seem to exist");
}
}
public Events makeEvents() throws IOException {
Events events = new Events();
for (AlertMapping mapping : m_alertMappings) {
for (EventDisposition dispo : m_eventDispositions) {
if (dispo.getEventCode().equals(mapping.getEventCode())) {
Event evt = makeEventConf(mapping, dispo);
if (evt == null) {
continue;
}
events.addEvent(evt);
}
}
}
LogUtils.debugf(this, "Made %d events", events.getEventCollection().size());
return events;
}
public Event makeEventConf(AlertMapping mapping, EventDisposition dispo) throws IOException {
Event evt = new Event();
evt.setMask(makeEventMask(mapping));
evt.setUei(makeUei(mapping.getEventCode()));
evt.setEventLabel(makeEventLabel(mapping));
evt.setDescr(makeDescr(mapping));
evt.setLogmsg(makeLogMsg(mapping, dispo));
evt.setSeverity(makeSeverity(mapping, dispo));
if (makeAlarmData(mapping, dispo) != null) {
evt.setAlarmData(makeAlarmData(mapping, dispo));
}
evt.setVarbindsdecode(makeVarbindsDecodes(mapping));
if (shouldDiscardEvent(dispo)) {
LogUtils.warnf(this, "Not creating an OpenNMS event definition corresponding to the following Spectrum event-disposition, because doing so would cause a conflict with an existing alarm-creating event for the same event-code and discriminators: %s. Hand-tweaking the output may be needed to compensate for this omission.", dispo);
return null;
}
return evt;
}
public Mask makeEventMask(AlertMapping mapping) {
Mask mask = new Mask();
// Trap-OID
Maskelement me = new Maskelement();
me.setMename("id");
me.setMevalue(new String[] { mapping.getTrapOid() });
mask.addMaskelement(me);
// Generic-type
me = new Maskelement();
me.setMename("generic");
me.setMevalue(new String[] { mapping.getTrapGenericType() });
mask.addMaskelement(me);
// Specific-type
me = new Maskelement();
me.setMename("specific");
me.setMevalue(new String[] { mapping.getTrapSpecificType() });
mask.addMaskelement(me);
return mask;
}
public String makeUei(String eventCode) {
StringBuilder ueiBuilder = new StringBuilder(m_baseUei);
ueiBuilder.append("/").append(eventCode);
return ueiBuilder.toString();
}
public String makeEventLabel(AlertMapping mapping) {
StringBuilder labelBuilder = new StringBuilder("Spectrum imported event: ");
String shortName = mapping.getEventCode();
if (m_eventFormats.containsKey(mapping.getEventCode())) {
Matcher m = Pattern.compile("(?s).*An? \"(.*?)\" event has occurred.*").matcher(m_eventFormats.get(mapping.getEventCode()).getContents());
if (m.find()) {
shortName = m.group(1);
}
}
labelBuilder.append(shortName);
return labelBuilder.toString();
}
public String makeDescr(AlertMapping mapping) {
StringBuilder descrBuilder = new StringBuilder("<p>");
if (m_eventFormats.containsKey(mapping.getEventCode())) {
String theDescr = m_utils.translateAllSubstTokens(m_eventFormats.get(mapping.getEventCode()));
theDescr = theDescr.replaceAll("\n", "<br/>\n");
descrBuilder.append(theDescr);
}
descrBuilder.append("</p>\n");
return descrBuilder.toString();
}
public Logmsg makeLogMsg(AlertMapping mapping, EventDisposition dispo) {
Logmsg msg = new Logmsg();
if (! dispo.isPersistent()) {
msg.setDest("donotpersist");
} else if (! dispo.isLogEvent()) {
msg.setDest("discardtraps");
} else {
msg.setDest("logndisplay");
}
msg.setContent("<p>" + makeEventLabel(mapping) + "</p>");
return msg;
}
public String makeSeverity(AlertMapping mapping, EventDisposition dispo) {
if (dispo.isClearAlarm()) {
// A clear-alarm always needs to have Normal severity for our automations to work
return "Normal";
}
return m_utils.translateSeverity(dispo.getAlarmSeverity());
}
public AlarmData makeAlarmData(AlertMapping mapping, EventDisposition dispo) {
if (!dispo.isCreateAlarm() && !dispo.isClearAlarm()) {
return null;
}
AlarmData alarmData = new AlarmData();
// Set the alarm-type according to clues in the disposition
if (dispo.isClearAlarm()) {
alarmData.setAlarmType(2);
} else {
alarmData.setAlarmType(1);
}
// Set the reduction key to include standard node stuff plus any discriminators
StringBuilder rkBuilder = new StringBuilder("%uei%:");
rkBuilder.append(m_reductionKeyBody);
for (int discriminator : dispo.getDiscriminators()) {
rkBuilder.append(":%parm[#").append(discriminator).append("]%");
}
// If it's marked as a unique alarm, add the event ID to the reduction-key
if (dispo.isUniqueAlarm()) {
rkBuilder.append("%eventid%");
}
alarmData.setReductionKey(rkBuilder.toString());
// If it's a clear-alarm, set the clear-key appropriately
if (dispo.isClearAlarm()) {
StringBuilder ckBuilder = new StringBuilder(makeUei(dispo.getClearAlarmCause()));
ckBuilder.append(":").append(m_reductionKeyBody);
for (int discriminator : dispo.getDiscriminators()) {
ckBuilder.append(":%parm[#").append(discriminator).append("]%");
}
alarmData.setClearKey(ckBuilder.toString());
}
return alarmData;
}
public List<Varbindsdecode> makeVarbindsDecodes(AlertMapping mapping) throws IOException {
if (m_eventFormats.containsKey(mapping.getEventCode())) {
EventFormat fmt = m_eventFormats.get(mapping.getEventCode());
return m_utils.translateAllEventTables(fmt, m_customEventsDir.getFile().getPath() + File.separator + "CsEvFormat" + File.separator + "EventTables");
} else {
return new ArrayList<Varbindsdecode>();
}
}
public boolean shouldDiscardEvent(EventDisposition dispo) {
boolean discard = false;
StringBuilder eventKeyBldr = new StringBuilder(dispo.getEventCode());
for (int d : dispo.getDiscriminators()) {
eventKeyBldr.append(",").append(d);
}
String eventKey = eventKeyBldr.toString();
// If not yet recorded, note this one
if (dispo.isCreateAlarm()) {
m_alarmCreators.put(eventKey, eventKey);
}
// If this is a clear-alarm, but a create-alarm already exists with
// the same event-code and discriminators, then we should discard
// this one
if (dispo.isClearAlarm() && m_alarmCreators.containsKey(eventKey)) {
discard = true;
}
return discard;
}
private void prettyPrintXML(String inDoc) throws IOException, SAXException, ParserConfigurationException {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
InputSource inSrc = new InputSource(new StringReader(inDoc));
Document outDoc = builder.parse(inSrc);
OutputFormat fmt = new OutputFormat(outDoc);
fmt.setLineWidth(72);
fmt.setIndenting(true);
fmt.setIndent(2);
XMLSerializer ser = new XMLSerializer(m_outputWriter, fmt);
ser.serialize(outDoc);
}
public void setBaseUei(String baseUei) {
if (baseUei == null) {
throw new IllegalArgumentException("The base-UEI must be non-null");
}
m_baseUei = baseUei;
}
public String getBaseUei() {
return m_baseUei;
}
public void setCustomEventsDir(FileSystemResource customEventsDir) throws IOException {
if (! customEventsDir.getFile().isDirectory()) {
throw new IllegalArgumentException("The customEventsDir property must refer to a directory");
}
m_customEventsDir = customEventsDir;
}
public Resource getCustomEventsDir() {
return m_customEventsDir;
}
public List<AlertMapping> getAlertMappings() {
return Collections.unmodifiableList(m_alertMappings);
}
public List<EventDisposition> getEventDispositions() {
return Collections.unmodifiableList(m_eventDispositions);
}
public Map<String,EventFormat> getEventFormats() {
return Collections.unmodifiableMap(m_eventFormats);
}
public void setOutputWriter(PrintWriter out) {
m_outputWriter = out;
}
public PrintWriter getOutputWriter() {
return m_outputWriter;
}
public void setModelTypeAssetField(String fieldName) {
m_modelTypeAssetField = fieldName;
}
public String getModelTypeAssetField() {
return m_modelTypeAssetField;
}
public void setReductionKeyBody(String body) {
m_reductionKeyBody = body;
}
public String getReductionKeyBody() {
return m_reductionKeyBody;
}
private void printHelp(String msg) {
System.err.println("Error: " + msg + "\n\n");
System.err.println(COMMAND_HELP);
}
private void printHelp(String msg, Throwable t) {
System.err.println("Error: " + msg + "\n\n");
t.printStackTrace();
System.err.println(COMMAND_HELP);
}
}