Java tutorial
/* * Copyright 2008-2009 LinkedIn, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package voldemort.store.routed; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; import voldemort.annotations.concurrency.Threadsafe; import voldemort.versioning.Occurred; import voldemort.versioning.Version; import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; /** * Repair out-dated reads, by sending an up-to-date value back to the offending * clients * * This class computes the set of repairs that need to be made. * * * @param <K> The class of the key in the fetch * @param <V> The class of the value in the fetch */ @Threadsafe public class ReadRepairer<K, V> { private final Logger logger = Logger.getLogger(getClass()); /** * Compute the repair set from the given values and nodes * * @param nodeValues The value found on each node * @return A set of repairs to perform */ public List<NodeValue<K, V>> getRepairs(List<NodeValue<K, V>> nodeValues) { int size = nodeValues.size(); if (size <= 1) return Collections.emptyList(); Map<K, List<NodeValue<K, V>>> keyToNodeValues = Maps.newHashMap(); for (NodeValue<K, V> nodeValue : nodeValues) { List<NodeValue<K, V>> keyNodeValues = keyToNodeValues.get(nodeValue.getKey()); if (keyNodeValues == null) { keyNodeValues = Lists.newArrayListWithCapacity(5); keyToNodeValues.put(nodeValue.getKey(), keyNodeValues); } keyNodeValues.add(nodeValue); } List<NodeValue<K, V>> result = Lists.newArrayList(); for (List<NodeValue<K, V>> keyNodeValues : keyToNodeValues.values()) result.addAll(singleKeyGetRepairs(keyNodeValues)); return result; } private List<NodeValue<K, V>> singleKeyGetRepairs(List<NodeValue<K, V>> nodeValues) { int size = nodeValues.size(); if (size <= 1) return Collections.emptyList(); // 1. Create a multi-map of nodes to their existing Versions Multimap<Integer, NodeValue<K, V>> nodeVersionsMap = HashMultimap.create(); for (NodeValue<K, V> nodeValue : nodeValues) { nodeVersionsMap.put(nodeValue.getNodeId(), nodeValue); } // 2. Create a map of the final set of versions (for this key) Map<Version, NodeValue<K, V>> mostCurrentVersionsMap = new HashMap<Version, NodeValue<K, V>>(); // Initialize with the first element from the input mostCurrentVersionsMap.put(nodeValues.get(0).getVersion(), nodeValues.get(0)); // check each value against the current set of most current versions for (int i = 1; i < nodeValues.size(); i++) { NodeValue<K, V> curr = nodeValues.get(i); boolean concurrentToAll = true; /* * Make a copy for the traversal. This is because the original map * can be modified during this traversal */ Set<Version> knownGoodVersions = new HashSet<Version>(mostCurrentVersionsMap.keySet()); for (Version currentGoodversion : knownGoodVersions) { // If the version already exists, do nothing if (curr.getVersion().equals(currentGoodversion)) { concurrentToAll = false; if (logger.isDebugEnabled()) { logger.debug("Version already exists in the most current set: " + curr); } break; } // Check the ordering of the current value Occurred occurred = curr.getVersion().compare(currentGoodversion); if (occurred == Occurred.BEFORE) { // This value is obsolete! Break from the loop if (logger.isDebugEnabled()) { logger.debug("Version is obsolete : " + curr); } concurrentToAll = false; break; } else if (occurred == Occurred.AFTER) { // This concurrent value is obsolete and the current value // should replace it mostCurrentVersionsMap.remove(currentGoodversion); concurrentToAll = false; mostCurrentVersionsMap.put(curr.getVersion(), curr); if (logger.isDebugEnabled()) { logger.debug("Updating the current best - adding : " + curr); } } } // if the value is concurrent to all existing versions then add it // to the concurrent set if (concurrentToAll) { mostCurrentVersionsMap.put(curr.getVersion(), curr); if (logger.isDebugEnabled()) { logger.debug("Value is concurrent to all ! : " + curr); } } } // 3. Compare 1 and 2 and create the repair list List<NodeValue<K, V>> repairs = new ArrayList<NodeValue<K, V>>(3); for (int nodeId : nodeVersionsMap.keySet()) { Set<Version> finalVersions = new HashSet<Version>(mostCurrentVersionsMap.keySet()); if (logger.isDebugEnabled()) { logger.debug("Set of final versions = " + finalVersions); } // Calculate the set difference between final Versions and // the versions currently existing for nodeId Set<Version> currentNodeVersions = new HashSet<Version>(); for (NodeValue<K, V> nodeValue : nodeVersionsMap.get(nodeId)) { currentNodeVersions.add(nodeValue.getVersion()); } finalVersions.removeAll(currentNodeVersions); if (logger.isDebugEnabled()) { logger.debug("Remaining versions to be repaired for this node after the set difference = " + finalVersions); } // Repair nodeId with the remaining Versioned values for (Version remainingVersion : finalVersions) { NodeValue<K, V> repair = new NodeValue<K, V>(nodeId, mostCurrentVersionsMap.get(remainingVersion).getKey(), mostCurrentVersionsMap.get(remainingVersion).getVersioned()); if (logger.isDebugEnabled()) { logger.debug("Node value marked to be repaired : " + repair); } repairs.add(repair); } } return repairs; } }