/******************************************************************************* * 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.protocols.xml.collector; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.opennms.netmgt.collectd.CollectionAgent; import org.opennms.netmgt.collectd.CollectionException; import org.opennms.netmgt.collectd.ServiceCollector; import org.opennms.netmgt.config.collector.AttributeGroupType; import org.opennms.netmgt.dao.support.ResourceTypeUtils; import org.opennms.protocols.sftp.Sftp3gppUrlConnection; import org.opennms.protocols.sftp.Sftp3gppUrlHandler; import org.opennms.protocols.xml.config.XmlDataCollection; import org.opennms.protocols.xml.config.XmlObject; import org.opennms.protocols.xml.config.XmlSource; import org.w3c.dom.Document; /** * The custom implementation of the interface XmlCollectionHandler for 3GPP XML Data. * <p>This supports the processing of several files ordered by filename, and the * timestamp between files won't be taken in consideration.</p> * <p>The state will be persisted on disk by saving the name of the last successfully * processed file.</p> * * @author <a href="mailto:agalue@opennms.org">Alejandro Galue</a> */ public class Sftp3gppXmlCollectionHandler extends AbstractXmlCollectionHandler { /** The Constant XML_LAST_FILENAME. */ public static final String XML_LAST_FILENAME = "_xmlCollectorLastFilename"; /** The 3GPP Performance Metric Instance Formats. */ private Properties m_pmGroups; /* (non-Javadoc) * @see org.opennms.protocols.xml.collector.XmlCollectionHandler#collect(org.opennms.netmgt.collectd.CollectionAgent, org.opennms.protocols.xml.config.XmlDataCollection, java.util.Map) */ @Override public XmlCollectionSet collect(CollectionAgent agent, XmlDataCollection collection, Map<String, Object> parameters) throws CollectionException { // Create a new collection set. XmlCollectionSet collectionSet = new XmlCollectionSet(agent); collectionSet.setCollectionTimestamp(new Date()); collectionSet.setStatus(ServiceCollector.COLLECTION_UNKNOWN); // TODO We could be careful when handling exceptions because parsing exceptions will be treated different from connection or retrieval exceptions try { File resourceDir = new File(getRrdRepository().getRrdBaseDir(), Integer.toString(agent.getNodeId())); for (XmlSource source : collection.getXmlSources()) { if (!source.getUrl().startsWith(Sftp3gppUrlHandler.PROTOCOL)) { throw new CollectionException("The 3GPP SFTP Collection Handler can only use the protocol " + Sftp3gppUrlHandler.PROTOCOL); } String urlStr = parseUrl(source.getUrl(), agent, collection.getXmlRrd().getStep()); URL url = UrlFactory.getUrl(urlStr); String lastFile = getLastFilename(resourceDir, url.getPath()); Sftp3gppUrlConnection connection = (Sftp3gppUrlConnection) url.openConnection(); if (lastFile == null) { lastFile = connection.get3gppFileName(); log().debug("collect(single): retrieving file from " + url.getPath() + File.separatorChar + lastFile + " from " + agent.getHostAddress()); Document doc = getXmlDocument(urlStr); fillCollectionSet(agent, collectionSet, source, doc); setLastFilename(resourceDir, url.getPath(), lastFile); deleteFile(connection, lastFile); } else { connection.connect(); List<String> files = connection.getFileList(); long lastTs = connection.getTimeStampFromFile(lastFile); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); factory.setIgnoringComments(true); boolean collected = false; for (String fileName : files) { if (connection.getTimeStampFromFile(fileName) > lastTs) { log().debug("collect(multiple): retrieving file " + fileName + " from " + agent.getHostAddress()); InputStream is = connection.getFile(fileName); Document doc = builder.parse(is); fillCollectionSet(agent, collectionSet, source, doc); setLastFilename(resourceDir, url.getPath(), fileName); deleteFile(connection, fileName); collected = true; } } if (!collected) { log().warn("collect: could not find any file after " + lastFile + " on " + agent); } connection.disconnect(); } } collectionSet.setStatus(ServiceCollector.COLLECTION_SUCCEEDED); return collectionSet; } catch (Exception e) { collectionSet.setStatus(ServiceCollector.COLLECTION_FAILED); throw new CollectionException(e.getMessage(), e); } } /** * Gets the last filename. * * @param resourceDir the resource directory * @param targetPath the target path * @return the last filename * @throws Exception the exception */ private String getLastFilename(File resourceDir, String targetPath) throws Exception { String filename = null; try { filename = ResourceTypeUtils.getStringProperty(resourceDir, getCacheId(targetPath)); } catch (Exception e) { log().info("getLastFilename: creating a new filename tracker on " + resourceDir); } return filename; } /** * Sets the last filename. * * @param resourceDir the resource directory * @param targetPath the target path * @param filename the filename * @throws Exception the exception */ private void setLastFilename(File resourceDir, String targetPath, String filename) throws Exception { ResourceTypeUtils.updateStringProperty(resourceDir, filename, getCacheId(targetPath)); } /** * Gets the cache id. * * @param targetPath the target path * @return the cache id */ private String getCacheId(String targetPath) { return XML_LAST_FILENAME + '.' + getServiceName() + targetPath.replaceAll("/", "_"); } /** * Safely delete file on remote node. * * @param connection the SFTP URL Connection * @param fileName the file name */ private void deleteFile(Sftp3gppUrlConnection connection, String fileName) { try { connection.deleteFile(fileName); } catch (Exception e) { log().warn("Can't delete file " + fileName + " from " + connection.getURL().getHost() + " because " + e.getMessage()); } } /* (non-Javadoc) * @see org.opennms.protocols.xml.collector.AbstractXmlCollectionHandler#processXmlResource(org.opennms.protocols.xml.collector.XmlCollectionResource, org.opennms.netmgt.config.collector.AttributeGroupType) */ @Override protected void processXmlResource(XmlCollectionResource resource, AttributeGroupType attribGroupType) { Map<String,String> properties = get3gppProperties(get3gppFormat(resource.getResourceTypeName()), resource.getInstance()); for (Entry<String,String> entry : properties.entrySet()) { XmlCollectionAttributeType attribType = new XmlCollectionAttributeType(new XmlObject(entry.getKey(), "string"), attribGroupType); resource.setAttributeValue(attribType, entry.getValue()); } } /** * Parses the URL. * * @param unformattedUrl the unformatted URL * @param agent the agent * @param collectionStep the collection step (in seconds) * @param currentTimestamp the current timestamp * @return the string */ protected String parseUrl(String unformattedUrl, CollectionAgent agent, Integer collectionStep, long currentTimestamp) throws IllegalArgumentException { if (!unformattedUrl.startsWith(Sftp3gppUrlHandler.PROTOCOL)) { throw new IllegalArgumentException("The 3GPP SFTP Collection Handler can only use the protocol " + Sftp3gppUrlHandler.PROTOCOL); } String baseUrl = parseUrl(unformattedUrl, agent, collectionStep); return baseUrl + "&referenceTimestamp=" + currentTimestamp; } /** * Gets the 3GPP resource format. * * @param resourceType the resource type * @return the 3gpp format */ public String get3gppFormat(String resourceType) { if (m_pmGroups == null) { m_pmGroups = new Properties(); try { m_pmGroups.load(getClass().getResourceAsStream("/3gpp-pmgroups.properties")); } catch (IOException e) { log().warn("Can't load 3GPP PM Groups formats because " + e.getMessage()); } } return m_pmGroups.getProperty(resourceType); } /** * Gets the 3GPP properties based on measInfoId. * * @param format the format * @param measInfoId the measInfoId (the resource instance) * @return the properties */ public Map<String,String> get3gppProperties(String format, String measInfoId) { Map<String,String> properties = new LinkedHashMap<String,String>(); if (format != null) { String[] groups = format.split("\\|"); for (String group : groups) { String[] subgroups = group.split("/"); for (String subgroup : subgroups) { String pair[] = subgroup.split("="); if (pair.length > 1) { if (pair[1].matches("^[<].+[>]$")) { // I'm not sure how to deal with separating by | or / and avoiding separating by \/ String valueRegex = pair[1].equals("<directory path>") ? "=([^|]+)" : "=([^|/]+)"; Matcher m = Pattern.compile(pair[0] + valueRegex).matcher(measInfoId); if (m.find()) { String v = pair[1].equals("<directory path>") ? m.group(1).replaceAll("\\\\/", "/") : m.group(1); properties.put(pair[0], v); } } } } } } properties.put("label", properties.toString().replaceAll("[{}]", "")); properties.put("instance", measInfoId); return properties; } }