package org.icij.extract.report;
import org.icij.extract.document.Document;
import org.icij.extract.extractor.ExtractionStatus;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
/**
* Records the extraction result of a file to the given {@link ReportMap}.
*
* @since 1.0.0-beta
*/
public class Reporter implements AutoCloseable {
private Set<Class<? extends Exception>> journalableTypes = new HashSet<>();
private Map<Document, Report> journal = new ConcurrentHashMap<>();
private Semaphore flushing = new Semaphore(1);
/**
* The report to save results to or check.
*/
protected final ReportMap reportMap;
/**
* Create a new reporter that will record results to the given {@link ReportMap}.
*
* @param reportMap the report map
*/
public Reporter(final ReportMap reportMap) {
this.reportMap = reportMap;
final Collection<Class<? extends Exception>> journalableExceptions = reportMap.journalableExceptions();
if (null != journalableExceptions) {
journalableExceptions.forEach(this::journalableException);
}
}
/**
* Check the extraction result of a given document.
*
* @param document the document to check
* @return The extraction report or {@code null} if no result was recorded for the file.
*/
public Report report(final Document document) {
return reportMap.get(document);
}
/**
* Save the extraction report for the given document.
*
* @param document the document
* @param report the extraction report
*/
public void save(final Document document, final Report report) {
try {
reportMap.fastPut(document, report);
} catch (Exception e) {
if (journalableTypes.contains(e.getClass())) {
journal.put(document, report);
}
throw e;
}
if (flushing.tryAcquire()) {
try {
flushJournal();
} finally {
flushing.release();
}
}
}
/**
* Save the extraction status and optional exception for the given document.
*
* @param document the document
* @param status the extraction status
* @param exception any exception caught during extraction
*/
public void save(final Document document, final ExtractionStatus status, final Exception exception) {
save(document, new Report(status, exception));
}
/**
* Save the extraction status for the given document.
*
* @param document the document
* @param status the extraction status
*/
public void save(final Document document, final ExtractionStatus status) {
save(document, new Report(status));
}
/**
* Check an extraction result.
*
* @param document the document to check
* @param result matched against the actual result
* @return {@code true} if the results match or {@code false} if there is no match or no recorded result.
*/
public boolean check(final Document document, final ExtractionStatus result) {
final Report report = report(document);
return null != report && report.getStatus().equals(result);
}
/**
* Check whether a path should be skipped.
*
* @param document the document to check
* @return {@code true} if the document should be skipped.
*/
public boolean skip(final Document document) {
return check(document, ExtractionStatus.SUCCESS);
}
@Override
public void close() throws Exception {
flushing.acquire();
try {
flushJournal();
} finally {
flushing.release();
}
reportMap.close();
}
/**
* Add a class of {@link Exception} that when caught during {@link #save(Document, Report)}, would add
* the arguments to journal which is flushed when the next operation succeeds.
*
* @param e the class of exception that is temporary
*/
private synchronized void journalableException(final Class<? extends Exception> e) {
journalableTypes.add(e);
}
/**
* Flush the journal of failed status to the report.
*/
private void flushJournal() {
final Iterator<Map.Entry<Document, Report>> iterator = journal.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry<Document, Report> entry = iterator.next();
reportMap.fastPut(entry.getKey(), entry.getValue());
iterator.remove();
}
}
}