Java tutorial
/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2009-2014 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2014 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 Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero 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.io.LineNumberReader; import java.io.Reader; import java.io.StringReader; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.stream.EventFilter; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.util.EntityUtils; import org.opennms.core.criteria.Criteria; import org.opennms.core.criteria.restrictions.EqRestriction; import org.opennms.core.criteria.restrictions.GtRestriction; import org.opennms.core.web.HttpClientWrapper; import org.opennms.netmgt.config.ackd.Parameter; import org.opennms.netmgt.dao.api.AckdConfigurationDao; import org.opennms.netmgt.dao.api.AcknowledgmentDao; import org.opennms.netmgt.dao.api.AlarmDao; import org.opennms.netmgt.model.AckAction; import org.opennms.netmgt.model.OnmsAcknowledgment; import org.opennms.netmgt.model.OnmsAlarm; import org.opennms.netmgt.model.OnmsSeverity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p>HypericAckProcessor class.</p> * * @author ranger * @version $Id: $ */ public class HypericAckProcessor implements AckProcessor { private static final Logger LOG = LoggerFactory.getLogger(HypericAckProcessor.class); /** Constant <code>READER_NAME_HYPERIC="HypericReader"</code> */ public static final String READER_NAME_HYPERIC = "HypericReader"; /** Constant <code>PARAMETER_PREFIX_HYPERIC_SOURCE="source:"</code> */ public static final String PARAMETER_PREFIX_HYPERIC_SOURCE = "source:"; /** Constant <code>ALERTS_PER_HTTP_TRANSACTION=200</code> */ public static final int ALERTS_PER_HTTP_TRANSACTION = 200; // public static final String PARAMETER_HYPERIC_HOSTS = "hyperic-hosts"; private AckdConfigurationDao m_ackdConfigDao; private AcknowledgmentDao m_ackDao; private AlarmDao m_alarmDao; /** * <p>This class is used as the data bean for parsing XML responses from the Hyperic HQ * systems that are serving up our alert status groovy servlet. The expected data * format is:</p> * <pre> * <?xml version="1.0" encoding="UTF-8"?> * <hyperic-alert-statuses> * <alert id="1" ack="true" fixed="true"/> * <alert id="2" ack="true" fixed="true"/> * <alert id="3" ack="true" fixed="false"/> * <alert id="4" ack="false" fixed="true"/> * <alert id="5" ack="false" fixed="false"/> * </hyperic-alert-statuses> * </pre> */ @XmlRootElement(name = "hyperic-alert-statuses") static class HypericAlertStatuses { private List<HypericAlertStatus> statusList; @XmlElement public List<HypericAlertStatus> getStatusList() { return statusList; } public void setStatusList(List<HypericAlertStatus> statusList) { this.statusList = statusList; } } /** * <p>This class represents each individual alarm status within the message. The expected * format is:</p> * <pre> * <alert id="1" ack="true" fixed="true"/> * </pre> * * <p>TODO: Add ackUser, ackTime, fixedUser, fixedTime attributes to objects if possible</p> */ @XmlRootElement(name = "alert") static class HypericAlertStatus { private int alertId; private String ackUser; private String ackMessage; private Date ackTime; private boolean isFixed; private String fixUser; private String fixMessage; private Date fixTime; @XmlAttribute(name = "id", required = true) public int getAlertId() { return alertId; } public void setAlertId(int alertId) { this.alertId = alertId; } @XmlAttribute(name = "fixed", required = true) public boolean isFixed() { return isFixed; } public void setFixed(boolean isFixed) { this.isFixed = isFixed; } @XmlAttribute(name = "ackUser") public String getAckUser() { return ackUser; } public void setAckUser(String ackUser) { this.ackUser = ackUser; } @XmlAttribute(name = "ackMessage") public String getAckMessage() { return ackMessage; } public void setAckMessage(String ackMessage) { this.ackMessage = ackMessage; } @XmlAttribute(name = "ackTime") public Date getAckTime() { return ackTime; } public void setAckTime(Date ackTime) { this.ackTime = ackTime; } @XmlAttribute(name = "fixUser") public String getFixUser() { return fixUser; } public void setFixUser(String fixUser) { this.fixUser = fixUser; } @XmlAttribute(name = "fixMessage") public String getFixMessage() { return fixMessage; } public void setFixMessage(String fixMessage) { this.fixMessage = fixMessage; } @XmlAttribute(name = "fixTime") public Date getFixTime() { return fixTime; } public void setFixTime(Date fixTime) { this.fixTime = fixTime; } @Override public String toString() { StringBuffer retval = new StringBuffer(); retval.append("{ "); retval.append("id: ").append(String.valueOf(alertId)).append(", "); retval.append("fixed: ").append(String.valueOf(isFixed)).append(", "); retval.append("ackUser: ").append(String.valueOf(ackUser)).append(", "); retval.append("ackMessage: ").append(String.valueOf(ackMessage)).append(", "); retval.append("ackTime: ").append(String.valueOf(ackTime)).append(", "); retval.append("fixUser: ").append(String.valueOf(fixUser)).append(", "); retval.append("fixMessage: ").append(String.valueOf(fixMessage)).append(", "); retval.append("fixTime: ").append(String.valueOf(fixTime)); retval.append(" }"); return retval.toString(); } } /// TODO Verify that this works properly /** * <p>reloadConfigs</p> */ @Override public void reloadConfigs() { LOG.debug("reloadConfigs: reloading configuration..."); m_ackdConfigDao.reloadConfiguration(); LOG.debug("reloadConfigs: configuration reloaded"); } /** * <p>fetchUnclearedHypericAlarms</p> * * @return a {@link java.util.List} object. */ public List<OnmsAlarm> fetchUnclearedHypericAlarms() { // Query for existing, unacknowledged alarms in OpenNMS that were generated based on Hyperic alerts Criteria criteria = new Criteria(OnmsAlarm.class); // criteria.add(Restrictions.isNull("alarmAckUser")); // Restrict to Hyperic alerts criteria.addRestriction(new EqRestriction("uei", "uei.opennms.org/external/hyperic/alert")); // Only consider alarms that are above severity NORMAL // {@see org.opennms.netmgt.model.OnmsSeverity} criteria.addRestriction(new GtRestriction("severity", OnmsSeverity.NORMAL)); // TODO Figure out how to query by parameters (maybe necessary) // Query list of outstanding alerts with remote platform identifiers return m_alarmDao.findMatching(criteria); } /** * <p>getUrlForHypericSource</p> * * @param source a {@link java.lang.String} object. * @return a {@link java.lang.String} object. */ public String getUrlForHypericSource(String source) { if (source == null) { throw new IllegalArgumentException( "Cannot search for null Hyperic platform IDs inside the ackd configuration"); } else if ("".equals(source)) { throw new IllegalArgumentException( "Cannot search for blank Hyperic platform IDs inside the ackd configuration"); } List<Parameter> params = m_ackdConfigDao.getParametersForReader(READER_NAME_HYPERIC); if (params == null) { throw new IllegalStateException("There is no configuration for the '" + READER_NAME_HYPERIC + "' reader inside the ackd configuration"); } for (Parameter param : params) { if ((PARAMETER_PREFIX_HYPERIC_SOURCE + source).equalsIgnoreCase(param.getKey())) { return param.getValue(); } } return null; } /** * <p>run</p> */ @Override public void run() { List<OnmsAcknowledgment> acks = new ArrayList<OnmsAcknowledgment>(); try { LOG.info("run: Processing Hyperic acknowledgments..."); // Query list of outstanding alerts with remote platform identifiers List<OnmsAlarm> unAckdAlarms = fetchUnclearedHypericAlarms(); Map<String, List<OnmsAlarm>> organizedAlarms = new TreeMap<String, List<OnmsAlarm>>(); int legacyAlarmCount = 0; // Split the list of alarms up according to the Hyperic system where they originated for (OnmsAlarm alarm : unAckdAlarms) { String key = getAlertSourceParmValue(alarm); if (key == null || "".equals(key)) { legacyAlarmCount++; } else { List<OnmsAlarm> targetList = organizedAlarms.get(key); if (targetList == null) { targetList = new ArrayList<OnmsAlarm>(); organizedAlarms.put(key, targetList); } targetList.add(alarm); } } if (legacyAlarmCount > 0) { LOG.info( "{} Hyperic alarms without an alert.source param found, these alarms will not be processed", String.valueOf(legacyAlarmCount)); } // Connect to each Hyperic system and query for the status of corresponding alerts for (Map.Entry<String, List<OnmsAlarm>> alarmList : organizedAlarms.entrySet()) { String hypericSystem = alarmList.getKey(); List<OnmsAlarm> alarmsForSystem = alarmList.getValue(); // Match the alert.source to the Hyperic URL via the config String hypericUrl = getUrlForHypericSource(hypericSystem); if (hypericUrl == null) { // If the alert.source doesn't match anything in our current config, just ignore it, warn in the logs LOG.warn("Could not find Hyperic host URL for the following platform ID: {}", hypericSystem); LOG.warn("Skipping processing of {} alarms with that platform ID", alarmsForSystem.size()); continue; } try { List<String> alertIdList = new ArrayList<String>(); for (OnmsAlarm alarmForSystem : alarmList.getValue()) { // Construct a sane query for the Hyperic system String alertId = getAlertIdParmValue(alarmForSystem); alertIdList.add(alertId); } // Call fetchHypericAlerts() for each system List<HypericAlertStatus> alertsForSystem = fetchHypericAlerts(hypericUrl, alertIdList); // Iterate and update any acknowledged or fixed alerts for (HypericAlertStatus alert : alertsForSystem) { OnmsAlarm alarm = findAlarmForHypericAlert(alarmsForSystem, hypericSystem, alert); if (alarm == null) { LOG.warn( "Could not find the OpenNMS alarm for the following Hyperic alert: URL: \"{}\", id: {}", hypericUrl, alert.getAlertId()); } else if (alert.isFixed() && !OnmsSeverity.CLEARED.equals(alarm.getSeverity())) { // If the Hyperic alert has been fixed and the local alarm is not yet marked as CLEARED, then clear it OnmsAcknowledgment ack = new OnmsAcknowledgment(alarm, "Ackd.HypericAckProcessor", (alert.getFixTime() != null) ? alert.getFixTime() : new Date()); ack.setAckAction(AckAction.CLEAR); ack.setLog(alert.getFixMessage()); acks.add(ack); } else if (alert.getAckMessage() != null && alarm.getAckTime() == null) { // If the Hyperic alert has been ack'd and the local alarm is not yet ack'd, then ack it OnmsAcknowledgment ack = new OnmsAcknowledgment(alarm, "Ackd.HypericAckProcessor", (alert.getAckTime() != null) ? alert.getAckTime() : new Date()); ack.setAckAction(AckAction.ACKNOWLEDGE); ack.setLog(alert.getAckMessage()); acks.add(ack); } } } catch (Throwable e) { LOG.warn("run: threw exception when processing alarms for Hyperic system {}", hypericSystem, e.getMessage()); LOG.warn("run: {} acknowledgements processed successfully before exception", acks.size()); } finally { if (acks.size() > 0) { m_ackDao.processAcks(acks); } } } LOG.info("run: Finished processing Hyperic acknowledgments ({} ack(s) processed for {} alarm(s))", acks.size(), unAckdAlarms.size()); } catch (Throwable e) { LOG.warn("run: threw exception", e); } } /** * <p>findAlarmForHypericAlert</p> * * @param alarms a {@link java.util.List} object. * @param platformId a {@link java.lang.String} object. * @param alert a {@link org.opennms.netmgt.ackd.readers.HypericAckProcessor.HypericAlertStatus} object. * @return a {@link org.opennms.netmgt.model.OnmsAlarm} object. */ public static OnmsAlarm findAlarmForHypericAlert(List<OnmsAlarm> alarms, String platformId, HypericAlertStatus alert) { String targetPlatformId = "alert.source=" + platformId + "(string,text)"; String targetAlertId = "alert.id=" + String.valueOf(alert.getAlertId()) + "(string,text)"; for (OnmsAlarm alarm : alarms) { String parmString = alarm.getEventParms(); String[] parms = parmString.split(";"); for (String parm : parms) { if (targetPlatformId.equals(parm)) { for (String alertparm : parms) { if (targetAlertId.equals(alertparm)) { return alarm; } } } } } return null; } /** * <p>getAlertSourceParmValue</p> * * @param alarm a {@link org.opennms.netmgt.model.OnmsAlarm} object. * @return a {@link java.lang.String} object. */ public static String getAlertSourceParmValue(OnmsAlarm alarm) { return getParmValueByRegex(alarm, "alert.source=(.*)[(]string,text[)]"); } /** * <p>getAlertIdParmValue</p> * * @param alarm a {@link org.opennms.netmgt.model.OnmsAlarm} object. * @return a {@link java.lang.String} object. */ public static String getAlertIdParmValue(OnmsAlarm alarm) { return getParmValueByRegex(alarm, "alert.id=([0-9]*)[(]string,text[)]"); } /** * <p>Some parameter values that you might be interested in inside this class:</p> * * <ul> * <li><code>alert.id</code>: ID of the alert in the remote Hyperic HQ system</li> * <li><code>alert.baseURL</code>: Base URL of the Hyperic HQ service that generated the alert</li> * <li><code>alert.source</code>: String key that identifies the Hyperic HQ service that generated the alert</li> * </ul> * * @param alarm The alarm to fetch parameters from * @param regex Java regex expression with a () group that will be returned * @return The matching group from the regex */ public static String getParmValueByRegex(OnmsAlarm alarm, String regex) { Pattern pattern = Pattern.compile(regex); String parmString = alarm.getEventParms(); String[] parms = parmString.split(";"); for (String parm : parms) { Matcher matcher = pattern.matcher(parm); if (matcher.matches()) { return matcher.group(1); } } return null; } /** * <p>fetchHypericAlerts</p> * * @param hypericUrl a {@link java.lang.String} object. * @param alertIds a {@link java.util.List} object. * @return a {@link java.util.List} object. * @throws org.apache.commons.httpclient.HttpException if any. * @throws java.io.IOException if any. * @throws javax.xml.bind.JAXBException if any. * @throws javax.xml.stream.XMLStreamException if any. */ public static List<HypericAlertStatus> fetchHypericAlerts(String hypericUrl, List<String> alertIds) throws IOException, JAXBException, XMLStreamException { List<HypericAlertStatus> retval = new ArrayList<HypericAlertStatus>(); if (alertIds.size() < 1) { return retval; } for (int i = 0; i < alertIds.size(); i++) { // Construct the query string for the HTTP operation StringBuffer alertIdString = new StringBuffer(); alertIdString.append("?"); for (int j = 0; (j < ALERTS_PER_HTTP_TRANSACTION) && (i < alertIds.size()); j++, i++) { if (j > 0) alertIdString.append("&"); // Numeric values, no need to worry about URL encoding alertIdString.append("id=").append(alertIds.get(i)); } final HttpClientWrapper clientWrapper = HttpClientWrapper.create().setConnectionTimeout(3000) .setSocketTimeout(3000) // Set a custom user-agent so that it's easy to tcpdump these requests .setUserAgent("OpenNMS-Ackd.HypericAckProcessor"); HttpUriRequest httpMethod = new HttpGet(hypericUrl + alertIdString.toString()); // Parse the URI from the config so that we can deduce the username/password information String userinfo = null; try { URI hypericUri = new URI(hypericUrl); userinfo = hypericUri.getUserInfo(); } catch (final URISyntaxException e) { LOG.warn("Could not parse URI to get username/password stanza: {}", hypericUrl, e); } if (userinfo != null && !"".equals(userinfo)) { final String[] credentials = userinfo.split(":"); if (credentials.length == 2) { clientWrapper.addBasicCredentials(credentials[0], credentials[1]).usePreemptiveAuth(); } else { LOG.warn("Unable to deduce username/password from '{}'", userinfo); } } try { CloseableHttpResponse response = clientWrapper.execute(httpMethod); retval = parseHypericAlerts(new StringReader(EntityUtils.toString(response.getEntity()))); } finally { IOUtils.closeQuietly(clientWrapper); } } return retval; } /** * <p>parseHypericAlerts</p> * * @param reader a {@link java.io.Reader} object. * @return a {@link java.util.List} object. * @throws javax.xml.bind.JAXBException if any. * @throws javax.xml.stream.XMLStreamException if any. */ public static List<HypericAlertStatus> parseHypericAlerts(Reader reader) throws JAXBException, XMLStreamException { List<HypericAlertStatus> retval = new ArrayList<HypericAlertStatus>(); // Instantiate a JAXB context to parse the alert status JAXBContext context = JAXBContext .newInstance(new Class[] { HypericAlertStatuses.class, HypericAlertStatus.class }); XMLInputFactory xmlif = XMLInputFactory.newInstance(); XMLEventReader xmler = xmlif.createXMLEventReader(reader); EventFilter filter = new EventFilter() { @Override public boolean accept(XMLEvent event) { return event.isStartElement(); } }; XMLEventReader xmlfer = xmlif.createFilteredReader(xmler, filter); // Read up until the beginning of the root element StartElement startElement = (StartElement) xmlfer.nextEvent(); // Fetch the root element name for {@link HypericAlertStatus} objects String rootElementName = context.createJAXBIntrospector().getElementName(new HypericAlertStatuses()) .getLocalPart(); if (rootElementName.equals(startElement.getName().getLocalPart())) { Unmarshaller unmarshaller = context.createUnmarshaller(); // Use StAX to pull parse the incoming alert statuses while (xmlfer.peek() != null) { Object object = unmarshaller.unmarshal(xmler); if (object instanceof HypericAlertStatus) { HypericAlertStatus alertStatus = (HypericAlertStatus) object; retval.add(alertStatus); } } } else { // Try to pull in the HTTP response to give the user a better idea of what went wrong StringBuffer errorContent = new StringBuffer(); LineNumberReader lineReader = new LineNumberReader(reader); try { String line; while (true) { line = lineReader.readLine(); if (line == null) { break; } else { errorContent.append(line.trim()); } } } catch (IOException e) { errorContent.append("Exception while trying to print out message content: " + e.getMessage()); } // Throw an exception and include the erroneous HTTP response in the exception text throw new JAXBException("Found wrong root element in Hyperic XML document, expected: \"" + rootElementName + "\", found \"" + startElement.getName().getLocalPart() + "\"\n" + errorContent.toString()); } return retval; } /** * <p>setAckdConfigDao</p> * * @param configDao a {@link org.opennms.netmgt.dao.api.AckdConfigurationDao} object. */ public synchronized void setAckdConfigDao(final AckdConfigurationDao configDao) { m_ackdConfigDao = configDao; } /** * @param ackDao a {@link org.opennms.netmgt.dao.api.AcknowledgmentDao} object. */ public synchronized void setAcknowledgmentDao(final AcknowledgmentDao ackDao) { m_ackDao = ackDao; } /** * <p>afterPropertiesSet</p> * * @throws java.lang.Exception if any. */ @Override public void afterPropertiesSet() throws Exception { } /** * <p>setAlarmDao</p> * * @param dao a {@link org.opennms.netmgt.dao.api.AlarmDao} object. */ public synchronized void setAlarmDao(final AlarmDao dao) { m_alarmDao = dao; } }