package com.aconex.scrutineer;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
public class IdAndVersionStreamVerifier {
private static final Logger LOG = LogUtils.loggerForThisClass();
//CHECKSTYLE:OFF
@SuppressWarnings("PMD.NcssMethodCount")
public void verify(IdAndVersionStream primaryStream, IdAndVersionStream secondayStream, IdAndVersionStreamVerifierListener idAndVersionStreamVerifierListener) {
long numItems = 0;
long begin = System.currentTimeMillis();
try {
parallelOpenStreamsAndWait(primaryStream, secondayStream);
Iterator<IdAndVersion> primaryIterator = primaryStream.iterator();
Iterator<IdAndVersion> secondaryIterator = secondayStream.iterator();
IdAndVersion primaryItem = next(primaryIterator);
IdAndVersion secondaryItem = next(secondaryIterator);
while (primaryItem != null && secondaryItem != null) {
if (primaryItem.equals(secondaryItem)) {
primaryItem = verifiedNext(primaryIterator, primaryItem);
secondaryItem = next(secondaryIterator);
} else if (primaryItem.getId().equals(secondaryItem.getId())) {
idAndVersionStreamVerifierListener.onVersionMisMatch(primaryItem, secondaryItem);
primaryItem = verifiedNext(primaryIterator, primaryItem);
secondaryItem = next(secondaryIterator);
} else if (primaryItem.compareTo(secondaryItem) < 0) {
idAndVersionStreamVerifierListener.onMissingInSecondaryStream(primaryItem);
primaryItem = verifiedNext(primaryIterator, primaryItem);
} else {
idAndVersionStreamVerifierListener.onMissingInPrimaryStream(secondaryItem);
secondaryItem = next(secondaryIterator);
}
numItems++;
}
while (primaryItem != null) {
idAndVersionStreamVerifierListener.onMissingInSecondaryStream(primaryItem);
primaryItem = verifiedNext(primaryIterator, primaryItem);
numItems++;
}
while (secondaryItem != null) {
idAndVersionStreamVerifierListener.onMissingInPrimaryStream(secondaryItem);
secondaryItem = next(secondaryIterator);
numItems++;
}
} finally {
closeWithoutThrowingException(primaryStream);
closeWithoutThrowingException(secondayStream);
}
LogUtils.infoTimeTaken(LOG, begin, numItems, "Completed verification");
}
//CHECKSTYLE:ON
@SuppressWarnings("PMD.NcssMethodCount")
private void parallelOpenStreamsAndWait(IdAndVersionStream primaryStream, IdAndVersionStream secondaryStream) {
try {
ExecutorService executorService = Executors.newFixedThreadPool(1, new NamedDaemonThreadFactory("StreamOpener"));
Future<?> secondaryStreamFuture = executorService.submit(new OpenStreamRunner(secondaryStream));
primaryStream.open();
secondaryStreamFuture.get();
executorService.shutdown();
} catch (Exception e) {
throw new IllegalStateException("Failed to open one or both of the streams in parallel", e);
}
}
private IdAndVersion verifiedNext(Iterator<IdAndVersion> iterator, IdAndVersion previous) {
IdAndVersion next = next(iterator);
if (next != null && previous.compareTo(next) >= 0) {
throw new IllegalStateException("primary stream not ordered as expected: " + next + " followed "
+ previous);
} else {
return next;
}
}
@SuppressWarnings("PMD.NcssMethodCount")
private IdAndVersion next(Iterator<IdAndVersion> iterator) {
if (iterator.hasNext()) {
IdAndVersion next = iterator.next();
if (next == null) {
throw new IllegalStateException("stream must not return null");
} else {
return next;
}
} else {
return null;
}
}
private void closeWithoutThrowingException(IdAndVersionStream idAndVersionStream) {
try {
idAndVersionStream.close();
} catch (Exception e) {
LogUtils.warn(LOG, "Unable to close IdAndVersionStream", e);
}
}
private static class OpenStreamRunner implements Runnable {
private final IdAndVersionStream stream;
public OpenStreamRunner(IdAndVersionStream stream) {
this.stream = stream;
}
@Override
public void run() {
stream.open();
}
}
private static class NamedDaemonThreadFactory implements ThreadFactory {
private final String namePrefix;
private final AtomicInteger threadCount = new AtomicInteger();
public NamedDaemonThreadFactory(String namePrefix) {
this.namePrefix = namePrefix;
}
@Override
public Thread newThread(Runnable command) {
Thread thread = new Thread(command, namePrefix + "-" + threadCount.incrementAndGet());
thread.setDaemon(true);
return thread;
}
}
}