/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2007-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.web.svclayer.support; import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.hibernate.criterion.CriteriaSpecification; import org.hibernate.criterion.Criterion; import org.hibernate.criterion.Disjunction; import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.Order; import org.hibernate.criterion.Restrictions; import org.hibernate.type.StringType; import org.opennms.core.utils.ByteArrayComparator; import org.opennms.core.utils.InetAddressUtils; import org.opennms.netmgt.config.siteStatusViews.Category; import org.opennms.netmgt.config.siteStatusViews.RowDef; import org.opennms.netmgt.config.siteStatusViews.Rows; import org.opennms.netmgt.config.siteStatusViews.View; import org.opennms.netmgt.dao.CategoryDao; import org.opennms.netmgt.dao.NodeDao; import org.opennms.netmgt.dao.SiteStatusViewConfigDao; import org.opennms.netmgt.model.OnmsArpInterface; import org.opennms.netmgt.model.OnmsCriteria; import org.opennms.netmgt.model.OnmsIpInterface; import org.opennms.netmgt.model.OnmsNode; import org.opennms.netmgt.model.OnmsRestrictions; import org.opennms.netmgt.model.OnmsSnmpInterface; import org.opennms.web.command.NodeListCommand; import org.opennms.web.svclayer.AggregateStatus; import org.opennms.web.svclayer.NodeListService; import org.opennms.web.svclayer.support.NodeListModel.NodeModel; import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.util.Assert; /** * <p>DefaultNodeListService class.</p> * * @author <a href="mailto:dj@opennms.org">DJ Gregor</a> * @author <a href="mailto:ayres@opennms.org">Bill Ayres</a> */ public class DefaultNodeListService implements NodeListService, InitializingBean { private static final Comparator<OnmsIpInterface> IP_INTERFACE_COMPARATOR = new IpInterfaceComparator(); private static final Comparator<OnmsArpInterface> ARP_INTERFACE_COMPARATOR = new ArpInterfaceComparator(); private static final Comparator<OnmsSnmpInterface> SNMP_INTERFACE_COMPARATOR = new SnmpInterfaceComparator(); private NodeDao m_nodeDao; private CategoryDao m_categoryDao; private SiteStatusViewConfigDao m_siteStatusViewConfigDao; /** {@inheritDoc} */ public NodeListModel createNodeList(NodeListCommand command) { Collection<OnmsNode> onmsNodes = null; /* * All search queries can be done solely with * criteria, so we build a common criteria object with common * restrictions and sort options. Each specific search query * adds its own crtieria restrictions (if any). * * A set of booleans is maintained for aliases that might be * added in muliple places to ensure we don't add the same alias * multiple times. */ OnmsCriteria criteria = new OnmsCriteria(OnmsNode.class, "node"); criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY); criteria.add(Restrictions.ne("node.type", "D")); // Add additional criteria based on the command object addCriteriaForCommand(criteria, command); criteria.addOrder(Order.asc("node.label")); onmsNodes = m_nodeDao.findMatching(criteria); if (command.getNodesWithDownAggregateStatus()) { AggregateStatus as = new AggregateStatus(onmsNodes); onmsNodes = as.getDownNodes(); } return createModelForNodes(command, onmsNodes); } private void addCriteriaForCommand(OnmsCriteria criteria, NodeListCommand command) { if (command.hasNodename()) { addCriteriaForNodename(criteria, command.getNodename()); } else if (command.hasNodeId()) { addCriteriaForNodeId(criteria, command.getNodeId()); } else if (command.hasIplike()) { addCriteriaForIpLike(criteria, command.getIplike()); } else if (command.hasService()) { addCriteriaForService(criteria, command.getService()); } else if (command.hasMaclike()) { addCriteriaForMaclike(criteria, command.getMaclike()); } else if (command.hasSnmpParm() &&command.hasSnmpParmValue() && command.hasSnmpParmMatchType()) { addCriteriaForSnmpParm(criteria, command.getSnmpParm(), command.getSnmpParmValue(), command.getSnmpParmMatchType()); } else if (command.hasCategory1() && command.hasCategory2()) { addCriteriaForCategories(criteria, command.getCategory1(), command.getCategory2()); } else if (command.hasCategory1()) { addCriteriaForCategories(criteria, command.getCategory1()); } else if (command.hasStatusViewName() && command.hasStatusSite() && command.hasStatusRowLabel()) { addCriteriaForSiteStatusView(criteria, command.getStatusViewName(), command.getStatusSite(), command.getStatusRowLabel()); }else if(command.hasForeignSource()) { addCriteriaForForeignSource(criteria, command.getForeignSource()); }else { // Do nothing.... don't add any restrictions other than the default ones } if (command.getNodesWithOutages()) { addCriteriaForCurrentOutages(criteria); } } private void addCriteriaForSnmpParm(OnmsCriteria criteria, String snmpParm, String snmpParmValue, String snmpParmMatchType) { criteria.createAlias("node.ipInterfaces", "ipInterface"); criteria.add(Restrictions.ne("ipInterface.isManaged", "D")); criteria.createAlias("node.snmpInterfaces", "snmpInterface"); criteria.add(Restrictions.ne("snmpInterface.collect", "D")); if(snmpParmMatchType.equals("contains")) { criteria.add(Restrictions.ilike("snmpInterface.".concat(snmpParm), snmpParmValue, MatchMode.ANYWHERE)); } else if(snmpParmMatchType.equals("equals")) { snmpParmValue = snmpParmValue.toLowerCase(); criteria.add(Restrictions.sqlRestriction("{alias}.nodeid in (select nodeid from snmpinterface where snmpcollect != 'D' and lower(snmp" + snmpParm + ") = '" + snmpParmValue + "')")); } } private void addCriteriaForCurrentOutages(OnmsCriteria criteria) { /* * This doesn't work properly if ipInterfaces and/or * monitoredServices have other restrictions. If we are * matching on service ID = 7, but service ID = 1 has an * outage, the node won't show up. */ /* criteria.createAlias("ipInterfaces", "ipInterface"); criteria.createAlias("ipInterfaces.monitoredServices", "monitoredService"); criteria.createAlias("ipInterfaces.monitoredServices.currentOutages", "currentOutages"); criteria.add(Restrictions.isNull("currentOutages.ifRegainedService")); criteria.add(Restrictions.or(Restrictions.isNull("currentOutages.suppressTime"), Restrictions.lt("currentOutages.suppressTime", new Date()))); */ // This SQL restriction does work fine, however criteria.add(Restrictions.sqlRestriction("{alias}.nodeId in (select o.nodeId from outages o where o.ifregainedservice is null and o.suppresstime is null or o.suppresstime < now())")); } private void addCriteriaForNodename(OnmsCriteria criteria, String nodeName) { criteria.add(Restrictions.ilike("node.label", nodeName, MatchMode.ANYWHERE)); } private void addCriteriaForNodeId(OnmsCriteria criteria, int nodeId) { criteria.add(Restrictions.idEq(nodeId)); } private void addCriteriaForForeignSource(OnmsCriteria criteria, String foreignSource) { criteria.add(Restrictions.ilike("node.foreignSource", foreignSource, MatchMode.ANYWHERE)); } private void addCriteriaForIpLike(OnmsCriteria criteria, String iplike) { OnmsCriteria ipInterface = criteria.createCriteria("node.ipInterfaces", "ipInterface"); ipInterface.add(Restrictions.ne("isManaged", "D")); ipInterface.add(OnmsRestrictions.ipLike(iplike)); } private void addCriteriaForService(OnmsCriteria criteria, int serviceId) { criteria.createAlias("node.ipInterfaces", "ipInterface"); criteria.add(Restrictions.ne("ipInterface.isManaged", "D")); criteria.createAlias("node.ipInterfaces.monitoredServices", "monitoredService"); criteria.createAlias("node.ipInterfaces.monitoredServices.serviceType", "serviceType"); criteria.add(Restrictions.eq("serviceType.id", serviceId)); criteria.add(Restrictions.ne("monitoredService.status", "D")); } private void addCriteriaForMaclike(OnmsCriteria criteria, String macLike) { String macLikeStripped = macLike.replaceAll("[:-]", ""); criteria.createAlias("node.snmpInterfaces", "snmpInterface", CriteriaSpecification.LEFT_JOIN); criteria.createAlias("node.arpInterfaces", "arpInterface", CriteriaSpecification.LEFT_JOIN); Disjunction physAddrDisjunction = Restrictions.disjunction(); physAddrDisjunction.add(Restrictions.ilike("snmpInterface.physAddr", macLikeStripped, MatchMode.ANYWHERE)); physAddrDisjunction.add(Restrictions.ilike("arpInterface.physAddr", macLikeStripped, MatchMode.ANYWHERE)); criteria.add(physAddrDisjunction); // This is an alternative to the above code if we need to use the out-of-the-box DetachedCriteria which doesn't let us specify the join type /* String propertyName = "nodeId"; String value = "%" + macLikeStripped + "%"; Disjunction physAddrDisjuction = Restrictions.disjunction(); physAddrDisjuction.add(Restrictions.sqlRestriction("{alias}." + propertyName + " IN (SELECT nodeid FROM snmpinterface WHERE snmpphysaddr LIKE ? )", value, new StringType())); physAddrDisjuction.add(Restrictions.sqlRestriction("{alias}." + propertyName + " IN (SELECT nodeid FROM atinterface WHERE atphysaddr LIKE ? )", value, new StringType())); criteria.add(physAddrDisjuction); */ } // This doesn't work because we can only join a specific table once with criteria /* public void addCriteriaForCategories(OnmsCriteria criteria, String[] ... categories) { Assert.notNull(categories, "categories argument must not be null"); Assert.isTrue(categories.length >= 1, "categories must have at least one set of categories"); for (String[] categoryStrings : categories) { for (String categoryString : categoryStrings) { OnmsCategory category = m_categoryDao.findByName(categoryString); if (category == null) { throw new IllegalArgumentException("Could not find category for name '" + categoryString + "'"); } } } int categoryCount = 0; for (String[] categoryStrings : categories) { OnmsCriteria categoriesCriteria = criteria.createCriteria("categories", "category" + categoryCount++); categoriesCriteria.add(Restrictions.in("name", categoryStrings)); } } */ private void addCriteriaForCategories(OnmsCriteria criteria, String[]... categories) { Assert.notNull(criteria, "criteria argument must not be null"); for (Criterion criterion : m_categoryDao.getCriterionForCategorySetsUnion(categories)) { criteria.add(criterion); } } private void addCriteriaForSiteStatusView(OnmsCriteria criteria, String statusViewName, String statusSite, String rowLabel) { View view = m_siteStatusViewConfigDao.getView(statusViewName); RowDef rowDef = getRowDef(view, rowLabel); Set<String> categoryNames = getCategoryNamesForRowDef(rowDef); addCriteriaForCategories(criteria, categoryNames.toArray(new String[categoryNames.size()])); String sql = "{alias}.nodeId in (select nodeId from assets where " + view.getColumnName() + " = ?)"; criteria.add(Restrictions.sqlRestriction(sql, statusSite, new StringType())); } private RowDef getRowDef(View view, String rowLabel) { Rows rows = view.getRows(); Collection<RowDef> rowDefs = rows.getRowDefCollection(); for (RowDef rowDef : rowDefs) { if (rowDef.getLabel().equals(rowLabel)) { return rowDef; } } throw new DataRetrievalFailureException("Unable to locate row: "+rowLabel+" for status view: "+view.getName()); } private Set<String> getCategoryNamesForRowDef(RowDef rowDef) { Set<String> categories = new LinkedHashSet<String>(); List<Category> cats = rowDef.getCategoryCollection(); for (Category cat : cats) { categories.add(cat.getName()); } return categories; } private NodeListModel createModelForNodes(NodeListCommand command, Collection<OnmsNode> onmsNodes) { int interfaceCount = 0; List<NodeModel> displayNodes = new LinkedList<NodeModel>(); for (OnmsNode node : onmsNodes) { List<OnmsIpInterface> displayInterfaces = new LinkedList<OnmsIpInterface>(); List<OnmsArpInterface> displayArpInterfaces = new LinkedList<OnmsArpInterface>(); List<OnmsSnmpInterface> displaySnmpInterfaces = new LinkedList<OnmsSnmpInterface>(); if (command.getListInterfaces()) { if (command.hasSnmpParm() && command.getSnmpParmMatchType().equals("contains")) { String parmValueMatchString = (".*" + command.getSnmpParmValue().toLowerCase().replaceAll("([\\W])", "\\\\$0").replaceAll("\\\\%", ".*").replaceAll("_", ".") + ".*"); if (command.getSnmpParm().equals("ifAlias")) { for (OnmsSnmpInterface snmpIntf : node.getSnmpInterfaces()) { if (snmpIntf != null && !"D".equals(snmpIntf.getCollect()) && snmpIntf.getIfAlias() != null && snmpIntf.getIfAlias().toLowerCase().matches(parmValueMatchString)) { displaySnmpInterfaces.add(snmpIntf); } } } else if (command.getSnmpParm().equals("ifName")) { for (OnmsSnmpInterface snmpIntf : node.getSnmpInterfaces()) { if (snmpIntf != null && !"D".equals(snmpIntf.getCollect()) &&snmpIntf.getIfName() != null && snmpIntf.getIfName().toLowerCase().matches(parmValueMatchString)) { displaySnmpInterfaces.add(snmpIntf); } } } else if (command.getSnmpParm().equals("ifDescr")) { for (OnmsSnmpInterface snmpIntf : node.getSnmpInterfaces()) { if (snmpIntf != null && !"D".equals(snmpIntf.getCollect()) &&snmpIntf.getIfDescr() != null && snmpIntf.getIfDescr().toLowerCase().matches(parmValueMatchString)) { displaySnmpInterfaces.add(snmpIntf); } } } } else if (command.hasSnmpParm() && command.getSnmpParmMatchType().equals("equals")) { if (command.getSnmpParm().equals("ifAlias")) { for (OnmsSnmpInterface snmpIntf : node.getSnmpInterfaces()) { if (snmpIntf != null && !"D".equals(snmpIntf.getCollect()) && snmpIntf.getIfAlias() != null && snmpIntf.getIfAlias().equalsIgnoreCase(command.getSnmpParmValue())) { displaySnmpInterfaces.add(snmpIntf); } } } else if (command.getSnmpParm().equals("ifName")) { for (OnmsSnmpInterface snmpIntf : node.getSnmpInterfaces()) { if (snmpIntf != null && !"D".equals(snmpIntf.getCollect()) &&snmpIntf.getIfName() != null && snmpIntf.getIfName().equalsIgnoreCase(command.getSnmpParmValue())) { displaySnmpInterfaces.add(snmpIntf); } } } else if (command.getSnmpParm().equals("ifDescr")) { for (OnmsSnmpInterface snmpIntf : node.getSnmpInterfaces()) { if (snmpIntf != null && !"D".equals(snmpIntf.getCollect()) &&snmpIntf.getIfDescr() != null && snmpIntf.getIfDescr().equalsIgnoreCase(command.getSnmpParmValue())) { displaySnmpInterfaces.add(snmpIntf); } } } } else if (command.hasMaclike()) { String macLikeStripped = command.getMaclike().toLowerCase().replaceAll("[:-]", ""); for (OnmsIpInterface intf : node.getIpInterfaces()) { if (intf.getSnmpInterface() != null &&intf.getSnmpInterface().getPhysAddr() != null && intf.getSnmpInterface().getPhysAddr().toLowerCase().contains(macLikeStripped)) { displayInterfaces.add(intf); } } for (OnmsSnmpInterface snmpIntf : node.getSnmpInterfaces()) { if (snmpIntf.getPhysAddr() != null && !"D".equals(snmpIntf.getCollect()) && snmpIntf.getPhysAddr().toLowerCase().contains(macLikeStripped)) { OnmsIpInterface intf = snmpIntf.getPrimaryIpInterface(); if (intf == null) { displaySnmpInterfaces.add(snmpIntf); } } } for (OnmsArpInterface aint : node.getArpInterfaces()) { if (aint.getPhysAddr() != null && aint.getPhysAddr().toLowerCase().contains(macLikeStripped)) { OnmsIpInterface intf = node.getIpInterfaceByIpAddress(aint.getIpAddress()); if ((intf == null || intf.getSnmpInterface() == null || intf.getSnmpInterface().getPhysAddr() == null || !intf.getSnmpInterface().getPhysAddr().equalsIgnoreCase(aint.getPhysAddr()))) { displayArpInterfaces.add(aint); } } } } else { for (OnmsIpInterface intf : node.getIpInterfaces()) { if (!"D".equals(intf.getIsManaged()) && !"0.0.0.0".equals(InetAddressUtils.str(intf.getIpAddress()))) { displayInterfaces.add(intf); } } } } Collections.sort(displayInterfaces, IP_INTERFACE_COMPARATOR); Collections.sort(displayArpInterfaces, ARP_INTERFACE_COMPARATOR); Collections.sort(displaySnmpInterfaces, SNMP_INTERFACE_COMPARATOR); displayNodes.add(new NodeListModel.NodeModel(node, displayInterfaces, displayArpInterfaces, displaySnmpInterfaces)); interfaceCount += displayInterfaces.size(); interfaceCount += displayArpInterfaces.size(); interfaceCount += displaySnmpInterfaces.size(); } return new NodeListModel(displayNodes, interfaceCount); } /** * <p>getCategoryDao</p> * * @return a {@link org.opennms.netmgt.dao.CategoryDao} object. */ public CategoryDao getCategoryDao() { return m_categoryDao; } /** * <p>setCategoryDao</p> * * @param categoryDao a {@link org.opennms.netmgt.dao.CategoryDao} object. */ public void setCategoryDao(CategoryDao categoryDao) { m_categoryDao = categoryDao; } /** * <p>getNodeDao</p> * * @return a {@link org.opennms.netmgt.dao.NodeDao} object. */ public NodeDao getNodeDao() { return m_nodeDao; } /** * <p>setNodeDao</p> * * @param nodeDao a {@link org.opennms.netmgt.dao.NodeDao} object. */ public void setNodeDao(NodeDao nodeDao) { m_nodeDao = nodeDao; } /** * <p>getSiteStatusViewConfigDao</p> * * @return a {@link org.opennms.netmgt.dao.SiteStatusViewConfigDao} object. */ public SiteStatusViewConfigDao getSiteStatusViewConfigDao() { return m_siteStatusViewConfigDao; } /** * <p>setSiteStatusViewConfigDao</p> * * @param siteStatusViewConfigDao a {@link org.opennms.netmgt.dao.SiteStatusViewConfigDao} object. */ public void setSiteStatusViewConfigDao(SiteStatusViewConfigDao siteStatusViewConfigDao) { m_siteStatusViewConfigDao = siteStatusViewConfigDao; } /** * <p>afterPropertiesSet</p> * * @throws java.lang.Exception if any. */ @Override public void afterPropertiesSet() throws Exception { Assert.state(m_nodeDao != null, "nodeDao property cannot be null"); Assert.state(m_categoryDao != null, "categoryDao property cannot be null"); Assert.state(m_siteStatusViewConfigDao != null, "siteStatusViewConfigDao property cannot be null"); } public static class IpInterfaceComparator implements Comparator<OnmsIpInterface>, Serializable { /** * */ private static final long serialVersionUID = 1538654897829381114L; public int compare(final OnmsIpInterface o1, final OnmsIpInterface o2) { int diff; // Sort by IP first if the IPs are non-0.0.0.0 final String o1ip = InetAddressUtils.str(o1.getIpAddress()); final String o2ip = InetAddressUtils.str(o2.getIpAddress()); if (!"0.0.0.0".equals(o1ip) && !"0.0.0.0".equals(o2ip)) { return new ByteArrayComparator().compare(InetAddressUtils.toIpAddrBytes(o1ip), InetAddressUtils.toIpAddrBytes(o2ip)); } else { // Sort IPs that are non-0.0.0.0 so they are first if (!"0.0.0.0".equals(o1ip)) { return -1; } else if (!"0.0.0.0".equals(o2ip)) { return 1; } } // If we don't have an SNMP interface for both, compare by ID if (o1.getSnmpInterface() == null || o2.getSnmpInterface() == null) { // List interfaces without SNMP interface first if (o1.getSnmpInterface() != null) { return -1; } else { return o1.getId().compareTo(o2.getId()); } } // Sort by ifName if (o1.getSnmpInterface().getIfName() == null || o2.getSnmpInterface().getIfName() == null) { if (o1.getSnmpInterface().getIfName() != null) { return -1; } else if (o2.getSnmpInterface().getIfName() != null) { return 1; } } else if ((diff = o1.getSnmpInterface().getIfName().compareTo(o2.getSnmpInterface().getIfName())) != 0) { return diff; } // Sort by ifDescr if (o1.getSnmpInterface().getIfDescr() == null || o2.getSnmpInterface().getIfDescr() == null) { if (o1.getSnmpInterface().getIfDescr() != null) { return -1; } else if (o2.getSnmpInterface().getIfDescr() != null) { return 1; } } else if ((diff = o1.getSnmpInterface().getIfDescr().compareTo(o2.getSnmpInterface().getIfDescr())) != 0) { return diff; } // Sort by ifIndex if ((diff = o1.getSnmpInterface().getIfIndex().compareTo(o2.getSnmpInterface().getIfIndex())) != 0) { return diff; } // Fallback to id return o1.getId().compareTo(o2.getId()); } } /** * This class implements a comparator for OnmsSnmpInterfaces. (For comparing non-ip interfaces). * */ public static class SnmpInterfaceComparator implements Comparator<OnmsSnmpInterface>, Serializable { /** * */ private static final long serialVersionUID = 3751865611949289845L; public int compare(OnmsSnmpInterface o1, OnmsSnmpInterface o2) { int diff; // Sort by ifName if (o1.getIfName() == null || o2.getIfName() == null) { if (o1.getIfName() != null) { return -1; } else if (o2.getIfName() != null) { return 1; } } else if ((diff = o1.getIfName().compareTo(o2.getIfName())) != 0) { return diff; } // Sort by ifDescr if (o1.getIfDescr() == null || o2.getIfDescr() == null) { if (o1.getIfDescr() != null) { return -1; } else if (o2.getIfDescr() != null) { return 1; } } else if ((diff = o1.getIfDescr().compareTo(o2.getIfDescr())) != 0) { return diff; } // Sort by ifIndex if ((diff = o1.getIfIndex().compareTo(o2.getIfIndex())) != 0) { return diff; } // Fallback to id return o1.getId().compareTo(o2.getId()); } } public static class ArpInterfaceComparator implements Comparator<OnmsArpInterface>, Serializable { private static final long serialVersionUID = 2955682030166384496L; public int compare(OnmsArpInterface o1, OnmsArpInterface o2) { int diff; // Sort by IP first if the IPs are non-0.0.0.0 if (!"0.0.0.0".equals(o1.getIpAddress()) && !"0.0.0.0".equals(o2.getIpAddress())) { return new ByteArrayComparator().compare(InetAddressUtils.toIpAddrBytes(o1.getIpAddress()), InetAddressUtils.toIpAddrBytes(o2.getIpAddress())); } else { // Sort IPs that are non-0.0.0.0 so they are first if (!"0.0.0.0".equals(o1.getIpAddress())) { return -1; } else if (!"0.0.0.0".equals(o2.getIpAddress())) { return 1; } } // Sort by mac address if (o1.getPhysAddr() == null || o2.getPhysAddr() == null) { if (o1.getPhysAddr() != null) { return -1; } else if (o2.getPhysAddr() != null) { return 1; } } else if ((diff = o1.getPhysAddr().compareTo(o2.getPhysAddr())) != 0) { return diff; } // Fallback to id return o1.getId().compareTo(o2.getId()); } } }