/*******************************************************************************
* 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.config;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.TreeMap;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.ValidationException;
import org.opennms.core.utils.ConfigFileConstants;
import org.opennms.core.utils.IPLike;
import org.opennms.core.utils.InetAddressComparator;
import org.opennms.core.utils.InetAddressUtils;
import org.opennms.core.utils.ThreadCategory;
import org.opennms.core.xml.CastorUtils;
import org.opennms.netmgt.config.wmi.Definition;
import org.opennms.netmgt.config.wmi.Range;
import org.opennms.netmgt.config.wmi.WmiConfig;
import org.opennms.protocols.wmi.WmiAgentConfig;
import org.springframework.core.io.FileSystemResource;
/**
* This class is the main repository for WMI configuration information used by
* the capabilities daemon. When this class is loaded it reads the WMI
* configuration into memory, and uses the configuration to find the
* {@link org.opennms.protocols.wmi.WmiAgentConfig WmiAgentConfig} objects for specific
* addresses. If an address cannot be located in the configuration then a
* default peer instance is returned to the caller.
*
* <strong>Note: </strong>Users of this class should make sure the
* <em>init()</em> is called before calling any other method to ensure the
* config is loaded before accessing other convenience methods.
*
* @author <a href="mailto:david@opennms.org">David Hustace </a>
* @author <a href="mailto:weave@oculan.com">Weave </a>
* @author <a href="mailto:gturner@newedgenetworks.com">Gerald Turner </a>
* @author <a href="mailto:matt.raykowski@gmail.com">Matt Raykowski</a>
*/
public class WmiPeerFactory {
/**
* The singleton instance of this factory
*/
private static WmiPeerFactory m_singleton = null;
/**
* The config class loaded from the config file
*/
private static WmiConfig m_config;
/**
* This member is set to true if the configuration file has been loaded.
*/
private static boolean m_loaded = false;
/**
* Private constructor
*
* @exception java.io.IOException
* Thrown if the specified config file cannot be read
* @exception org.exolab.castor.xml.MarshalException
* Thrown if the file does not conform to the schema.
* @exception org.exolab.castor.xml.ValidationException
* Thrown if the contents do not match the required schema.
*
* @param configFile the path to the config file to load in.
*/
private WmiPeerFactory(String configFile) throws IOException, MarshalException, ValidationException {
m_config = CastorUtils.unmarshal(WmiConfig.class, new FileSystemResource(configFile));
}
/**
* <p>Constructor for WmiPeerFactory.</p>
*
* @param stream a {@link java.io.InputStream} object.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
*/
public WmiPeerFactory(InputStream stream) throws MarshalException, ValidationException {
m_config = CastorUtils.unmarshal(WmiConfig.class, stream);
}
/**
* Load the config from the default config file and create the singleton
* instance of this factory.
*
* @exception java.io.IOException
* Thrown if the specified config file cannot be read
* @exception org.exolab.castor.xml.MarshalException
* Thrown if the file does not conform to the schema.
* @exception org.exolab.castor.xml.ValidationException
* Thrown if the contents do not match the required schema.
* @throws java.io.IOException if any.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
*/
public static synchronized void init() throws IOException, MarshalException, ValidationException {
if (m_loaded) {
// init already called - return
// to reload, reload() will need to be called
return;
}
File cfgFile = ConfigFileConstants.getFile(ConfigFileConstants.WMI_CONFIG_FILE_NAME);
log().debug("init: config file path: " + cfgFile.getPath());
m_singleton = new WmiPeerFactory(cfgFile.getPath());
m_loaded = true;
}
private static ThreadCategory log() {
return ThreadCategory.getInstance(WmiPeerFactory.class);
}
/**
* Reload the config from the default config file
*
* @exception java.io.IOException
* Thrown if the specified config file cannot be read/loaded
* @exception org.exolab.castor.xml.MarshalException
* Thrown if the file does not conform to the schema.
* @exception org.exolab.castor.xml.ValidationException
* Thrown if the contents do not match the required schema.
* @throws java.io.IOException if any.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
*/
public static synchronized void reload() throws IOException, MarshalException, ValidationException {
m_singleton = null;
m_loaded = false;
init();
}
/**
* Package-private access. Should only be used for unit testing.
*/
WmiConfig getConfig() {
return m_config;
}
/**
* Saves the current settings to disk
*
* @throws java.lang.Exception if saving settings to disk fails.
*/
public static synchronized void saveCurrent() throws Exception {
optimize();
// Marshal to a string first, then write the string to the file. This
// way the original config
// isn't lost if the XML from the marshal is hosed.
StringWriter stringWriter = new StringWriter();
Marshaller.marshal(m_config, stringWriter);
if (stringWriter.toString() != null) {
Writer fileWriter = new OutputStreamWriter(new FileOutputStream(ConfigFileConstants.getFile(ConfigFileConstants.WMI_CONFIG_FILE_NAME)), "UTF-8");
fileWriter.write(stringWriter.toString());
fileWriter.flush();
fileWriter.close();
}
reload();
}
/**
* Combine specific and range elements so that WMIPeerFactory has to spend
* less time iterating all these elements.
* TODO This really should be pulled up into PeerFactory somehow, but I'm not sure how (given that "Definition" is different for both
* SNMP and WMI. Maybe some sort of visitor methodology would work. The basic logic should be fine as it's all IP address manipulation
*
* @throws UnknownHostException
*/
static void optimize() throws UnknownHostException {
ThreadCategory log = log();
// First pass: Remove empty definition elements
for (Iterator<Definition> definitionsIterator = m_config.getDefinitionCollection().iterator();
definitionsIterator.hasNext();) {
Definition definition = definitionsIterator.next();
if (definition.getSpecificCount() == 0 && definition.getRangeCount() == 0) {
if (log.isDebugEnabled())
log.debug("optimize: Removing empty definition element");
definitionsIterator.remove();
}
}
// Second pass: Replace single IP range elements with specific elements
for (Definition definition : m_config.getDefinitionCollection()) {
synchronized(definition) {
for (Iterator<Range> rangesIterator = definition.getRangeCollection().iterator(); rangesIterator.hasNext();) {
Range range = rangesIterator.next();
if (range.getBegin().equals(range.getEnd())) {
definition.addSpecific(range.getBegin());
rangesIterator.remove();
}
}
}
}
// Third pass: Sort specific and range elements for improved XML
// readability and then combine them into fewer elements where possible
for (Iterator<Definition> defIterator = m_config.getDefinitionCollection().iterator(); defIterator.hasNext(); ) {
Definition definition = defIterator.next();
// Sort specifics
final TreeMap<InetAddress,String> specificsMap = new TreeMap<InetAddress,String>(new InetAddressComparator());
for (String specific : definition.getSpecificCollection()) {
specificsMap.put(InetAddressUtils.getInetAddress(specific), specific.trim());
}
// Sort ranges
final TreeMap<InetAddress,Range> rangesMap = new TreeMap<InetAddress,Range>(new InetAddressComparator());
for (Range range : definition.getRangeCollection()) {
rangesMap.put(InetAddressUtils.getInetAddress(range.getBegin()), range);
}
// Combine consecutive specifics into ranges
InetAddress priorSpecific = null;
Range addedRange = null;
for (final InetAddress specific : specificsMap.keySet()) {
if (priorSpecific == null) {
priorSpecific = specific;
continue;
}
if (BigInteger.ONE.equals(InetAddressUtils.difference(specific, priorSpecific)) &&
InetAddressUtils.inSameScope(specific, priorSpecific)) {
if (addedRange == null) {
addedRange = new Range();
addedRange.setBegin(InetAddressUtils.toIpAddrString(priorSpecific));
rangesMap.put(priorSpecific, addedRange);
specificsMap.remove(priorSpecific);
}
addedRange.setEnd(InetAddressUtils.toIpAddrString(specific));
specificsMap.remove(specific);
}
else {
addedRange = null;
}
priorSpecific = specific;
}
// Move specifics to ranges
for (final InetAddress specific : new ArrayList<InetAddress>(specificsMap.keySet())) {
for (final InetAddress begin : new ArrayList<InetAddress>(rangesMap.keySet())) {
if (!InetAddressUtils.inSameScope(begin, specific)) {
continue;
}
if (InetAddressUtils.toInteger(begin).subtract(BigInteger.ONE).compareTo(InetAddressUtils.toInteger(specific)) > 0) {
continue;
}
Range range = rangesMap.get(begin);
final InetAddress end = InetAddressUtils.getInetAddress(range.getEnd());
if (InetAddressUtils.toInteger(end).add(BigInteger.ONE).compareTo(InetAddressUtils.toInteger(specific)) < 0) {
continue;
}
if (
InetAddressUtils.toInteger(specific).compareTo(InetAddressUtils.toInteger(begin)) >= 0 &&
InetAddressUtils.toInteger(specific).compareTo(InetAddressUtils.toInteger(end)) <= 0
) {
specificsMap.remove(specific);
break;
}
if (InetAddressUtils.toInteger(begin).subtract(BigInteger.ONE).equals(InetAddressUtils.toInteger(specific))) {
rangesMap.remove(begin);
rangesMap.put(specific, range);
range.setBegin(InetAddressUtils.toIpAddrString(specific));
specificsMap.remove(specific);
break;
}
if (InetAddressUtils.toInteger(end).add(BigInteger.ONE).equals(InetAddressUtils.toInteger(specific))) {
range.setEnd(InetAddressUtils.toIpAddrString(specific));
specificsMap.remove(specific);
break;
}
}
}
// Combine consecutive ranges
Range priorRange = null;
InetAddress priorBegin = null;
InetAddress priorEnd = null;
for (final Iterator<InetAddress> rangesIterator = rangesMap.keySet().iterator(); rangesIterator.hasNext();) {
final InetAddress beginAddress = rangesIterator.next();
final Range range = rangesMap.get(beginAddress);
final InetAddress endAddress = InetAddressUtils.getInetAddress(range.getEnd());
if (priorRange != null) {
if (InetAddressUtils.inSameScope(beginAddress, priorEnd) && InetAddressUtils.difference(beginAddress, priorEnd).compareTo(BigInteger.ONE) <= 0) {
priorBegin = new InetAddressComparator().compare(priorBegin, beginAddress) < 0 ? priorBegin : beginAddress;
priorRange.setBegin(InetAddressUtils.toIpAddrString(priorBegin));
priorEnd = new InetAddressComparator().compare(priorEnd, endAddress) > 0 ? priorEnd : endAddress;
priorRange.setEnd(InetAddressUtils.toIpAddrString(priorEnd));
rangesIterator.remove();
continue;
}
}
priorRange = range;
priorBegin = beginAddress;
priorEnd = endAddress;
}
// Update changes made to sorted maps
definition.setSpecific(specificsMap.values().toArray(new String[0]));
definition.setRange(rangesMap.values().toArray(new Range[0]));
}
}
/**
* Return the singleton instance of this factory.
*
* @return The current factory instance.
* @throws java.lang.IllegalStateException
* Thrown if the factory has not yet been initialized.
*/
public static synchronized WmiPeerFactory getInstance() {
if (!m_loaded)
throw new IllegalStateException("The WmiPeerFactory has not been initialized");
return m_singleton;
}
/**
* <p>setInstance</p>
*
* @param singleton a {@link org.opennms.netmgt.config.WmiPeerFactory} object.
*/
public static synchronized void setInstance(WmiPeerFactory singleton) {
m_singleton = singleton;
m_loaded = true;
}
/**
* <p>getAgentConfig</p>
*
* @param agentInetAddress a {@link java.net.InetAddress} object.
* @return a {@link org.opennms.protocols.wmi.WmiAgentConfig} object.
*/
public synchronized WmiAgentConfig getAgentConfig(InetAddress agentInetAddress) {
if (m_config == null) {
return new WmiAgentConfig(agentInetAddress);
}
WmiAgentConfig agentConfig = new WmiAgentConfig(agentInetAddress);
//Now set the defaults from the m_config
setWmiAgentConfig(agentConfig, new Definition());
// Attempt to locate the node
//
Enumeration<Definition> edef = m_config.enumerateDefinition();
DEFLOOP: while (edef.hasMoreElements()) {
Definition def = edef.nextElement();
// check the specifics first
for (String saddr : def.getSpecificCollection()) {
InetAddress addr = InetAddressUtils.addr(saddr);
if (addr.equals(agentConfig.getAddress())) {
setWmiAgentConfig(agentConfig, def);
break DEFLOOP;
}
}
// check the ranges
for (Range rng : def.getRangeCollection()) {
if (InetAddressUtils.isInetAddressInRange(InetAddressUtils.str(agentConfig.getAddress()), rng.getBegin(), rng.getEnd())) {
setWmiAgentConfig(agentConfig, def );
break DEFLOOP;
}
}
// check the matching IP expressions
//
for (String ipMatch : def.getIpMatchCollection()) {
if (IPLike.matches(InetAddressUtils.str(agentInetAddress), ipMatch)) {
setWmiAgentConfig(agentConfig, def);
break DEFLOOP;
}
}
} // end DEFLOOP
if (agentConfig == null) {
Definition def = new Definition();
setWmiAgentConfig(agentConfig, def);
}
return agentConfig;
}
private void setWmiAgentConfig(WmiAgentConfig agentConfig, Definition def) {
setCommonAttributes(agentConfig, def);
agentConfig.setPassword(determinePassword(def));
}
/**
* This is a helper method to set all the common attributes in the agentConfig.
*
* @param agentConfig
* @param def
*/
private void setCommonAttributes(WmiAgentConfig agentConfig, Definition def) {
agentConfig.setRetries(determineRetries(def));
agentConfig.setTimeout((int)determineTimeout(def));
agentConfig.setUsername(determineUsername(def));
agentConfig.setPassword(determinePassword(def));
agentConfig.setDomain(determineDomain(def));
}
/**
* Helper method to search the wmi-config for the appropriate username
* @param def
* @return a string containing the username. will return the default if none is set.
*/
private String determineUsername(Definition def) {
return (def.getPassword() == null ? (m_config.getUsername() == null ? WmiAgentConfig.DEFAULT_USERNAME :m_config.getUsername()) : def.getUsername());
}
/**
* Helper method to search the wmi-config for the appropriate domain/workgroup.
* @param def
* @return a string containing the domain. will return the default if none is set.
*/
private String determineDomain(Definition def) {
return (def.getDomain() == null ? (m_config.getDomain() == null ? WmiAgentConfig.DEFAULT_DOMAIN :m_config.getDomain()) : def.getDomain());
}
/**
* Helper method to search the wmi-config for the appropriate password
* @param def
* @return a string containing the password. will return the default if none is set.
*/
private String determinePassword(Definition def) {
return (def.getPassword() == null ? (m_config.getPassword() == null ? WmiAgentConfig.DEFAULT_PASSWORD :m_config.getPassword()) : def.getPassword());
}
/**
* Helper method to search the wmi-config
* @param def
* @return a long containing the timeout, WmiAgentConfig.DEFAULT_TIMEOUT if not specified.
*/
private long determineTimeout(Definition def) {
long timeout = WmiAgentConfig.DEFAULT_TIMEOUT;
return (long)(def.getTimeout() == 0 ? (m_config.getTimeout() == 0 ? timeout : m_config.getTimeout()) : def.getTimeout());
}
private int determineRetries(Definition def) {
int retries = WmiAgentConfig.DEFAULT_RETRIES;
return (def.getRetry() == 0 ? (m_config.getRetry() == 0 ? retries : m_config.getRetry()) : def.getRetry());
}
/**
* <p>getWmiConfig</p>
*
* @return a {@link org.opennms.netmgt.config.wmi.WmiConfig} object.
*/
public static WmiConfig getWmiConfig() {
return m_config;
}
/**
* <p>setWmiConfig</p>
*
* @param m_config a {@link org.opennms.netmgt.config.wmi.WmiConfig} object.
*/
public static synchronized void setWmiConfig(WmiConfig m_config) {
WmiPeerFactory.m_config = m_config;
}
}