diff --git a/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java b/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java index 863b5c0a7..758238279 100755 --- a/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java +++ b/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java @@ -157,6 +157,9 @@ public class HierarchicalMicroScheduler extends MicroScheduler implements Hierar result = reduceTree.getResult().get(); notifyTraversalDone(walker,result); } + catch (ReviewedStingException ex) { + throw ex; + } catch (Exception ex) { throw new ReviewedStingException("Unable to retrieve result", ex); } diff --git a/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java b/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java index 11398fb04..bc9a4d4b0 100755 --- a/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java +++ b/java/src/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalWalker.java @@ -25,6 +25,9 @@ package org.broadinstitute.sting.gatk.walkers.varianteval; +import org.apache.commons.jexl2.*; +import org.apache.commons.jexl2.introspection.*; +import org.apache.commons.logging.LogFactory; import org.apache.log4j.Logger; import org.broad.tribble.util.variantcontext.MutableVariantContext; import org.broad.tribble.util.variantcontext.VariantContext; @@ -32,6 +35,7 @@ import org.broad.tribble.vcf.VCFConstants; import org.broad.tribble.vcf.VCFWriter; import org.broad.tribble.vcf.VCFHeader; import org.broad.tribble.vcf.VCFHeaderLine; +import org.broadinstitute.sting.commandline.Hidden; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.gatk.contexts.variantcontext.VariantContextUtils; @@ -63,6 +67,7 @@ import java.io.FileNotFoundException; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.util.*; // todo -- evalations should support comment lines @@ -121,6 +126,10 @@ public class VariantEvalWalker extends RodWalker implements Tr @Argument(shortName="selectName", doc="Names to use for the list of stratifications (must be a 1-to-1 mapping)", required=false) protected ArrayList SELECT_NAMES = new ArrayList(); + @Hidden + @Argument(shortName="validate", doc="One or more JEXL validations to use after evaluating the data", required=false) + protected ArrayList VALIDATE_EXPS = new ArrayList(); + @Argument(shortName="known", doc="Name of ROD bindings containing variant sites that should be treated as known when splitting eval rods into known and novel subsets", required=false) protected String[] KNOWN_NAMES = {DbSNPHelper.STANDARD_DBSNP_TRACK_NAME}; @@ -231,7 +240,15 @@ public class VariantEvalWalker extends RodWalker implements Tr public boolean isSelected() { return selectExp == null; } public String getDisplayName() { - return Utils.join(CONTEXT_SEPARATOR, Arrays.asList(evalTrackName, compTrackName, selectExp == null ? "all" : selectExp.name, filtered, novelty)); + return getName(CONTEXT_SEPARATOR); + } + + public String getJexlName() { + return getName("."); + } + + private String getName(String separator) { + return Utils.join(separator, Arrays.asList(evalTrackName, compTrackName, selectExp == null ? "all" : selectExp.name, filtered, novelty)); } public String toString() { return getDisplayName(); } @@ -740,6 +757,14 @@ public class VariantEvalWalker extends RodWalker implements Tr } public void onTraversalDone(Integer result) { + writeReport(); + validateContext(); + } + + /** + * Writes the report out to disk. + */ + private void writeReport() { // our report mashaller ReportMarshaller marshaller; @@ -761,6 +786,64 @@ public class VariantEvalWalker extends RodWalker implements Tr marshaller.close(); } + /** + * Validates the JEXL expressions and throws an exception if they do not all return true. + */ + private void validateContext() { + if (VALIDATE_EXPS.size() == 0) + return; + + JexlContext jc = new MapContext(); + for (EvaluationContext context : contexts) + for (VariantEvaluator eval: context.evaluations) + jc.set(context.getJexlName() + "." + eval.getName(), eval); + + Uberspect uberspect = new UberspectImpl(LogFactory.getLog(JexlEngine.class)) { + /** Gets the field, even if the field was non-public. */ + @Override + public Field getField(Object obj, String name, JexlInfo info) { + Field result = super.getField(obj, name, info); + if (result == null && obj != null) { + Class clazz = obj instanceof Class ? (Class)obj : obj.getClass(); + try { + // TODO: Default UberspectImpl uses an internal field cache by class type + result = clazz.getDeclaredField(name); + result.setAccessible(true); + } catch (NoSuchFieldException nsfe) { + /* ignore */ + } + } + return result; + } + }; + + JexlEngine jexl = new JexlEngine(uberspect, null, null, null); + + List failedExpressions = new ArrayList(); + for (String expression: VALIDATE_EXPS) { + // ex: evalYRI.compYRI.all.called.novel.titv.tiTvRatio > 1.0 + Object jexlResult = jexl.createExpression(expression).evaluate(jc); + boolean pass = Boolean.TRUE.equals(jexlResult); + if (!pass) { + logger.error("FAIL: " + expression); + failedExpressions.add(expression); + } else if (logger.isDebugEnabled()) { + logger.debug("PASS: " + expression); + } + } + + int failed = failedExpressions.size(); + int total = VALIDATE_EXPS.size(); + + logger.info(String.format("Validations: Total %s, Passed %s, Failed %s", total, (total-failed), failed)); + if (failed > 0) { + StringBuilder message = new StringBuilder("The validation expressions below did not return true. Please check the report output for more info."); + for (String expression: failedExpressions) + message.append(String.format("%n ")).append(expression); + throw new UserException(message.toString()); + } + } + /** * create some additional output lines about the analysis * @return a list of nodes to attach to the report as tags diff --git a/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java b/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java index 912907721..fe9dcccd3 100755 --- a/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java +++ b/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java @@ -1,6 +1,7 @@ package org.broadinstitute.sting.gatk.walkers.varianteval; import org.broadinstitute.sting.WalkerTest; +import org.broadinstitute.sting.utils.exceptions.UserException; import org.testng.annotations.Test; import java.util.Arrays; @@ -149,4 +150,27 @@ public class VariantEvalIntegrationTest extends WalkerTest { //executeTest("testACDiscordanceAtAC1EvalAC2Comp",spec); } + @Test + public void testVEValidatePass() { + String extraArgs = "-L 1:1-10,000,000"; + for (String tests : testsEnumerations) { + WalkerTestSpec spec = new WalkerTestSpec(withValidateTiTv(withSelect(tests, "DP < 50", "DP50"), 1.0, 4.0) + " " + extraArgs + " -o %s", + 1, Arrays.asList("8a0203f0533b628ad7f1f230a43f105f")); + executeTestParallel("testVEValidatePass", spec); + } + } + + @Test + public void testVEValidateFail() { + String extraArgs = "-L 1:1-10,000,000"; + for (String tests : testsEnumerations) { + WalkerTestSpec spec = new WalkerTestSpec(withValidateTiTv(withSelect(tests, "DP < 50", "DP50"), 1.0, 1.2) + " " + extraArgs + " -o %s", + 1, UserException.class); + executeTestParallel("testVEValidateFail", spec); + } + } + + private static String withValidateTiTv(String cmd, double min, double max) { + return String.format("%s -validate 'eval.comp_genotypes.all.called.all.titv.tiTvRatio >= %2$s' -validate 'eval.comp_genotypes.all.called.all.titv.tiTvRatio <= %3$s'", cmd, min, max); + } }