/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2008-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.math.BigInteger;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import org.opennms.core.utils.InetAddressUtils;
import org.opennms.core.utils.ThreadCategory;
import org.opennms.netmgt.config.snmp.Definition;
import org.opennms.netmgt.config.snmp.Range;
import org.opennms.netmgt.model.discovery.IPAddressRange;
import org.opennms.netmgt.model.discovery.IPAddressRangeSet;
/**
* This is a wrapper class for the Definition class from the config package. Has the logic for
* comparing definitions, sorting child elements, etc.
*
* @author <a href="mailto:david@opennms.org>David Hustace</a>
*
*/
final class MergeableDefinition {
/**
* This field should remaining encapsulated for there is
* synchronization in the getter.
*
*/
private final Definition m_snmpConfigDef;
private IPAddressRangeSet m_configRanges = new IPAddressRangeSet();
/**
* <p>Constructor for MergeableDefinition.</p>
*
* @param def a {@link org.opennms.netmgt.config.snmp.Definition} object.
*/
public MergeableDefinition(Definition def) {
m_snmpConfigDef = def;
for (Range r : def.getRangeCollection()) {
m_configRanges.add(new IPAddressRange(r.getBegin(), r.getEnd()));
}
for(String s : def.getSpecificCollection()) {
m_configRanges.add(new IPAddressRange(s));
}
}
public IPAddressRangeSet getAddressRanges() {
return m_configRanges;
}
/**
* This method is called when a definition is found in the config and
* that has the same attributes as the params in the configureSNMP event and
* the IP specific/range needs to be merged into the definition.
*
* @param eventDefefinition a {@link org.opennms.netmgt.config.MergeableDefinition} object.
*/
protected void mergeMatchingAttributeDef(MergeableDefinition eventDefinition) {
m_configRanges.addAll(eventDefinition.getAddressRanges());
getConfigDef().removeAllRange();
getConfigDef().removeAllSpecific();
for(IPAddressRange range : m_configRanges) {
if (range.isSingleton()) {
getConfigDef().addSpecific(range.getBegin().toUserString());
} else {
Range xmlRange = new Range();
xmlRange.setBegin(range.getBegin().toUserString());
xmlRange.setEnd(range.getEnd().toUserString());
getConfigDef().addRange(xmlRange);
}
}
// if (eventDefinition.isSpecific()) {
// handleSpecificMerge(eventDefinition.getConfigDef());
// } else {
// handleRangeMerge(eventDefinition.getConfigDef());
// }
}
/**
* Little helper method for determining if this definition
* from a configureSNMP event containts a specific IP address vs.
* a range.
*
* @return true if the number of ranges in the def is 0.
*/
public boolean isSpecific() {
boolean specific = false;
if (getConfigDef().getRangeCount() == 0) {
specific = true;
}
return specific;
}
/**
* This method handles the updating of the SnmpConfig when
* the definition to be merged contains a specific IP address.
*
* @param eventDef a definition created to represent the values
* passed in the configureSNMP event params
*/
private void handleSpecificMerge(final Definition eventDef) {
if (hasRangeMatchingSpecific(eventDef.getSpecific(0))) {
log().error("handleSpecificMerge: definition already contains a range that matches requested SNMP specific change: " + this);
} else if (hasMatchingSpecific(eventDef.getSpecific(0))) {
log().error("handleSpecificMerge: definition already contains a specific that matches requested SNMP specific change: " + this);
} else {
getConfigDef().addSpecific(eventDef.getSpecific(0));
sortSpecifics();
}
}
private ThreadCategory log() {
return ThreadCategory.getInstance(getClass());
}
/**
* This method handles the updating of the SnmpConfig when
* the definition to be merged contains a range.
*
* @param eventDef a definition created to represent the values
* passed in the configureSNMP event params
*/
private void handleRangeMerge(final Definition eventDef) {
//first remove any specifics that would be eclipsed by this new range
while (hasSpecificMatchingNewRange(eventDef.getRange(0))) {
String spec = findSpecificMatchingNewRange(eventDef.getRange(0));
getConfigDef().removeSpecific(spec);
}
mergeNewRangeIntoDef(eventDef.getRange(0));
}
/**
* Enumerates over the list of specifics in a definition
* looking for a matching specific element. Assumes isSpecific()
* returns true.
*
* @param specific IP address as a string
* @return true if def has a matching specific IP address already
*/
public boolean hasMatchingSpecific(final String specific) {
boolean specificMatches = false;
for (String defSpecific : getConfigDef().getSpecificCollection()) {
if (defSpecific.equals(specific)) {
specificMatches = true;
break;
}
}
return specificMatches;
}
/**
* Simple method to add readability
*
* @param specific IP address
* @return true if the definition has a range covering
* the specified specific IP address
*/
public boolean hasRangeMatchingSpecific(final String specific) {
return (findRangeMatchingSpecific(specific) != null);
}
/**
* Analyzes the definition to determine if one of its ranges
* matches the specified specific IP address
*
* @param specific a {@link java.lang.String} object.
* @return the matching range
*/
public Range findRangeMatchingSpecific(final String specific) {
Range matchingRange = null;
for (Range erange : getConfigDef().getRangeCollection()) {
MergeableRange range = new MergeableRange(erange);
if (range.coversSpecific(specific)) {
matchingRange = range.getRange();
break;
}
}
return matchingRange;
}
/**
* Responsible for merging new ranges in to this definition.
*
* @param newRange a {@link org.opennms.netmgt.config.common.Range} object.
*/
public void mergeNewRangeIntoDef(final Range newRange) {
purgeEclipsedRangesInDef(newRange);
if (getConfigDef().getRangeCount() == 0) {
getConfigDef().addRange(newRange);
} else {
boolean overlapped = mergeOverlappingRanges(newRange);
if(!overlapped) {
getConfigDef().addRange(newRange);
}
}
}
/**
* Removes in ranges in the defintion that are eclipsed
* by the new range.
*
* @param range a {@link org.opennms.netmgt.config.common.Range} object.
*/
public void purgeEclipsedRangesInDef(final Range range) {
Range[] ranges = getConfigDef().getRange();
MergeableRange newRange = new MergeableRange(range);
for (int i = 0; i < ranges.length; i++) {
Range rng = ranges[i];
if (newRange.eclipses(rng)) {
getConfigDef().removeRange(rng);
}
}
}
/**
* The passed range is evaluated against the existing ranges in the
* definition and updates the def range if it overlaps or eclipses
* a def range.
*
* @param range a {@link org.opennms.netmgt.config.common.Range} object.
* @return the state of having updated any ranges in the definition
* due to being affected by the new range.
*/
public boolean mergeOverlappingRanges(final Range range) {
boolean overlapped = false;
sortRanges();
MergeableRange newRange = new MergeableRange(range);
Range[] ranges = getConfigDef().getRange();
for (int i = 0; i < ranges.length; i++) {
Range defRange = ranges[i];
if (newRange.equals(defRange)) {
overlapped = true;
} else if (newRange.overlapsBegin(defRange)) {
defRange.setBegin(newRange.getRange().getBegin());
overlapped = true;
} else if (newRange.overlapsEnd(defRange)) {
defRange.setEnd(newRange.getRange().getEnd());
overlapped = true;
} else if (newRange.eclipses(defRange)) {
defRange.setBegin(newRange.getRange().getBegin());
defRange.setEnd(newRange.getRange().getEnd());
overlapped = true;
}
}
sortRanges();
return overlapped;
}
/**
* Sorts the specifics in the current wrapped definition.
*/
public void sortSpecifics() {
String[] specifics = getConfigDef().getSpecific();
Arrays.sort(specifics, new SpecificComparator());
getConfigDef().setSpecific(specifics);
}
/**
* Sorts ranges with the current wrapped definition.
*/
public void sortRanges() {
Range[] ranges = getConfigDef().getRange();
Arrays.sort(ranges, new RangeComparator());
getConfigDef().setRange(ranges);
}
/**
* <p>hasSpecificMatchingNewRange</p>
*
* @param eventRange a {@link org.opennms.netmgt.config.common.Range} object.
* @return a boolean.
*/
public boolean hasSpecificMatchingNewRange(final Range eventRange) {
return (findSpecificMatchingNewRange(eventRange) != null);
}
/**
* <p>findSpecificMatchingNewRange</p>
*
* @param eventRange a {@link org.opennms.netmgt.config.common.Range} object.
* @return a {@link java.lang.String} object.
*/
public String findSpecificMatchingNewRange(final Range eventRange) {
String matchingSpecific = null;
for (String spec : getConfigDef().getSpecificCollection()) {
MergeableRange range = new MergeableRange(eventRange);
if (range.coversSpecific(spec)) {
matchingSpecific = spec;
break;
}
}
return matchingSpecific;
}
/**
* <p>getConfigDef</p>
*
* @return a {@link org.opennms.netmgt.config.snmp.Definition} object.
*/
final public Definition getConfigDef() {
return m_snmpConfigDef;
}
final private boolean compareStrings(String str1, String str2) {
boolean match = false;
if (str1 == null && str2 == null) {
match = true;
} else if (str1 == null || str2 == null) {
match = false;
} else if (str1.equals(str2)) {
match = true;
}
return match;
}
/**
* Removes the specified specific from the wrapped definition.
*
* @param specific a {@link java.lang.String} object.
*/
public void purgeSpecificFromDef(final String specific) {
String[] specs = getConfigDef().getSpecific();
for (int i = 0; i < specs.length; i++) {
String spec = specs[i];
if (spec.equals(specific)) {
getConfigDef().removeSpecific(spec);
}
}
Range[] ranges = getConfigDef().getRange();
for (int i = 0; i < ranges.length; i++) {
MergeableRange range = new MergeableRange(ranges[i]);
if (range.coversSpecific(specific)) {
Range newRange = range.removeSpecificFromRange(specific);
if (newRange != null) {
getConfigDef().addRange(newRange);
}
}
}
}
/**
* Call this method to optimize the specifics in the wrapped definition.
*/
public void optimizeSpecifics() {
adjustRangesWithAdjacentSpecifics();
removeSpecificsEclipsedByRanges();
convertAdjacentSpecficsIntoRange();
}
/**
* This method moves specifics that are adjacent to ranges in to the range.
*/
private void adjustRangesWithAdjacentSpecifics() {
sortSpecifics();
sortRanges();
String[] specifics = getConfigDef().getSpecific();
for (int i = 0; i < specifics.length; i++) {
MergeableSpecific specific = new MergeableSpecific(specifics[i]);
for (Range range : getConfigDef().getRangeCollection()) {
if (new BigInteger("-1").equals(InetAddressUtils.difference(specific.getSpecific(), range.getBegin()))) {
getConfigDef().removeSpecific(specific.getSpecific());
range.setBegin(specific.getSpecific());
} else if (new BigInteger("1").equals(InetAddressUtils.difference(specific.getSpecific(), range.getEnd()))) {
getConfigDef().removeSpecific(specific.getSpecific());
range.setEnd(specific.getSpecific());
}
}
}
sortSpecifics();
sortRanges();
}
/**
* This method removes any specifics from the wrapped definition that are covered one of its ranges.
*
*/
private void removeSpecificsEclipsedByRanges() {
sortSpecifics();
String[] specifics = getConfigDef().getSpecific();
for (int i = 0; i < specifics.length; i++) {
String specific = specifics[i];
if (hasRangeMatchingSpecific(specific)) {
getConfigDef().removeSpecific(specific);
}
}
sortSpecifics();
}
/**
* Converts specifics into ranges when their diffs are abs() 1
*/
private void convertAdjacentSpecficsIntoRange() {
sortSpecifics();
List<String> specificList = new ArrayList<String>(new LinkedHashSet<String>(getConfigDef().getSpecificCollection()));
List<Range> definedRanges = Arrays.asList(getConfigDef().getRange());
ArrayList<Range> newRanges = new ArrayList<Range>();
if (specificList.size() > 1) {
for (ListIterator<String> it = specificList.listIterator(); it.hasNext();) {
final MergeableSpecific specific = new MergeableSpecific(it.next());
final Range newRange = new Range();
newRange.setBegin(specific.getSpecific());
while (it.hasNext()) {
String nextSpecific = (String)it.next();
if (new BigInteger("-1").equals(InetAddressUtils.difference(specific.getSpecific(), nextSpecific))) {
newRange.setEnd(nextSpecific);
getConfigDef().removeSpecific(specific.getSpecific());
getConfigDef().removeSpecific(nextSpecific);
specific.setSpecific(nextSpecific);
} else {
it.previous();
break;
}
}
if (newRange.getEnd() != null) {
newRanges.add(newRange);
}
}
newRanges.addAll(definedRanges);
getConfigDef().setRange(newRanges);
sortRanges();
}
}
/**
* Removes the specifics and ranges covered by the range specified in the parameter from the current wrapped definition.
*
* @param eventRange a {@link org.opennms.netmgt.config.common.Range} object.
*/
public void purgeRangeFromDef(final Range eventRange) {
MergeableRange range = new MergeableRange(eventRange);
sortSpecifics();
String[] specs = getConfigDef().getSpecific();
for (int i = 0; i < specs.length; i++) {
String spec = specs[i];
if (range.coversSpecific(spec)) {
getConfigDef().removeSpecific(spec);
}
}
sortRanges();
Range[] ranges = getConfigDef().getRange();
for (int i = 0; i < ranges.length; i++) {
Range defRng = ranges[i];
try {
if (range.eclipses(defRng)) {
getConfigDef().removeRange(defRng);
} else if (range.withInRange(defRng)) {
Range newRange = new Range();
newRange.setBegin(InetAddressUtils.incr(range.getLast().getSpecific()));
newRange.setEnd(defRng.getEnd());
getConfigDef().addRange(newRange);
defRng.setEnd(InetAddressUtils.decr(range.getFirst().getSpecific()));
} else if (range.overlapsBegin(defRng)) {
defRng.setBegin(InetAddressUtils.incr(range.getLast().getSpecific()));
} else if (range.overlapsEnd(defRng)) {
defRng.setEnd(InetAddressUtils.decr(range.getFirst().getSpecific()));
}
} catch (UnknownHostException e) {
ThreadCategory.getInstance(getClass()).error("Error converting string to IP address: " + e.getMessage(), e);
}
}
}
/**
* Optimizes the ranges in the wrapped definition by making the definition as small and
* as ordered as possible.
*/
public void optimizeRanges() {
sortRanges();
Range[] ranges = getConfigDef().getRange();
for (int i = 0; i < ranges.length-1; i++) {
Range firstRange = ranges[i];
Range nextRange = ranges[i+1];
optimizeAdjacentRanges(firstRange, nextRange);
}
optimizeZeroLengthRanges();
}
/**
* Converts ranges in the wrapped defintion that have equal begin and end addresses.
*/
private void optimizeZeroLengthRanges() {
Range[] ranges = getConfigDef().getRange();
for (int i = 0; i < ranges.length; i++) {
Range range = ranges[i];
if (range.getBegin().equals(range.getEnd())) {
if (!hasMatchingSpecific(range.getBegin())) {
getConfigDef().addSpecific(range.getBegin());
}
getConfigDef().removeRange(range);
}
}
}
/**
* Adjusts 2 ordered ranges in the wrapped definition by determining if the nextRange is
* either eclipsed, or overlapped by its previousRange.
*
* @param previousRange
* @param nextRange
*/
private void optimizeAdjacentRanges(final Range previousRange, final Range nextRange) {
MergeableRange range = new MergeableRange(previousRange);
if (range.equals(nextRange)) {
getConfigDef().removeRange(previousRange);
} else if (range.eclipses(nextRange)) {
//We have to do this because of the side effects of object references
nextRange.setBegin(previousRange.getBegin());
nextRange.setEnd(previousRange.getEnd());
getConfigDef().removeRange(previousRange);
} else if (range.isAdjacentToBegin(nextRange) || range.overlapsBegin(nextRange)){
nextRange.setBegin(previousRange.getBegin());
getConfigDef().removeRange(previousRange);
} else if (range.isAdjacentToEnd(nextRange) || range.overlapsEnd(nextRange)) {
//this "probably" should never happen
nextRange.setEnd(previousRange.getEnd());
getConfigDef().removeRange(previousRange);
}
}
boolean matches(MergeableDefinition other) {
boolean compares = compareStrings(getConfigDef().getReadCommunity(), other.getConfigDef().getReadCommunity())
&& getConfigDef().getPort() == other.getConfigDef().getPort()
&& getConfigDef().getRetry() == other.getConfigDef().getRetry()
&& getConfigDef().getTimeout() == other.getConfigDef().getTimeout()
&& compareStrings(getConfigDef().getVersion(), other.getConfigDef().getVersion());
return compares;
}
boolean isEmpty(String s) {
return s == null || "".equals(s.trim());
}
boolean isTrivial() {
return isEmpty(getConfigDef().getReadCommunity())
&& isEmpty(getConfigDef().getVersion())
&& !getConfigDef().hasPort()
&& !getConfigDef().hasRetry()
&& !getConfigDef().hasTimeout();
}
void removeRanges(MergeableDefinition eventDefinition) {
m_configRanges.removeAll(eventDefinition.getAddressRanges());
getConfigDef().removeAllRange();
getConfigDef().removeAllSpecific();
for(IPAddressRange r : m_configRanges) {
if (r.isSingleton()) {
getConfigDef().addSpecific(r.getBegin().toUserString());
} else {
Range xmlRange = new Range();
xmlRange.setBegin(r.getBegin().toUserString());
xmlRange.setEnd(r.getEnd().toUserString());
getConfigDef().addRange(xmlRange);
}
}
// if (eventDefinition.isSpecific()) {
// purgeSpecificFromDef(eventDefinition.getConfigDef().getSpecific(0));
// } else {
// purgeRangeFromDef(eventDefinition.getConfigDef().getRange(0));
// }
}
boolean isEmpty() {
return getConfigDef().getRangeCount() < 1 && getConfigDef().getSpecificCount() < 1;
}
}