/*******************************************************************************
* 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 static org.opennms.core.utils.InetAddressUtils.addr;
import static org.opennms.core.utils.InetAddressUtils.isInetAddressInRange;
import static org.opennms.core.utils.InetAddressUtils.toIpAddrBytes;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.ValidationException;
import org.opennms.core.utils.ByteArrayComparator;
import org.opennms.core.utils.IpListFromUrl;
import org.opennms.core.utils.LogUtils;
import org.opennms.core.xml.CastorUtils;
import org.opennms.netmgt.config.map.adapter.Celement;
import org.opennms.netmgt.config.map.adapter.Cmap;
import org.opennms.netmgt.config.map.adapter.Csubmap;
import org.opennms.netmgt.config.map.adapter.ExcludeRange;
import org.opennms.netmgt.config.map.adapter.IncludeRange;
import org.opennms.netmgt.config.map.adapter.MapsAdapterConfiguration;
import org.opennms.netmgt.config.map.adapter.Package;
import org.opennms.netmgt.filter.FilterDaoFactory;
/**
* <p>Abstract MapsAdapterConfigManager class.</p>
*
* @author <a href="mailto:antonio@openms.it">Antonio Russo</a>
* @author <a href="mailto:brozow@openms.org">Mathew Brozowski</a>
* @author <a href="mailto:david@opennms.org">David Hustace</a>
*/
abstract public class MapsAdapterConfigManager implements MapsAdapterConfig {
private final ReadWriteLock m_globalLock = new ReentrantReadWriteLock();
private final Lock m_readLock = m_globalLock.readLock();
private final Lock m_writeLock = m_globalLock.writeLock();
/**
* <p>Constructor for MapsAdapterConfigManager.</p>
*
* @author <a href="mailto:antonio@opennms.org">Antonio Russo</a>
* @param reader a {@link java.io.InputStream} object.
* @param verifyServer a boolean.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
* @throws java.io.IOException if any.
* @param serverName a {@link java.lang.String} object.
*/
public MapsAdapterConfigManager(final InputStream reader, final String serverName, final boolean verifyServer) throws MarshalException, ValidationException, IOException {
m_localServer = serverName;
m_verifyServer = verifyServer;
reloadXML(reader);
}
/**
* The config class loaded from the config file
*/
private MapsAdapterConfiguration m_config;
/**
* A boolean flag to indicate If a filter rule against the local OpenNMS
* server has to be used.
*/
private static boolean m_verifyServer;
/**
* The name of the local OpenNMS server
*/
private static String m_localServer;
/**
* A mapping of the configured URLs to a list of the specific IPs configured
* in each - so as to avoid file reads
*/
private Map<String, List<String>> m_urlIPMap;
/**
* A mapping of the configured package to a list of IPs selected via filter
* rules, so as to avoid database access.
*/
private Map<Package, List<InetAddress>> m_pkgIpMap;
/**
* A mapping of the configured sub-maps to a list of maps
*/
private Map<String,List<String>> m_submapNameMapNameMap;
/**
* A mapping of the configured mapName to cmaps
*/
private Map<String,Cmap> m_mapNameCmapMap;
/**
* <p>Constructor for MapsAdapterConfigManager.</p>
*/
public MapsAdapterConfigManager() {
}
public Lock getReadLock() {
return m_readLock;
}
public Lock getWriteLock() {
return m_writeLock;
}
/**
* <p>reloadXML</p>
*
* @param reader a {@link java.io.InputStream} object.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
* @throws java.io.IOException if any.
*/
protected void reloadXML(final InputStream reader) throws MarshalException, ValidationException, IOException {
getWriteLock().lock();
try {
m_config = CastorUtils.unmarshal(MapsAdapterConfiguration.class, reader);
createUrlIpMap();
createPackageIpListMap();
createSubMapMapMap();
createmapNameCmapMap();
verifyMapConsistency();
verifyMapHasLoop();
} finally {
getWriteLock().unlock();
}
}
private boolean hasCmaps() {
return (m_config.getCmaps() != null);
}
/**
* Go through the maps adapter configuration and build a mapping of each
* configured map name to container cmap.
*
*/
private void createmapNameCmapMap() {
m_mapNameCmapMap = new HashMap<String, Cmap>();
if (hasCmaps()) {
for (final Cmap cmap : m_config.getCmaps().getCmapCollection()) {
m_mapNameCmapMap.put(cmap.getMapName(), cmap);
LogUtils.debugf(this, "createmapNameCmapMap: Added map: %s", cmap.getMapName());
}
}
}
/**
* Go through the maps adapter configuration and build a mapping of each
* configured sub-map to container map.
*
*/
private void createSubMapMapMap() {
m_submapNameMapNameMap = new HashMap<String, List<String>>();
if (hasCmaps()) {
for (final Cmap cmap : m_config.getCmaps().getCmapCollection()) {
for (final Csubmap csubmap : cmap.getCsubmapCollection()) {
final String subMapName = csubmap.getName();
List<String> containermaps = new ArrayList<String>();
if (m_submapNameMapNameMap.containsKey(subMapName)) {
containermaps = m_submapNameMapNameMap.get(subMapName);
}
containermaps.add(cmap.getMapName());
m_submapNameMapNameMap.put(subMapName, containermaps);
LogUtils.debugf(this, "createSubMapMapMap: added container map: %s to submap: %s", cmap.getMapName(), subMapName);
}
}
}
}
/**
* Verify that no loop are in maps definition
*/
private void verifyMapConsistency() throws ValidationException {
for (final String mapName : m_submapNameMapNameMap.keySet()) {
// verify cmap exists!
if (!cmapExist(mapName))
throw new ValidationException("Defined a submap without defining the map: mapName " + mapName);
}
}
/**
* Verify that all maps are well defined
*/
private void verifyMapHasLoop() throws ValidationException {
// TODO use the Floyd's Cycle-Finding Algorithm
/*
* String startnode;
* String slownode = startnode;
* String fastnode1 = startnode;
* String fastnode2 = startnode;
* while (slownode && fastnode1 = fastnode2.next && fastnode2 = fastnode1.next) {
* if (slownode == fastnode1 || slownode == fastnode2) return true;
* slownode.next;
* }
* return false;
*/
/* Iterator<String> ite = m_submapNameMapNameMap.keySet().iterator();
while (ite.hasNext()) {
// verify cmap exists!
String mapName = ite.next();
Iterator<String> sub_ite = m_submapNameMapNameMap.get(mapName).iterator();
while (sub_ite.hasNext()) {
String nextElementMap = sub_ite.next();
}
}
*/
}
private boolean cmapExist(final String mapName) {
if (hasCmaps()) {
for (Cmap cmap : m_config.getCmaps().getCmapCollection()) {
if (cmap.getMapName().equals(mapName)) return true;
}
}
return false;
}
/**
* Go through the maps adapter configuration and build a mapping of each
* configured URL to a list of IPs configured in that URL - done at init()
* time so that repeated file reads can be avoided
*/
private void createUrlIpMap() {
m_urlIPMap = new HashMap<String, List<String>>();
for(final Package pkg : packages()) {
for(final String url : includeURLs(pkg)) {
final List<String> iplist = IpListFromUrl.parse(url);
if (iplist.size() > 0) {
m_urlIPMap.put(url, iplist);
}
}
}
}
/**
* This method is used to establish package against iplist mapping, with
* which, the iplist is selected per package via the configured filter rules
* from the database.
*/
private void createPackageIpListMap() {
getWriteLock().lock();
try {
m_pkgIpMap = new HashMap<Package, List<InetAddress>>();
for(final Package pkg : packages()) {
// Get a list of IP addresses per package against the filter rules from
// database and populate the package, IP list map.
//
try {
final List<InetAddress> ipList = getIpList(pkg);
LogUtils.debugf(this, "createPackageIpMap: package %s: ipList size = %d", pkg.getName(), ipList.size());
if (ipList.size() > 0) {
m_pkgIpMap.put(pkg, ipList);
}
} catch (final Throwable t) {
LogUtils.errorf(this, t, "createPackageIpMap: failed to map package: %s to an IP List with filter \"%s\"", pkg.getName(), pkg.getFilter().getContent());
}
}
} finally {
getWriteLock().unlock();
}
}
private List<InetAddress> getIpList(final Package pkg) {
final StringBuffer filterRules = new StringBuffer(pkg.getFilter().getContent());
if (m_verifyServer) {
filterRules.append(" & (serverName == ");
filterRules.append('\"');
filterRules.append(m_localServer);
filterRules.append('\"');
filterRules.append(")");
}
final String rules = filterRules.toString();
LogUtils.debugf(this, "createPackageIpMap: package is %s. filter rules are %s", pkg.getName(), rules);
return FilterDaoFactory.getInstance().getActiveIPAddressList(rules);
}
/**
* This method is used to determine if the named interface is included in
* the passed package definition. If the interface belongs to the package
* then a value of true is returned. If the interface does not belong to the
* package a false value is returned.
*
* <strong>Note: </strong>Evaluation of the interface against a package
* filter will only work if the IP is already in the database.
*
* @param iface
* The interface to test against the package.
* @param pkg
* The package to check for the inclusion of the interface.
*
* @return True if the interface is included in the package, false
* otherwise.
*/
private boolean interfaceInPackage(final String iface, final Package pkg) {
boolean filterPassed = false;
// get list of IPs in this package
final List<InetAddress> ipList = m_pkgIpMap.get(pkg);
if (ipList != null && ipList.size() > 0) {
filterPassed = ipList.contains(addr(iface));
}
LogUtils.debugf(this, "interfaceInPackage: Interface %s passed filter for package %s?: %s", iface, pkg.getName(), String.valueOf(filterPassed));
if (!filterPassed)
return false;
//
// Ensure that the interface is in the specific list or
// that it is in the include range and is not excluded
//
boolean has_specific = false;
boolean has_range_include = false;
boolean has_range_exclude = false;
// if there are NO include ranges then treat act as if the user include
// the range 0.0.0.0 - 255.255.255.255
has_range_include = pkg.getIncludeRangeCount() == 0 && pkg.getSpecificCount() == 0;
for (IncludeRange rng : pkg.getIncludeRangeCollection()) {
if (isInetAddressInRange(iface, rng.getBegin(), rng.getEnd())) {
has_range_include = true;
break;
}
}
byte[] addr = toIpAddrBytes(iface);
for (String spec : pkg.getSpecificCollection()) {
byte[] speca = toIpAddrBytes(spec);
if (new ByteArrayComparator().compare(speca, addr) == 0) {
has_specific = true;
break;
}
}
final Enumeration<String> eurl = pkg.enumerateIncludeUrl();
while (!has_specific && eurl.hasMoreElements()) {
has_specific = interfaceInUrl(iface, eurl.nextElement());
}
for (ExcludeRange rng : pkg.getExcludeRangeCollection()) {
if (isInetAddressInRange(iface, rng.getBegin(), rng.getEnd())) {
has_range_exclude = true;
break;
}
}
return has_specific || (has_range_include && !has_range_exclude);
}
/**
* This method is used to determine if the named interface is included in
* the passed package's URL includes. If the interface is found in any of
* the URL files, then a value of true is returned, else a false value is
* returned.
*
* <pre>
*
* The file URL is read and each entry in this file checked. Each line
* in the URL file can be one of -
* <IP><space>#<comments>
* or
* <IP>
* or
* #<comments>
*
* Lines starting with a '#' are ignored and so are characters after
* a '<space>#' in a line.
*
* </pre>
*
* @param addr
* The interface to test against the package's URL
* @param url
* The URL file to read
*
* @return True if the interface is included in the URL, false otherwise.
*/
private boolean interfaceInUrl(final String addr, final String url) {
boolean bRet = false;
// get list of IPs in this URL
final List<String> iplist = m_urlIPMap.get(url);
if (iplist != null && iplist.size() > 0) {
bRet = iplist.contains(addr);
}
return bRet;
}
/**
* Returns a list of package names that the ip belongs to, null if none.
*
* <strong>Note: </strong>Evaluation of the interface against a package
* filter will only work if the IP is already in the database.
*
* @param ipaddr
* the interface to check
* @return a list of package names that the ip belongs to, null if none
*/
public List<String> getAllPackageMatches(final String ipaddr) {
getReadLock().lock();
try {
final List<String> matchingPkgs = new ArrayList<String>();
for(final Package pkg : packages()) {
final boolean inPkg = interfaceInPackage(ipaddr, pkg);
if (inPkg) {
matchingPkgs.add(pkg.getName());
}
}
return matchingPkgs;
} finally {
getReadLock().unlock();
}
}
/**
* <p>packages</p>
*
* @return a {@link java.lang.Iterable} object.
*/
public Iterable<Package> packages() {
getReadLock().lock();
try {
return getConfiguration().getPackageCollection();
} finally {
getReadLock().unlock();
}
}
/**
* <p>includeURLs</p>
*
* @param pkg a {@link org.opennms.netmgt.config.map.adapter.Package} object.
* @return a {@link java.lang.Iterable} object.
*/
public Iterable<String> includeURLs(final Package pkg) {
getReadLock().lock();
try {
return pkg.getIncludeUrlCollection();
} finally {
getReadLock().unlock();
}
}
/**
* Return the poller configuration object.
*
* @return a {@link org.opennms.netmgt.config.map.adapter.MapsAdapterConfiguration} object.
*/
public MapsAdapterConfiguration getConfiguration() {
getReadLock().lock();
try {
return m_config;
} finally {
getReadLock().unlock();
}
}
// methods from interface
/**
* <p>getAllMaps</p>
*
* @return a {@link java.util.List} object.
*/
public List<Cmap> getAllMaps() {
getReadLock().lock();
try {
if (hasCmaps()) {
return getConfiguration().getCmaps().getCmapCollection();
}
return Collections.emptyList();
} finally {
getReadLock().unlock();
}
}
/** {@inheritDoc} */
public Map<String, Celement> getElementByAddress(final String ipaddr) {
getReadLock().lock();
try {
final Map<String,Celement> mapAndElements = new HashMap<String, Celement>();
if (hasCmaps()) {
final List<String> pkgs = getAllPackageMatches(ipaddr);
for (final Cmap cmap : getConfiguration().getCmaps().getCmapCollection()) {
final Iterator<Celement> cels = cmap.getCelementCollection().iterator();
boolean found = false;
while (cels.hasNext()) {
final Celement celement = cels.next();
final Iterator<String> pkgname = pkgs.iterator();
while (pkgname.hasNext()) {
if (pkgname.next().equals(celement.getPackage())) {
mapAndElements.put(cmap.getMapName(), celement);
found = true;
break;
}
}
if (found) break;
}
}
}
return mapAndElements;
} finally {
getReadLock().unlock();
}
}
/** {@inheritDoc} */
public List<Csubmap> getSubMaps(final String mapName) {
getReadLock().lock();
try {
if (hasCmaps()) {
for (final Cmap cmap : getConfiguration().getCmaps().getCmapCollection()) {
if (cmap.getMapName().equals(mapName)) return cmap.getCsubmapCollection();
}
}
return Collections.emptyList();
} finally {
getReadLock().unlock();
}
}
/**
* <p>getMapElementDimension</p>
*
* @return a int.
*/
public int getMapElementDimension() {
getReadLock().lock();
try {
return getConfiguration().getElementDimension();
} finally {
getReadLock().unlock();
}
}
/** {@inheritDoc} */
public Map<String,Csubmap> getContainerMaps(final String submapName) {
getReadLock().lock();
try {
final Map<String,Csubmap> cmaps = new HashMap<String, Csubmap>();
if (m_submapNameMapNameMap.containsKey(submapName)) {
for (final String mapName : m_submapNameMapNameMap.get(submapName)) {
final Cmap cmap = m_mapNameCmapMap.get(mapName);
for (final Csubmap csubmap : cmap.getCsubmapCollection()) {
if (csubmap.getName().equals(submapName)) {
cmaps.put(mapName,csubmap);
break;
}
}
}
}
return cmaps;
} finally {
getReadLock().unlock();
}
}
/**
* <p>getsubMaps</p>
*
* @return a {@link java.util.Map} object.
*/
public Map<String, List<Csubmap>> getsubMaps() {
getReadLock().lock();
try {
final Map<String,List<Csubmap>> csubmaps = new HashMap<String, List<Csubmap>>();
if (hasCmaps()) {
for (final Cmap cmap : getConfiguration().getCmaps().getCmapCollection()) {
if (cmap.getCsubmapCount() > 0) {
csubmaps.put(cmap.getMapName(), cmap.getCsubmapCollection());
}
}
}
return Collections.unmodifiableMap(csubmaps);
} finally {
getReadLock().unlock();
}
}
/**
* <p>getCelements</p>
*
* @return a {@link java.util.Map} object.
*/
public Map<String,List<Celement>> getCelements() {
getReadLock().lock();
try {
final Map<String,List<Celement>> celements = new HashMap<String, List<Celement>>();
if (hasCmaps()) {
for (final Cmap cmap : getConfiguration().getCmaps().getCmapCollection()) {
if (cmap.getCelementCount() > 0) {
celements.put(cmap.getMapName(), cmap.getCelementCollection());
}
}
}
return Collections.unmodifiableMap(celements);
} finally {
getReadLock().unlock();
}
}
/**
* This method is used to rebuild the package against IP list mapping when
* needed. When a node gained service event occurs, poller has to determine
* which package the IP/service combination is in, but if the interface is a
* newly added one, the package IP list should be rebuilt so that poller
* could know which package this IP/service pair is in.
*/
public void rebuildPackageIpListMap() {
createPackageIpListMap();
}
}