package com.onaro.sanscreen.client.view.changes; import com.onaro.sanscreen.server.changes.Change; import com.onaro.sanscreen.server.changes.ChangeConstants; import com.onaro.sanscreen.types.ChangeType; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.*; /** * A list of ChangeGroup objects sorted by the change time and type. */ public class Changes extends ArrayList { private static final long serialVersionUID = 1L; /** * The logger used by this package. */ static Logger logger = LogManager.getLogger(Changes.class); /** * A compartor for soring groups. */ private final static Comparator groupCompartor = new GroupComparator(); /** * Builds change groups from a collection of changes. The groups are built to * contain changes that occurred at the same time and relate to the same entity. * The grouping is done in three phases:
    *
  1. Groups are created. Each change is assigned to a single group. *
  2. The groups are sorted by change time and type. *
  3. Redundant groups are merged. *
*

* In order to determine the group that a change belongs to, the groups are * held in a map indexed by a unique-key that is derived from the properties * of the change. All the changes in a group will derive the same unique-key. * The key generation is a service provided by the ChangeGroup * class. Note that in order to filter out unwanted changes, the ChangeGroup * will return a null key for that change. *

* Because the unique-key based grouping is limited, some redundancy may occur * among groups of changes that happened at the same time. For example, when * an adapter is added or removed from a host, the keys for such events will * differ from the keys regarding the host itself, yet they relate to the same * entity. Therefore a merging phase takes place after the grouping is donw. * Following are the kind of merges that are performed:

    *
  1. Adapter/Controller add/remove are merged into host/storage changes. *
  2. Path changes are merged into the violations caused by them (if at all). *
  3. Unresolve of adapters/controllers *

* Optionally, a filter can be applied in the end of the process for removing * groups by some custom cretaria. * * @param changes a collection of Change * objects * @param filter an optional type filter, may be null if no filter is in use */ public Changes(Collection changes, TypeFilter filter) { /** * Lists of change groups (not sorted) that occured at the same time. */ Map sameTimeGroups = new HashMap(); /** * Create changes and groups. */ Map changeGroups = new HashMap(changes.size()); for (Iterator changeItr = changes.iterator(); changeItr.hasNext();) { Change change = changeItr.next(); /** * Calculate the current change's unique key. If its null that current * change is ignored. */ Object uniqueKey = ChangeGroup.getUniqueKey(change); if (uniqueKey != null) {/** * Find the group that should contain the current change or create * a new one if none exist. */ ChangeGroup group = changeGroups.get(uniqueKey); if (group == null) { //this also adds the change to the group group = new ChangeGroup(change); changeGroups.put(uniqueKey, group); Long time = Long.valueOf(group.getTime()); TimeGroup timeGroup = sameTimeGroups.get(time); if (timeGroup == null) { timeGroup = new TimeGroup(); sameTimeGroups.put(time, timeGroup); } timeGroup.addChangeGroup(group, uniqueKey); } else { group.addChange(change); } } else { if (logger.isDebugEnabled()) logger.debug("Ignoring " + change); //$NON-NLS-1$ } } /** * Merge redundant groups having the same time */ for (Iterator timeItr = sameTimeGroups.values().iterator(); timeItr.hasNext();) { TimeGroup timeGroup = timeItr.next(); /** * Merge devices. */ if (!timeGroup.deviceUpChanges.isEmpty() || !timeGroup.deviceDownChanges.isEmpty()) { for (Map.Entry entry : timeGroup.otherDeviceChanges.entrySet()) { ChangeGroup candidate = entry.getValue(); String candidateMainDevId = candidate.getValue(ChangeConstants.SRC_MAIN_DEVICE + ChangeConstants.OBJID); if (candidateMainDevId != null) { Long candidateId = Long.valueOf(candidateMainDevId); ChangeGroup upGroup = timeGroup.deviceUpChanges.get(candidateId); ChangeGroup downGroup = timeGroup.deviceDownChanges.get(candidateId); if ((upGroup != null) && (downGroup == null)) { upGroup.mergeChangeGroup(candidate); changeGroups.remove(entry.getKey()); } else if ((upGroup == null) && (downGroup != null)) { downGroup.mergeChangeGroup(candidate); changeGroups.remove(entry.getKey()); } } } } /** * Merge paths into violations */ if (!timeGroup.violationChanges.isEmpty() && !timeGroup.pathChanges.isEmpty()) { for (Map.Entry entry : timeGroup.pathChanges.entrySet()) { ChangeGroup pathGroup = entry.getValue(); ViolationKey logicalKey = new ViolationKey(pathGroup); ChangeGroup violationGroup = timeGroup.violationChanges.get(logicalKey); if (violationGroup != null) { violationGroup.mergeChangeGroup(pathGroup); changeGroups.remove(entry.getKey()); } } } } sameTimeGroups.clear(); /** * Apply the filters. (after the merge of the group) */ if (filter != null) { for (Iterator groups = changeGroups.values().iterator(); groups.hasNext();) { if (!filter.shouldBeDisplayed(groups.next())) { groups.remove(); } } } /** * Sort the change groups. Sorting of the changes within the groups is postpone to the first time they are used. */ clear(); addAll(changeGroups.values()); Collections.sort(this, groupCompartor); } /** * The set of types that are candidate for merge into device up/down changes. */ static final Set TYPE_CANDIDATES_FOR_MERGE = new HashSet(Arrays.asList(ChangeType.DEVICE_ATTRIBUTE_CHANGE, ChangeType.DEVICE_CONFIGURATION_CHANGE, ChangeType.PORT_ATTRIBUTE_CHANGE)); public long getFromTime() { checkNotEmpty(); return get(size() - 1).getTime(); } public long getToTime() { checkNotEmpty(); return get(0).getTime(); } private void checkNotEmpty() { if (isEmpty()) { String msg = "this method should not be called when there are no changes"; //$NON-NLS-1$ throw new IllegalStateException(msg); } } /** * Points to change groups that occurred at the same time. Used for merging * change groups. */ static class TimeGroup { /** * Change groups for "device up" changes mapped by the device-id. */ Map deviceUpChanges = new HashMap(); /** * Change groups for "device down" changes mapped by the device-id. */ Map deviceDownChanges = new HashMap(); /** * Other change-groups that are candidate for merge into device up/down * groups. */ Map otherDeviceChanges = new HashMap(); /** * Violation changes mapped using a {@link ViolationKey} instances. */ Map violationChanges = new HashMap(); /** * Path changes. */ Map pathChanges = new HashMap(); /** * Violation changes mapped using a {@link ViolationKey} instances. */ Map hvViolationChanges = new HashMap(); /** * Adds a change group to this time group. Sort into the various collections * to simplify the merging. * * @param group the added group * @param uniqueKey */ void addChangeGroup(ChangeGroup group, Object uniqueKey) { String srcMainDeviceObjid = group.getValue(ChangeConstants.SRC_MAIN_DEVICE + ChangeConstants.OBJID); if (ChangeType.DEVICE_UP.equals(group.getType()) && srcMainDeviceObjid != null) { deviceUpChanges.put(Long.valueOf(srcMainDeviceObjid), group); } else if (ChangeType.DEVICE_DOWN.equals(group.getType()) && srcMainDeviceObjid != null) { deviceDownChanges.put(Long.valueOf(srcMainDeviceObjid), group); } else if (TYPE_CANDIDATES_FOR_MERGE.contains(group.getType())) { otherDeviceChanges.put(uniqueKey, group); } else if (ChangeType.PATH_CHANGE_TYPES.contains(group.getType())) { pathChanges.put(uniqueKey, group); } else if (ChangeType.VIOALTION_CHANGE_TYPES.contains(group.getType())) { violationChanges.put(new ViolationKey(group), group); } else if (ChangeType.HV_VIOLATION_TYPES.contains(group.getType())) { hvViolationChanges.put(new ViolationKey(group), group); } } } }