package org.molgenis.annotation.cmd;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import org.molgenis.annotation.cmd.conversion.EffectStructureConverter;
import org.molgenis.annotation.cmd.utils.CmdLineAnnotatorUtils;
import org.molgenis.annotation.cmd.utils.VcfValidator;
import org.molgenis.data.annotation.core.RepositoryAnnotator;
import org.molgenis.data.annotation.core.entity.AnnotatorConfig;
import org.molgenis.data.annotation.core.entity.AnnotatorInfo;
import org.molgenis.data.meta.model.Attribute;
import org.molgenis.data.meta.model.AttributeFactory;
import org.molgenis.data.meta.model.EntityTypeFactory;
import org.molgenis.data.vcf.model.VcfAttributes;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.JOptCommandLinePropertySource;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
/**
* Build JAR file...............: mvn clean install -pl molgenis-annotators-cmd/ -am -DskipTests -P create-delivery
* Run..........................: java -jar molgenis-annotators-cmd/target/CmdLineAnnotator.jar
*/
public class CmdLineAnnotator
{
private static final String VALIDATE = "validate";
private static final String OUTPUT = "output";
private static final String VCF_VALIDATOR_LOCATION = "vcf-validator-location";
private static final String SOURCE = "source";
private static final String ANNOTATOR = "annotator";
private static final String INPUT = "input";
private static final String HELP = "help";
private static final String REPLACE = "replace";
private static final String UPDATE_ANNOTATIONS = "update-annotations";
private static final String USER_HOME = "user.home";
@Autowired
CommandLineAnnotatorConfig commandLineAnnotatorConfig;
@Autowired
private ApplicationContext applicationContext;
@Autowired
VcfValidator vcfValidator;
@Autowired
VcfAttributes vcfAttributes;
@Autowired
EffectStructureConverter effectStructureConverter;
@Autowired
EntityTypeFactory entityTypeFactory;
@Autowired
AttributeFactory attributeFactory;
// Default settings for running vcf-validator
private void run(OptionSet options, OptionParser parser) throws Exception
{
Map<String, AnnotatorConfig> annotatorMap = applicationContext.getBeansOfType(AnnotatorConfig.class);
annotatorMap.values().forEach(AnnotatorConfig::init);
Map<String, RepositoryAnnotator> configuredAnnotators = applicationContext
.getBeansOfType(RepositoryAnnotator.class);
// for now, only get the annotators that have received a recent brush up for the new way of configuring
Map<String, RepositoryAnnotator> configuredFreshAnnotators = getFreshAnnotators(configuredAnnotators);
Set<String> annotatorNames = configuredFreshAnnotators.keySet();
if (!options.has(ANNOTATOR) || options.has(HELP))
{
String implementationVersion = getClass().getPackage().getImplementationVersion();
if (implementationVersion == null)
{
implementationVersion = "";
}
System.out.println("\n" + "****************************************************\n"
+ "* MOLGENIS Annotator, commandline interface " + implementationVersion + " *\n"
+ "****************************************************\n"
+ "Typical usage to annotate a VCF file:\n\n"
+ "java -jar CmdLineAnnotator.jar [options] [attribute names]\n"
+ "Example: java -Xmx4g -jar CmdLineAnnotator.jar -v -a gonl -s GoNL/release5_noContam_noChildren_with_AN_AC_GTC_stripped/ -i Cardio.vcf -o Cardio_gonl.vcf GoNL_GTC GoNL_AF\n"
+ "\n" + "----------------------------------------------------\n\n" + "Available options:\n");
parser.printHelpOn(System.out);
System.out.println("\n" + "----------------------------------------------------\n\n"
+ "To get detailed description for a specific annotator:\n"
+ "java -jar CmdLineAnnotator.jar -a [Annotator]\n\n"
+ "To select only a few columns from an annotation source instead of everything, use:\n"
+ "java -jar CmdLineAnnotator.jar -a [Annotator] -s [Annotation source file] <column1> <column2>\n\n"
+ "----------------------------------------------------\n");
System.out.println("List of available annotators per category:\n\n" + printAnnotatorsPerType(
configuredFreshAnnotators));
return;
}
String annotatorName = (String) options.valueOf("annotator");
if (!annotatorNames.contains(annotatorName))
{
System.out.println("Annotator must be one of the following: " + annotatorNames.toString());
return;
}
Map<String, RepositoryAnnotator> annotators = applicationContext.getBeansOfType(RepositoryAnnotator.class);
RepositoryAnnotator annotator = annotators.get(annotatorName);
if (annotator == null) throw new Exception("Annotator unknown: " + annotatorName);
if (!options.has(INPUT))
{
printInfo(annotator.getInfo());
return;
}
File annotationSourceFile = (File) options.valueOf(SOURCE);
if (!annotationSourceFile.exists())
{
System.out.println("Annotation source file or directory not found at " + annotationSourceFile);
return;
}
File inputVcfFile = (File) options.valueOf("input");
if (!inputVcfFile.exists())
{
System.out.println("Input VCF file not found at " + inputVcfFile);
return;
}
else if (inputVcfFile.isDirectory())
{
System.out.println("Input VCF file is a directory, not a file!");
return;
}
File outputVCFFile = (File) options.valueOf(OUTPUT);
if (outputVCFFile.exists())
{
if (options.has(REPLACE))
{
System.out.println("Override enabled, replacing existing vcf with specified output: " + outputVCFFile
.getAbsolutePath());
}
else
{
System.out.println(
"Output file already exists, please either enter a different output name or use the '-r' option to overwrite the output file.");
return;
}
}
annotator.getCmdLineAnnotatorSettingsConfigurer().addSettings(annotationSourceFile.getAbsolutePath());
annotate(annotator, vcfAttributes, entityTypeFactory, attributeFactory, effectStructureConverter, inputVcfFile,
outputVCFFile, options);
}
/**
* Annotate VCF file
*
* @param annotator
* @param inputVcfFile
* @param outputVCFFile
* @param options , the attributes of the annotator to include in the output vcf, if empty outputs all
* @throws Exception
*/
private void annotate(RepositoryAnnotator annotator, VcfAttributes vcfAttributes,
EntityTypeFactory entityTypeFactory, AttributeFactory attributeFactory,
EffectStructureConverter effectStructureConverter, File inputVcfFile, File outputVCFFile, OptionSet options)
throws Exception
{
List<String> attributesToInclude = options.nonOptionArguments().stream().map(Object::toString)
.collect(Collectors.toList());
CmdLineAnnotatorUtils
.annotate(annotator, vcfAttributes, entityTypeFactory, attributeFactory, effectStructureConverter,
inputVcfFile, outputVCFFile, attributesToInclude, options.has("u"));
if (options.has(VALIDATE))
{
System.out.println("Validating produced VCF file...");
System.out.println(vcfValidator.validateVCF(outputVCFFile));
}
System.out.println("All done!");
}
public static void main(String[] args) throws Exception
{
configureLogging();
OptionParser parser = createOptionParser();
try
{
OptionSet options = parser.parse(args);
// See http://stackoverflow.com/questions/4787719/spring-console-application-configured-using-annotations
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
JOptCommandLinePropertySource propertySource = new JOptCommandLinePropertySource(options);
ctx.getEnvironment().getPropertySources().addFirst(propertySource);
ctx.register(CommandLineAnnotatorConfig.class);
ctx.scan("org.molgenis.data.annotation.core", "org.molgenis.annotation.cmd");
ctx.refresh();
CmdLineAnnotator main = ctx.getBean(CmdLineAnnotator.class);
main.run(options, parser);
ctx.close();
}
catch (OptionException ex)
{
System.out.println(ex.getMessage());
}
}
private static OptionParser createOptionParser()
{
OptionParser parser = new OptionParser();
parser.acceptsAll(asList("i", INPUT), "Input VCF file").withRequiredArg().ofType(File.class);
parser.acceptsAll(asList("a", ANNOTATOR), "Annotator name").requiredIf("input").withRequiredArg();
parser.acceptsAll(asList("s", SOURCE), "Source file for the annotator").requiredIf("input").withRequiredArg()
.ofType(File.class);
parser.acceptsAll(asList("o", OUTPUT), "Output VCF file").requiredIf("input").withRequiredArg()
.ofType(File.class);
parser.acceptsAll(asList("v", VALIDATE), "Use VCF validator on the output file");
parser.acceptsAll(asList("t", VCF_VALIDATOR_LOCATION),
"Location of the vcf-validator executable from the vcf-tools suite").withRequiredArg()
.ofType(String.class).defaultsTo(
System.getProperty(USER_HOME) + File.separator + ".molgenis" + File.separator + "vcf-tools"
+ File.separator + "bin" + File.separator + "vcf-validator");
parser.acceptsAll(asList("h", HELP), "Prints this help text");
parser.acceptsAll(asList("r", REPLACE),
"Enables output file override, replacing a file with the same name as the argument for the -o option");
parser.acceptsAll(asList("u", UPDATE_ANNOTATIONS),
"Enables add/updating of annotations, i.e. CADD scores from a different source, by reusing existing annotations when no match was found.");
return parser;
}
private void printInfo(AnnotatorInfo info)
{
System.out.println("*********************************************");
System.out.println(" " + info.getCode());
System.out.println("*********************************************");
System.out.println("Description: " + info.getDescription());
System.out.println("Type: " + info.getType());
System.out.println("Status: " + info.getStatus());
System.out.print("Attributes: ");
List<Attribute> attributes = info.getOutputAttributes();
if (attributes.isEmpty())
{
System.out.println();
}
else
{
System.out.println(attributes.get(0).getName());
for (int i = 1; i < attributes.size(); i++)
{
System.out.print(" ");
System.out.println(attributes.get(i).getName());
}
}
}
private static void configureLogging()
{
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
PatternLayoutEncoder patternLayoutEncoder = new PatternLayoutEncoder();
patternLayoutEncoder.setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n");
patternLayoutEncoder.setContext(loggerContext);
patternLayoutEncoder.start();
ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
consoleAppender.setContext(loggerContext);
consoleAppender.setEncoder(patternLayoutEncoder);
consoleAppender.setName("STDOUT");
consoleAppender.start();
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.addAppender(consoleAppender);
rootLogger.setLevel(Level.WARN);
Logger molgenisLogger = (Logger) LoggerFactory.getLogger("org.molgenis");
molgenisLogger.addAppender(consoleAppender);
molgenisLogger.setLevel(Level.INFO);
molgenisLogger.setAdditive(false);
}
/**
* Helper function to print annotators per type
*
* @param annotators
* @return
*/
static String printAnnotatorsPerType(Map<String, RepositoryAnnotator> annotators)
{
Map<AnnotatorInfo.Type, List<String>> annotatorsPerType = new HashMap<>();
for (String annotator : annotators.keySet())
{
AnnotatorInfo.Type type = annotators.get(annotator).getInfo().getType();
if (annotatorsPerType.containsKey(type))
{
annotatorsPerType.get(type).add(annotator);
}
else
{
annotatorsPerType.put(type, new ArrayList<>(Arrays.asList(new String[] { annotator })));
}
}
StringBuilder sb = new StringBuilder();
for (AnnotatorInfo.Type type : annotatorsPerType.keySet())
{
sb.append("### ").append(type).append(" ###\n");
for (String annotatorName : annotatorsPerType.get(type))
{
sb.append("* ").append(annotatorName).append("\n");
}
sb.append("\n");
}
return sb.toString();
}
/**
* Helper function to select the annotators that have received a recent brush up for the new way of configuring
*
* @param configuredAnnotators
* @return
*/
static HashMap<String, RepositoryAnnotator> getFreshAnnotators(
Map<String, RepositoryAnnotator> configuredAnnotators)
{
HashMap<String, RepositoryAnnotator> configuredFreshAnnotators = new HashMap<>();
configuredAnnotators.keySet().stream()
.filter(annotator -> configuredAnnotators.get(annotator).getInfo() != null && configuredAnnotators
.get(annotator).getInfo().getStatus().equals(AnnotatorInfo.Status.READY))
.forEachOrdered(annotator ->
{
configuredFreshAnnotators.put(annotator, configuredAnnotators.get(annotator));
});
return configuredFreshAnnotators;
}
}