/*
* NOTE: This copyright does *not* cover user programs that use HQ
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004, 2005, 2006], Hyperic, Inc.
* This file is part of HQ.
*
* HQ is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License as
* published by the Free Software Foundation. This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.hq.autoinventory;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.hyperic.hq.appdef.shared.AIIpValue;
import org.hyperic.hq.appdef.shared.AIPlatformValue;
import org.hyperic.hq.appdef.shared.AIServerExtValue;
import org.hyperic.hq.appdef.shared.AIServerValue;
import org.hyperic.hq.appdef.shared.AIServiceTypeValue;
import org.hyperic.hq.appdef.shared.AIServiceValue;
import org.hyperic.hq.common.SystemException;
import org.hyperic.hq.product.MeasurementInfo;
import org.hyperic.hq.product.MeasurementInfos;
import org.hyperic.util.config.ConfigOption;
import org.hyperic.util.config.ConfigResponse;
import org.hyperic.util.config.ConfigSchema;
import org.hyperic.util.config.EncodingException;
/**
* A utility class to facilitate comparisons of AI value objects.
* This is used in determining if we should send a default scan or
* a runtime scan to the server. If nothing has changed, we don't
* send the report.
*
* @see org.hyperic.hq.autoinventory.ScanState#isSameState
* @see org.hyperic.hq.product.RuntimeResourceReport#isSameReport
*/
public class AICompare {
private AICompare () {}
public static boolean compareAIPlatforms(AIPlatformValue p1,
AIPlatformValue p2) {
// NOTE: we only compare attributes that are autodiscovered.
// So, for example, we don't care that the ctime/mtime is different -
// because it will always be different. Also, the certDN is set
// later by the agent itself before sending the report, so there's
// no sense diffing on that.
if ( !compare(p1.getPlatformTypeName(), p2.getPlatformTypeName()) ||
!compare(p1.getFqdn(), p2.getFqdn()) ||
!compare(p1.getName(), p2.getName()) ||
!compare(p1.getLocation(), p2.getLocation()) ||
!compare(p1.getDescription(), p2.getDescription()) ||
!configsEqual(p1.getProductConfig(), p2.getProductConfig()) ||
!configsEqual(p1.getControlConfig(), p2.getControlConfig()) ||
!configsEqual(p1.getMeasurementConfig(), p2.getMeasurementConfig()) ||
!configsEqual(p1.getCustomProperties(), p2.getCustomProperties())) {
//!MathUtil.compare(p1.getCpuCount(), p2.getCpuCount()) ||
// System.err.println("AIC: platform attrs differ");
return false;
}
// Compare IP sets
AIIpValue[] ips1, ips2;
ips1 = p1.getAIIpValues();
ips2 = p2.getAIIpValues();
if (ips1.length != ips2.length) {
// System.err.println("AIC: IP sets different length");
return false;
}
boolean foundMatchingIP;
for (int i=0; i<ips1.length; i++) {
foundMatchingIP = false;
for (int j=0; j<ips2.length; j++) {
if (compare(ips1[i].getAddress(), ips2[j].getAddress()) &&
compare(ips1[i].getMACAddress(), ips2[j].getMACAddress()) &&
compare(ips1[i].getNetmask(), ips2[j].getNetmask())) {
foundMatchingIP = true;
break;
}
}
if (!foundMatchingIP) {
// System.err.println("AIC: no matching IP: " + ips1[i]);
return false;
}
}
AIServerValue[] sarray1, sarray2;
sarray1 = p1.getAIServerValues();
sarray2 = p2.getAIServerValues();
return compareAIServers(Arrays.asList(sarray1),
Arrays.asList(sarray2));
}
public static boolean compareAIServers ( Collection servers1,
Collection servers2 ) {
if (servers1.size() != servers2.size()) {
// System.err.println("AIC: server lengths differ");
return false;
}
boolean foundMatchingServer;
AIServerValue s1, s2;
for (Iterator i=servers1.iterator(); i.hasNext(); ) {
s1 = (AIServerValue) i.next();
foundMatchingServer = false;
for (Iterator j=servers2.iterator(); j.hasNext(); ) {
s2 = (AIServerValue) j.next();
// As above with the aiplatforms, we only care about
// autodiscovered fields, no ctime/mtime, etc.
if (configsEqual(s1.getProductConfig(), s2.getProductConfig()) &&
configsEqual(s1.getControlConfig(), s2.getControlConfig()) &&
configsEqual(s1.getMeasurementConfig(), s2.getMeasurementConfig()) &&
configsEqual(s1.getResponseTimeConfig(), s2.getResponseTimeConfig()) &&
configsEqual(s1.getCustomProperties(), s2.getCustomProperties()) &&
compare(s1.getName(), s2.getName()) &&
compare(s1.getServerTypeName(),s2.getServerTypeName()) &&
compare(s1.getDescription(), s2.getDescription()) &&
compare(s1.getAutoinventoryIdentifier(),s2.getAutoinventoryIdentifier()) &&
compare(s1.getInstallPath(), s2.getInstallPath())) {
if (s1 instanceof AIServerExtValue) {
if (s2 instanceof AIServerExtValue) {
AIServerExtValue se1 = (AIServerExtValue) s1;
AIServerExtValue se2 = (AIServerExtValue) s2;
if (se1.getPlaceholder() != se2.getPlaceholder()) {
// System.err.println("AIC: placeholder status differs");
return false;
}
AIServiceValue[] svc1, svc2;
svc1 = se1.getAIServiceValues();
svc2 = se2.getAIServiceValues();
if (!compareAIServices(svc1, svc2)) {
//System.err.println("AIC: services differ (svc1="+StringUtil.arrayToString(svc1)
// + ",svc2="+StringUtil.arrayToString(svc1)+")");
return false;
}
if(! aIServiceTypesEqual(se1.getAiServiceTypes(),se2.getAiServiceTypes())) {
return false;
}
} else {
// System.err.println("AIC: both servers are not exts");
return false;
}
} else if (s2 instanceof AIServerExtValue) {
// System.err.println("AIC: both servers are not exts (2)");
return false;
}
foundMatchingServer = true;
break;
}
}
if (!foundMatchingServer) {
// System.err.println("AIC: no matching server:"+s1);
return false;
}
}
return true;
}
public static boolean compareAIServices (AIServiceValue[] services1,
AIServiceValue[] services2) {
if (services1 == null) return (services2 == null);
if (services2 == null) return false;
if (services1.length != services2.length) {
// System.err.println("SVC_AIC: lengths differ (l1="+services1.length+",l2="+services2.length+")\n(s1="+StringUtil.arrayToString(services1)+",s2="+StringUtil.arrayToString(services2)+")");
return false;
}
boolean foundMatchingService;
AIServiceValue s1, s2;
for (int i=0; i<services1.length; i++) {
foundMatchingService = false;
s1 = services1[i];
for (int j=0; j<services2.length; j++) {
s2 = services2[j];
if (compare(s1.getServiceTypeName(), s2.getServiceTypeName()) &&
configsEqual(s1.getProductConfig(), s2.getProductConfig()) &&
configsEqual(s1.getControlConfig(), s2.getControlConfig()) &&
configsEqual(s1.getMeasurementConfig(), s2.getMeasurementConfig()) &&
configsEqual(s1.getResponseTimeConfig(), s2.getResponseTimeConfig()) &&
configsEqual(s1.getCustomProperties(), s2.getCustomProperties()) &&
compare(s1.getName(), s2.getName()) &&
compare(s1.getDescription(), s2.getDescription())) {
// System.err.println("SVC_AIC: found match for: " + s1.getName());
foundMatchingService = true;
break;
}
}
if (!foundMatchingService) {
// System.err.println("SVC_AIC: NO match for: " + s1.getName());
return false;
}
}
return true;
}
public static boolean aIServiceTypesEqual(AIServiceTypeValue[] serviceTypes1, AIServiceTypeValue[] serviceTypes2) {
if (serviceTypes1 == null)
return (serviceTypes2 == null);
if (serviceTypes2 == null)
return false;
if (serviceTypes1.length != serviceTypes2.length) {
return false;
}
boolean foundMatchingServiceType;
AIServiceTypeValue s1, s2;
for (int i = 0; i < serviceTypes1.length; i++) {
foundMatchingServiceType = false;
s1 = serviceTypes1[i];
for (int j = 0; j < serviceTypes2.length; j++) {
s2 = serviceTypes2[j];
if (aiServiceTypesEqual(s1, s2)){
foundMatchingServiceType = true;
break;
}
}
if (!foundMatchingServiceType) {
return false;
}
}
return true;
}
public static boolean aiServiceTypesEqual(AIServiceTypeValue s1, AIServiceTypeValue s2) {
return compare(s1.getDescription(), s2.getDescription()) &&
compare(s1.getName(),s2.getName()) &&
compare(s1.getServiceName(), s2.getServiceName()) &&
configsEqual(s1.getPluginClasses(), s2
.getPluginClasses()) &&
configsEqual(s1.getProperties(), s2
.getProperties()) &&
configSchemasEqual(s1.getCustomProperties(), s2
.getCustomProperties()) &&
compare(s1.getControlActions(),s2.getControlActions()) &&
measurementInfosEqual(s1.getMeasurements(), s2.getMeasurements());
}
public static boolean measurementInfosEqual(byte[] m1,byte[]m2) {
if (m1 == m2) {
return true;
}
if ((m1 == null) || (m2 == null)) {
return false;
}
//skip length comparison here - measurement infos may or may not have an attributes map, but could be equal in all ways that count
if ((m1.length == 0) && (m2.length == 0)) {
return true; // both empty
}
try {
MeasurementInfos mi1 = MeasurementInfos.decode(m1);
MeasurementInfos mi2 = MeasurementInfos.decode(m2);
if(! mi1.equals(mi2)) {
return false;
}
//we know that the measurement names are equal at this point;
Set measurementNames = mi1.getMeasurementNames();
for(Iterator iterator = measurementNames.iterator();iterator.hasNext();) {
final String measurementName = (String)iterator.next();
boolean measurementsEqual = compare(mi1.getMeasurement(measurementName),mi2.getMeasurement(measurementName));
if(! measurementsEqual) {
return false;
}
}
return true;
} catch (EncodingException e) {
throw new SystemException(e.getMessage(), e);
}
}
public static boolean configSchemasEqual(byte[] c1, byte[] c2) {
if (c1 == c2) {
return true;
}
if ((c1 == null) || (c2 == null)) {
return false;
}
if (c1.length != c2.length) {
return false;
}
if ((c1.length == 0) && (c2.length == 0)) {
return true; // both empty
}
// can't use Arrays.equals(c1, c2), order may have changed.
try {
ConfigSchema cs1 = ConfigSchema.decode(c1);
ConfigSchema cs2 = ConfigSchema.decode(c2);
boolean configNamesEqual = compare(cs1.getOptionNames(),cs2.getOptionNames());
if(! configNamesEqual) {
return false;
}
final String[] optionNames = cs1.getOptionNames();
for(int i=0;i< optionNames.length;i++) {
ConfigOption option1 = cs1.getOption(optionNames[i]);
ConfigOption option2 = cs2.getOption(optionNames[i]);
//compare only name and description, as that is all we set for custom properties in ServiceTypeFactory
if(!(compare(option1.getName(),option2.getName()) || compare(option1.getDescription(),option2.getDescription()))) {
return false;
}
}
return true;
} catch (EncodingException e) {
throw new SystemException(e.getMessage());
}
}
public static boolean configsEqual(byte[] c1, byte[] c2) {
if ((c1 == null) != (c2 == null)) {
return false;
}
if (c1 == c2 || Arrays.equals(c1, c2)) {
return true;
}
if (c1.length != c2.length) {
return false;
}
if ((c1.length == 0) && (c2.length == 0)) {
return true; //both empty
}
//can't use Arrays.equals(c1, c2), order may have changed.
try {
ConfigResponse cr1 = ConfigResponse.decode(c1);
ConfigResponse cr2 = ConfigResponse.decode(c2);
return cr1.equals(cr2);
} catch (EncodingException e) {
throw new SystemException(e.getMessage(), e);
}
}
public static class ConfigDiff {
protected ConfigResponse newConf = new ConfigResponse();
protected ConfigResponse changedConf = new ConfigResponse();
protected ConfigResponse deletedConf = new ConfigResponse();
public ConfigResponse getNewConf() {
return newConf;
}
public void setNewConf(ConfigResponse newConf) {
this.newConf = newConf;
}
public ConfigResponse getChangedConf() {
return changedConf;
}
public void setChangedConf(ConfigResponse changedConf) {
this.changedConf = changedConf;
}
public ConfigResponse getDeletedConf() {
return deletedConf;
}
public void setDeletedConf(ConfigResponse deletedConf) {
this.deletedConf = deletedConf;
}
}
public static ConfigDiff configsDiff(byte[] newConf, byte[] oldConf) throws EncodingException {
ConfigDiff res = new ConfigDiff();
if ((oldConf == null) != (newConf == null)) { // only one is empty (null)
res.setNewConf(ConfigResponse.decode(oldConf!=null?oldConf:newConf));
return res;
}
if (oldConf == newConf || Arrays.equals(oldConf, newConf)) {
return res;
}
if ((oldConf.length == 0) && (newConf.length == 0)) {
return res; //both empty
}
//can't use Arrays.equals(oldConf, newConf), order may have changed.
try {
ConfigResponse cr1 = ConfigResponse.decode(oldConf);
ConfigResponse cr2 = ConfigResponse.decode(newConf);
Map<String,String> m1 = cr1.getConfig();
Map<String,String> m2 = cr2.getConfig();
for(String k1:m1.keySet()) {
String v1 = m1.get(k1);
String v2 = m2.get(k1);
if (v2==null) {
res.getDeletedConf().setValue(k1, v1);
} else if ((v1==null && v2!=null) || (v1!=null && v2==null) || !v1.equals(v2)) {
res.getChangedConf().setValue(k1, v2);
}
}
for(String k2:m2.keySet()) {
if (!m1.containsKey(k2)) {
res.getNewConf().setValue(k2, m2.get(k2));
}
}
return res;
} catch (EncodingException e) {
throw new SystemException(e.getMessage(), e);
}
}
/**
* Compare 2 strings for equality.
* @return true if both strings are null, or if they are both non-null
* and ".equals" returns true. return false otherwise
*/
private static boolean compare(String s1, String s2) {
if (s1 == s2) {
return true;
}
if (s1 == null || s2 == null) {
return false;
}
return s1.equals(s2);
}
private static boolean compare(MeasurementInfo m1, MeasurementInfo m2) {
if (m1 == m2) {
return true;
}
if (m1 == null || m2 == null) {
return false;
}
return compare(m1.getAlias(),m2.getAlias()) && compare(m1.getCategory(),m2.getCategory()) &&
compare(m1.getGroup(),m2.getGroup()) && compare(m1.getName(),m2.getName()) && compare(m1.getRate(),m2.getRate()) &&
compare(m1.getTemplate(), m2.getTemplate()) && compare(m1.getUnits(),m2.getUnits()) && (m1.getCollectionType() == m2.getCollectionType()) &&
(m1.getInterval() == m2.getInterval()) && (m1.isDefaultOn() == m2.isDefaultOn()) && (m1.isIndicator() == m2.isIndicator());
}
/**
* Compare String arrays without regard to order of elements
* @param strArray1
* @param strArray2
* @return
*/
private static boolean compare(String[] strArray1, String[] strArray2) {
if (strArray1 == strArray2) {
return true;
}
if ((strArray1 == null) || (strArray2 == null)) {
return false;
}
Set set1 = new HashSet(Arrays.asList(strArray1));
Set set2 = new HashSet(Arrays.asList(strArray2));
return set1.equals(set2);
}
}