* returns the number of the affected documents even if
* {@code dryRun} is set true and no document was changed.
*/
public int recover(Iterator<NodeDocument> suspects,
int clusterId, boolean dryRun) {
Closer closer = Closer.create();
try {
UnsavedModifications unsaved = new UnsavedModifications();
closer.register(unsaved);
UnsavedModifications unsavedParents = new UnsavedModifications();
closer.register(unsavedParents);
//Map of known last rev of checked paths
UnsavedModifications knownLastRevs = new UnsavedModifications();
closer.register(knownLastRevs);
long count = 0;
while (suspects.hasNext()) {
NodeDocument doc = suspects.next();
count++;
if (count % 100000 == 0) {
log.info("Scanned {} suspects so far...", count);
}
Revision currentLastRev = doc.getLastRev().get(clusterId);
if (currentLastRev != null) {
knownLastRevs.put(doc.getPath(), currentLastRev);
}
Revision lostLastRev = determineMissedLastRev(doc, clusterId);
//1. Update lastRev for this doc
if (lostLastRev != null) {
unsaved.put(doc.getPath(), lostLastRev);
}
Revision lastRevForParents = lostLastRev != null ? lostLastRev : currentLastRev;
//If both currentLastRev and lostLastRev are null it means
//that no change is done by suspect cluster on this document
//so nothing needs to be updated. Probably it was only changed by
//other cluster nodes. If this node is parent of any child node which
//has been modified by cluster then that node roll up would
//add this node path to unsaved
//2. Update lastRev for parent paths aka rollup
if (lastRevForParents != null) {
String path = doc.getPath();
while (true) {
if (PathUtils.denotesRoot(path)) {
break;
}
path = PathUtils.getParentPath(path);
unsavedParents.put(path, lastRevForParents);
}
}
}
for (String parentPath : unsavedParents.getPaths()) {
Revision calcLastRev = unsavedParents.get(parentPath);
Revision knownLastRev = knownLastRevs.get(parentPath);
//Copy the calcLastRev of parent only if they have changed
//In many case it might happen that parent have consistent lastRev
//This check ensures that unnecessary updates are not made
if (knownLastRev == null
|| calcLastRev.compareRevisionTime(knownLastRev) > 0) {
unsaved.put(parentPath, calcLastRev);
}
}
//Note the size before persist as persist operation
//would empty the internal state
int size = unsaved.getPaths().size();
String updates = unsaved.toString();
if (dryRun) {
log.info("Dry run of lastRev recovery identified [{}] documents for " +
"cluster node [{}]: {}", size, clusterId, updates);
} else {
//UnsavedModifications is designed to be used in concurrent
//access mode. For recovery case there is no concurrent access
//involve so just pass a new lock instance
unsaved.persist(nodeStore, new ReentrantLock());
log.info("Updated lastRev of [{}] documents while performing lastRev recovery for " +
"cluster node [{}]: {}", size, clusterId, updates);
}
return size;
} finally {
try {
closer.close();
} catch (IOException e) {
log.warn("Error closing UnsavedModifications", e);
}
}
}