Refactor CLI to merge both CLI command frameworks

This commit is contained in:
James Agnew 2018-04-30 08:29:38 -04:00
parent 92dc0d42ee
commit d85efbaa5b
19 changed files with 642 additions and 392 deletions

View File

@ -47,7 +47,12 @@
<version>${project.version}</version> <version>${project.version}</version>
<classifier>classes</classifier> <classifier>classes</classifier>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.14</version>
</dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId> <artifactId>hapi-fhir-structures-dstu2</artifactId>
@ -232,7 +237,6 @@
<artifactItem> <artifactItem>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli-jpaserver</artifactId> <artifactId>hapi-fhir-cli-jpaserver</artifactId>
<!--<version>1.5-SNAPSHOT</version>-->
<type>war</type> <type>war</type>
<overWrite>true</overWrite> <overWrite>true</overWrite>
<outputDirectory>target/classes</outputDirectory> <outputDirectory>target/classes</outputDirectory>
@ -283,35 +287,6 @@
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<versionRange>[2.10,)</versionRange>
<goals>
<goal>copy</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build> </build>
</project> </project>

View File

@ -1,220 +1,25 @@
package ca.uhn.fhir.cli; package ca.uhn.fhir.cli;
import static org.fusesource.jansi.Ansi.ansi;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.Ansi.Color;
import org.fusesource.jansi.AnsiConsole;
import org.slf4j.LoggerFactory;
import com.phloc.commons.io.file.FileUtils;
import ca.uhn.fhir.util.VersionUtil; import ca.uhn.fhir.util.VersionUtil;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
public class App { public class App extends BaseApp {
private static List<BaseCommand> ourCommands;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(App.class);
public static final String LINESEP = System.getProperty("line.separator"); @Override
protected String provideCommandName() {
static { return "hapi-fhir-cli";
ourCommands = new ArrayList<BaseCommand>();
ourCommands.add(new RunServerCommand());
ourCommands.add(new ExampleDataUploader());
ourCommands.add(new ValidateCommand());
ourCommands.add(new ValidationDataUploader());
ourCommands.add(new WebsocketSubscribeCommand());
ourCommands.add(new UploadTerminologyCommand());
ourCommands.add(new IgPackUploader());
Collections.sort(ourCommands);
} }
private static void logCommandUsage(BaseCommand theCommand) { @Override
logAppHeader(); protected String provideProductName() {
return "HAPI FHIR";
logCommandUsageNoHeader(theCommand);
} }
private static void logCommandUsageNoHeader(BaseCommand theCommand) { @Override
System.out.println("Usage:"); protected String provideProductVersion() {
System.out.println(" hapi-fhir-cli " + theCommand.getCommandName() + " [options]"); return VersionUtil.getVersion();
System.out.println();
System.out.println("Options:");
HelpFormatter fmt = new HelpFormatter();
PrintWriter pw = new PrintWriter(System.out);
fmt.printOptions(pw, 80, theCommand.getOptions(), 2, 2);
pw.flush();
pw.close();
}
private static void loggingConfigOff() {
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
((LoggerContext) LoggerFactory.getILoggerFactory()).reset();
configurator.doConfigure(App.class.getResourceAsStream("/logback-cli-off.xml"));
} catch (JoranException e) {
e.printStackTrace();
}
}
private static void loggingConfigOn() {
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
((LoggerContext) LoggerFactory.getILoggerFactory()).reset();
configurator.doConfigure(App.class.getResourceAsStream("/logback-cli-on.xml"));
} catch (JoranException e) {
e.printStackTrace();
}
}
private static void logUsage() {
logAppHeader();
System.out.println("Usage:");
System.out.println(" hapi-fhir-cli {command} [options]");
System.out.println();
System.out.println("Commands:");
int longestCommandLength = 0;
for (BaseCommand next : ourCommands) {
longestCommandLength = Math.max(longestCommandLength, next.getCommandName().length());
}
for (BaseCommand next : ourCommands) {
String left = " " + StringUtils.rightPad(next.getCommandName(), longestCommandLength);
String[] rightParts = WordUtils.wrap(next.getCommandDescription(), 80 - (left.length() + 3)).split("\\n");
for (int i = 1; i < rightParts.length; i++) {
rightParts[i] = StringUtils.leftPad("", left.length() + 3) + rightParts[i];
}
System.out.println(ansi().bold().fg(Color.GREEN) + left + ansi().boldOff().fg(Color.WHITE) + " - " + ansi().bold() + StringUtils.join(rightParts, LINESEP));
}
System.out.println();
System.out.println(ansi().boldOff().fg(Color.WHITE) + "See what options are available:");
System.out.println(" hapi-fhir-cli help {command}");
System.out.println();
}
private static void logAppHeader() {
System.out.flush();
System.out.println("------------------------------------------------------------");
System.out.println("\ud83d\udd25 " + ansi().bold() + "HAPI FHIR" + ansi().boldOff() + " " + VersionUtil.getVersion() + " - Command Line Tool");
System.out.println("------------------------------------------------------------");
System.out.println("Max configured JVM memory (Xmx): " + FileUtils.getFileSizeDisplay(Runtime.getRuntime().maxMemory(), 1));
System.out.println("Detected Java version: " + System.getProperty("java.version"));
System.out.println("------------------------------------------------------------");
} }
public static void main(String[] theArgs) { public static void main(String[] theArgs) {
loggingConfigOff(); new App().run(theArgs);
AnsiConsole.systemInstall();
// log version while the logging is off
VersionUtil.getVersion();
if (theArgs.length == 0) {
logUsage();
return;
}
if (theArgs[0].equals("help")) {
if (theArgs.length < 2) {
logUsage();
return;
}
BaseCommand command = null;
for (BaseCommand nextCommand : ourCommands) {
if (nextCommand.getCommandName().equals(theArgs[1])) {
command = nextCommand;
break;
}
}
if (command == null) {
System.err.println("Unknown command: " + theArgs[1]);
return;
}
logCommandUsage(command);
return;
}
BaseCommand command = null;
for (BaseCommand nextCommand : ourCommands) {
if (nextCommand.getCommandName().equals(theArgs[0])) {
command = nextCommand;
break;
}
}
if (command == null) {
System.out.println("Unrecognized command: " + ansi().bold().fg(Color.RED) + theArgs[0] + ansi().boldOff().fg(Ansi.Color.WHITE));
System.out.println();
logUsage();
return;
}
Options options = command.getOptions();
DefaultParser parser = new DefaultParser();
CommandLine parsedOptions;
logAppHeader();
validateJavaVersion();
loggingConfigOn();
try {
String[] args = Arrays.asList(theArgs).subList(1, theArgs.length).toArray(new String[theArgs.length - 1]);
parsedOptions = parser.parse(options, args, true);
if (parsedOptions.getArgList().isEmpty()==false) {
throw new ParseException("Unrecognized argument: " + parsedOptions.getArgList().get(0).toString());
}
// Actually execute the command
command.run(parsedOptions);
} catch (ParseException e) {
loggingConfigOff();
System.err.println("Invalid command options for command: " + command.getCommandName());
System.err.println(" " + ansi().fg(Color.RED).bold() + e.getMessage());
System.err.println("" + ansi().fg(Color.WHITE).boldOff());
logCommandUsageNoHeader(command);
System.exit(1);
} catch (CommandFailureException e) {
ourLog.error(e.getMessage());
System.exit(1);
} catch (Exception e) {
ourLog.error("Error during execution: ", e);
System.exit(1);
}
} }
private static void validateJavaVersion() {
String specVersion = System.getProperty("java.specification.version");
double version = Double.parseDouble(specVersion);
if (version < 1.8) {
System.err.flush();
System.err.println("HAPI-CLI requires Java 1.8+ to run (detected " + specVersion + ")");
System.err.println("Note that the HAPI library requires only Java 1.6, but you must install");
System.err.println("a newer JVM in order to use the HAPI CLI tool.");
System.exit(1);
}
}
} }

View File

@ -0,0 +1,256 @@
package ca.uhn.fhir.cli;
import ca.uhn.fhir.util.VersionUtil;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import com.phloc.commons.io.file.FileUtils;
import org.apache.commons.cli.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.AnsiConsole;
import org.slf4j.LoggerFactory;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.fusesource.jansi.Ansi.ansi;
public abstract class BaseApp {
public static final String STACKFILTER_PATTERN = "%xEx{full, sun.reflect, org.junit, org.eclipse, java.lang.reflect.Method, org.springframework, org.hibernate, com.sun.proxy, org.attoparser, org.thymeleaf}";
public static final String STACKFILTER_PATTERN_PROP = "log.stackfilter.pattern";
public static final String LINESEP = System.getProperty("line.separator");
protected static final org.slf4j.Logger ourLog;
private static List<BaseCommand> ourCommands;
static {
System.setProperty(STACKFILTER_PATTERN_PROP, STACKFILTER_PATTERN);
loggingConfigOff();
// We don't use qualified names for loggers in CLI
ourLog = LoggerFactory.getLogger(App.class.getSimpleName());
}
private void logAppHeader() {
System.out.flush();
System.out.println("------------------------------------------------------------");
System.out.println("\ud83d\udd25 " + ansi().bold() + " " + provideProductName() + ansi().boldOff() + " " + provideProductVersion() + " - Command Line Tool");
System.out.println("------------------------------------------------------------");
System.out.println("Max configured JVM memory (Xmx): " + FileUtils.getFileSizeDisplay(Runtime.getRuntime().maxMemory(), 1));
System.out.println("Detected Java version: " + System.getProperty("java.version"));
System.out.println("------------------------------------------------------------");
}
private void logCommandUsage(BaseCommand theCommand) {
logAppHeader();
logCommandUsageNoHeader(theCommand);
}
private void logCommandUsageNoHeader(BaseCommand theCommand) {
System.out.println("Usage:");
System.out.println(" " + provideCommandName() + " " + theCommand.getCommandName() + " [options]");
System.out.println();
System.out.println("Options:");
// This is passed in from the launch script
String columnsString = System.getProperty("columns");
int columns;
try {
columns = Integer.parseInt(columnsString);
columns = Math.max(columns, 40);
columns = Math.min(columns, 180);
} catch (Exception e) {
columns = 80;
}
HelpFormatter fmt = new HelpFormatter();
PrintWriter pw = new PrintWriter(System.out);
fmt.printOptions(pw, columns, theCommand.getOptions(), 2, 2);
pw.flush();
pw.close();
}
private void logUsage() {
logAppHeader();
System.out.println("Usage:");
System.out.println(" " + provideCommandName() + " {command} [options]");
System.out.println();
System.out.println("Commands:");
int longestCommandLength = 0;
for (BaseCommand next : ourCommands) {
longestCommandLength = Math.max(longestCommandLength, next.getCommandName().length());
}
for (BaseCommand next : ourCommands) {
String left = " " + StringUtils.rightPad(next.getCommandName(), longestCommandLength);
String[] rightParts = WordUtils.wrap(next.getCommandDescription(), 80 - (left.length() + 3)).split("\\n");
for (int i = 1; i < rightParts.length; i++) {
rightParts[i] = StringUtils.leftPad("", left.length() + 3) + rightParts[i];
}
System.out.println(ansi().bold().fg(Ansi.Color.GREEN) + left + ansi().boldOff().fg(Ansi.Color.WHITE) + " - " + ansi().bold() + StringUtils.join(rightParts, LINESEP));
}
System.out.println();
System.out.println(ansi().boldOff().fg(Ansi.Color.WHITE) + "See what options are available:");
System.out.println(" " + provideCommandName() + " help {command}");
System.out.println();
}
protected abstract String provideCommandName();
public List<BaseCommand> provideCommands() {
ArrayList<BaseCommand> commands = new ArrayList<>();
commands.add(new RunServerCommand());
commands.add(new ExampleDataUploader());
commands.add(new ValidateCommand());
commands.add(new ValidationDataUploader());
commands.add(new WebsocketSubscribeCommand());
commands.add(new UploadTerminologyCommand());
commands.add(new IgPackUploader());
return commands;
}
protected abstract String provideProductName();
protected abstract String provideProductVersion();
@SuppressWarnings("ResultOfMethodCallIgnored")
public void run(String[] theArgs) {
loggingConfigOff();
validateJavaVersion();
AnsiConsole.systemInstall();
// log version while the logging is off
VersionUtil.getVersion();
// Set up command list
ourCommands = new ArrayList<>();
ourCommands.addAll(provideCommands());
Collections.sort(ourCommands);
if (theArgs.length == 0) {
logUsage();
return;
}
if (theArgs[0].equals("help")) {
if (theArgs.length < 2) {
logUsage();
return;
}
BaseCommand command = null;
for (BaseCommand nextCommand : ourCommands) {
if (nextCommand.getCommandName().equals(theArgs[1])) {
command = nextCommand;
break;
}
}
if (command == null) {
System.err.println("Unknown command: " + theArgs[1]);
return;
}
logCommandUsage(command);
return;
}
BaseCommand command = null;
for (BaseCommand nextCommand : ourCommands) {
if (nextCommand.getCommandName().equals(theArgs[0])) {
command = nextCommand;
break;
}
}
if (command == null) {
System.out.println("Unrecognized command: " + ansi().bold().fg(Ansi.Color.RED) + theArgs[0] + ansi().boldOff().fg(Ansi.Color.WHITE));
System.out.println();
logUsage();
return;
}
Options options = command.getOptions();
DefaultParser parser = new DefaultParser();
CommandLine parsedOptions;
logAppHeader();
validateJavaVersion();
loggingConfigOn();
try {
String[] args = Arrays.copyOfRange(theArgs, 1, theArgs.length);
parsedOptions = parser.parse(options, args, true);
if (!parsedOptions.getArgList().isEmpty()) {
throw new ParseException("Unrecognized argument: " + parsedOptions.getArgList().get(0));
}
// Actually execute the command
command.run(parsedOptions);
if (!"true".equals(System.getProperty("test"))) {
System.exit(0);
}
} catch (ParseException e) {
loggingConfigOff();
System.err.println("Invalid command options for command: " + command.getCommandName());
System.err.println(" " + ansi().fg(Ansi.Color.RED).bold() + e.getMessage());
System.err.println("" + ansi().fg(Ansi.Color.WHITE).boldOff());
logCommandUsageNoHeader(command);
System.exit(1);
} catch (CommandFailureException e) {
ourLog.error(e.getMessage());
if ("true".equals(System.getProperty("test"))) {
throw e;
} else {
System.exit(1);
}
} catch (Exception e) {
ourLog.error("Error during execution: ", e);
if ("true".equals(System.getProperty("test"))) {
throw new CommandFailureException("Error: " + e.toString(), e);
} else {
System.exit(1);
}
}
}
private void validateJavaVersion() {
String specVersion = System.getProperty("java.specification.version");
double version = Double.parseDouble(specVersion);
if (version < 1.8) {
System.err.flush();
System.err.println(provideProductName() + " requires Java 1.8+ to run (detected " + specVersion + ")");
System.exit(1);
}
}
private static void loggingConfigOff() {
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
configurator.doConfigure(App.class.getResourceAsStream("/logback-cli-off.xml"));
} catch (JoranException e) {
e.printStackTrace();
}
}
private static void loggingConfigOn() {
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
((LoggerContext) LoggerFactory.getILoggerFactory()).reset();
configurator.doConfigure(App.class.getResourceAsStream("/logback-cli-on.xml"));
} catch (JoranException e) {
e.printStackTrace();
}
}
}

View File

@ -2,11 +2,15 @@ package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
import com.google.common.base.Charsets;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option; import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@ -16,21 +20,28 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClientBuilder;
import org.fusesource.jansi.Ansi; import org.fusesource.jansi.Ansi;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;
import java.io.File; import java.io.*;
import java.io.FileOutputStream; import java.util.Arrays;
import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.*;
public abstract class BaseCommand implements Comparable<BaseCommand> { public abstract class BaseCommand implements Comparable<BaseCommand> {
public static final String BASE_URL_PARAM = "t";
private static final String SPEC_DEFAULT_VERSION = "dstu3"; public static final String BASIC_AUTH_OPTION = "b";
public static final String BASIC_AUTH_LONGOPT = "basic-auth";
public static final String BEARER_TOKEN_LONGOPT = "bearer-token";
public static final String FHIR_VERSION_OPTION = "v";
private static final Logger ourLog = LoggerFactory.getLogger(BaseCommand.class); private static final Logger ourLog = LoggerFactory.getLogger(BaseCommand.class);
private FhirContext myFhirCtx; private FhirContext myFhirCtx;
@ -38,10 +49,57 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
super(); super();
} }
protected void addBasicAuthOption(Options theOptions) {
addOptionalOption(theOptions, BASIC_AUTH_OPTION, BASIC_AUTH_LONGOPT, true, "If specified, this parameter supplies a username and password (in the format \"username:password\") to include in an HTTP Basic Auth header");
addOptionalOption(theOptions, null, BEARER_TOKEN_LONGOPT, true, "If specified, this parameter supplies a Bearer Token to supply with the request");
}
protected void addFhirVersionOption(Options theOptions) { protected void addFhirVersionOption(Options theOptions) {
Option opt = new Option("f", "fhirversion", true, "Spec version to upload (default is '" + SPEC_DEFAULT_VERSION + "')"); String versions = Arrays.stream(FhirVersionEnum.values())
opt.setRequired(false); .filter(t -> t != FhirVersionEnum.DSTU2_1 && t != FhirVersionEnum.DSTU2_HL7ORG)
theOptions.addOption(opt); .map(t -> t.name().toLowerCase())
.sorted()
.collect(Collectors.joining(", "));
addRequiredOption(theOptions, FHIR_VERSION_OPTION, "fhir-version", "version", "The FHIR version being used. Valid values: " + versions);
}
private void addOption(Options theOptions, boolean theRequired, String theOpt, String theLong, boolean theHasArgument, String theArgumentName, String theDescription) {
Option option = new Option(theOpt, theLong, theHasArgument, theDescription);
option.setRequired(theRequired);
if (theHasArgument && isNotBlank(theArgumentName)) {
option.setArgName(theArgumentName);
}
if (isNotBlank(theOpt)) {
if (theOptions.getOption(theOpt) != null) {
throw new IllegalStateException("Duplicate option: " + theOpt);
}
}
if (isNotBlank(theLong)) {
if (theOptions.getOption(theLong) != null) {
throw new IllegalStateException("Duplicate option: " + theLong);
}
}
theOptions.addOption(option);
}
protected void addOptionalOption(Options theOptions, String theOpt, String theLong, boolean theTakesArgument, String theDescription) {
addOption(theOptions, false, theOpt, theLong, theTakesArgument, null, theDescription);
}
protected void addOptionalOption(Options theOptions, String theOpt, String theLong, String theArgumentName, String theDescription) {
addOption(theOptions, false, theOpt, theLong, isNotBlank(theArgumentName), theArgumentName, theDescription);
}
protected void addRequiredOption(Options theOptions, String theOpt, String theLong, boolean theTakesArgument, String theDescription) {
addOption(theOptions, true, theOpt, theLong, theTakesArgument, null, theDescription);
}
protected void addRequiredOption(Options theOptions, String theOpt, String theLong, String theArgumentName, String theDescription) {
boolean hasArgument = isNotBlank(theArgumentName);
boolean required = true;
addOption(theOptions, required, theOpt, theLong, hasArgument, theArgumentName, theDescription);
} }
@Override @Override
@ -49,6 +107,18 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
return getCommandName().compareTo(theO.getCommandName()); return getCommandName().compareTo(theO.getCommandName());
} }
protected Reader createReader(File theInputFile) throws IOException {
InputStream inputStream = new FileInputStream(theInputFile);
if (theInputFile.getName().toLowerCase().endsWith(".gz")) {
inputStream = new GZIPInputStream(inputStream);
}
if (theInputFile.getName().toLowerCase().endsWith(".bz2")) {
inputStream = new BZip2CompressorInputStream(inputStream);
}
return new InputStreamReader(inputStream, Charsets.UTF_8);
}
private void downloadFileFromInternet(CloseableHttpResponse result, File localFile) throws IOException { private void downloadFileFromInternet(CloseableHttpResponse result, File localFile) throws IOException {
FileOutputStream buffer = FileUtils.openOutputStream(localFile); FileOutputStream buffer = FileUtils.openOutputStream(localFile);
try { try {
@ -88,40 +158,73 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
} }
} }
/**
* @return Returns the complete authorization header value using the "-b" option
*/
protected String getAndParseOptionBasicAuthHeader(CommandLine theCommandLine) {
return getAndParseOptionBasicAuthHeader(theCommandLine, BASIC_AUTH_OPTION);
}
/**
* @return Returns the complete authorization header value using an arbitrary option
*/
protected String getAndParseOptionBasicAuthHeader(CommandLine theCommandLine, String theOptionName) {
String basicAuthHeaderValue = null;
if (theCommandLine.hasOption(theOptionName)) {
byte[] basicAuth = theCommandLine.getOptionValue(theOptionName).getBytes();
String base64EncodedBasicAuth = Base64Utils.encodeToString(basicAuth);
basicAuthHeaderValue = Constants.HEADER_AUTHORIZATION_VALPREFIX_BASIC + base64EncodedBasicAuth;
} else {
basicAuthHeaderValue = null;
}
return basicAuthHeaderValue;
}
public <T extends Enum> T getAndParseOptionEnum(CommandLine theCommandLine, String theOption, Class<T> theEnumClass, T theDefault) throws ParseException {
String val = theCommandLine.getOptionValue(theOption);
if (isBlank(val)) {
return theDefault;
}
try {
return (T) Enum.valueOf(theEnumClass, val);
} catch (Exception e) {
throw new ParseException("Invalid option \"" + val + "\" for option -" + theOption);
}
}
public Integer getAndParsePositiveIntegerParam(CommandLine theCommandLine, String theName) throws ParseException {
String value = theCommandLine.getOptionValue(theName);
value = trim(value);
if (isBlank(value)) {
return null;
}
try {
int valueInt = Integer.parseInt(value);
if (valueInt < 1) {
throw new ParseException("Value for argument " + theName + " must be a positive integer, got: " + value);
}
return valueInt;
} catch (NumberFormatException e) {
throw new ParseException("Value for argument " + theName + " must be a positive integer, got: " + value);
}
}
public Class<? extends IBaseBundle> getBundleTypeForFhirVersion() {
return getFhirContext().getResourceDefinition("Bundle").getImplementingClass(IBaseBundle.class);
}
public abstract String getCommandDescription(); public abstract String getCommandDescription();
public abstract String getCommandName(); public abstract String getCommandName();
public abstract Options getOptions(); protected FhirContext getFhirContext() {
protected FhirContext getSpecVersionContext(CommandLine theCommandLine) throws ParseException {
if (myFhirCtx == null) {
String specVersion = theCommandLine.getOptionValue("f", SPEC_DEFAULT_VERSION);
specVersion = specVersion.toLowerCase();
FhirVersionEnum version;
if ("dstu2".equals(specVersion)) {
version = FhirVersionEnum.DSTU2;
} else if ("dstu3".equals(specVersion)) {
version = FhirVersionEnum.DSTU3;
} else if ("r4".equals(specVersion)) {
version = FhirVersionEnum.R4;
} else {
throw new ParseException("Unknown spec version: " + specVersion);
}
myFhirCtx = new FhirContext(version);
}
return myFhirCtx; return myFhirCtx;
} }
// public FhirContext getFhirCtx() { public abstract Options getOptions();
// if (myFhirCtx == null) {
// myFhirCtx = FhirContext.forDstu2();
// }
// return myFhirCtx;
// }
protected Collection<File> loadFile(FhirContext theCtx, String theSpecUrl, String theFilepath, boolean theCacheFile) throws IOException { protected Collection<File> loadFile(String theSpecUrl, String theFilepath, boolean theCacheFile) throws IOException {
String userHomeDir = System.getProperty("user.home"); String userHomeDir = System.getProperty("user.home");
File applicationDir = new File(userHomeDir + File.separator + "." + "hapi-fhir-cli"); File applicationDir = new File(userHomeDir + File.separator + "." + "hapi-fhir-cli");
@ -138,7 +241,7 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
File suppliedFile = new File(FilenameUtils.normalize(theFilepath)); File suppliedFile = new File(FilenameUtils.normalize(theFilepath));
if (suppliedFile.isDirectory()) { if (suppliedFile.isDirectory()) {
inputFiles = FileUtils.listFiles(suppliedFile, new String[]{"zip"}, false); inputFiles = FileUtils.listFiles(suppliedFile, new String[] {"zip"}, false);
} else { } else {
inputFiles = Collections.singletonList(suppliedFile); inputFiles = Collections.singletonList(suppliedFile);
} }
@ -148,13 +251,13 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
File cacheDir = new File(applicationDir, "cache"); File cacheDir = new File(applicationDir, "cache");
FileUtils.forceMkdir(cacheDir); FileUtils.forceMkdir(cacheDir);
File inputFile = new File(cacheDir, "examples-json-" + theCtx.getVersion().getVersion() + ".zip"); File inputFile = new File(cacheDir, "examples-json-" + getFhirContext().getVersion().getVersion() + ".zip");
Date cacheExpiryDate = DateUtils.addHours(new Date(), -12); Date cacheExpiryDate = DateUtils.addHours(new Date(), -12);
if (!inputFile.exists() | (theCacheFile && FileUtils.isFileOlder(inputFile, cacheExpiryDate))) { if (!inputFile.exists() | (theCacheFile && FileUtils.isFileOlder(inputFile, cacheExpiryDate))) {
File exampleFileDownloading = new File(cacheDir, "examples-json-" + theCtx.getVersion().getVersion() + ".zip.partial"); File exampleFileDownloading = new File(cacheDir, "examples-json-" + getFhirContext().getVersion().getVersion() + ".zip.partial");
HttpGet get = new HttpGet(theSpecUrl); HttpGet get = new HttpGet(theSpecUrl);
CloseableHttpClient client = HttpClientBuilder.create().build(); CloseableHttpClient client = HttpClientBuilder.create().build();
@ -184,12 +287,46 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
return inputFiles; return inputFiles;
} }
protected IGenericClient newClient(FhirContext ctx, String theBaseUrl) { protected IGenericClient newClient(CommandLine theCommandLine) {
ctx.getRestfulClientFactory().setSocketTimeout(10 * 60 * 1000); return newClient(theCommandLine, BASE_URL_PARAM, BASIC_AUTH_OPTION, BEARER_TOKEN_LONGOPT);
IGenericClient fhirClient = ctx.newRestfulGenericClient(theBaseUrl);
return fhirClient;
} }
public abstract void run(CommandLine theCommandLine) throws ParseException, Exception; protected IGenericClient newClient(CommandLine theCommandLine, String theBaseUrlParamName, String theBasicAuthOptionName, String theBearerTokenOptionName) {
String baseUrl = theCommandLine.getOptionValue(theBaseUrlParamName);
myFhirCtx.getRestfulClientFactory().setSocketTimeout(10 * 60 * 1000);
IGenericClient retVal = myFhirCtx.newRestfulGenericClient(baseUrl);
String basicAuthHeaderValue = getAndParseOptionBasicAuthHeader(theCommandLine, theBasicAuthOptionName);
if (isNotBlank(basicAuthHeaderValue)) {
retVal.registerInterceptor(new SimpleRequestHeaderInterceptor(Constants.HEADER_AUTHORIZATION, basicAuthHeaderValue));
}
if (isNotBlank(theBearerTokenOptionName)) {
String bearerToken = theCommandLine.getOptionValue(theBearerTokenOptionName);
if (isNotBlank(bearerToken)) {
retVal.registerInterceptor(new SimpleRequestHeaderInterceptor(Constants.HEADER_AUTHORIZATION, Constants.HEADER_AUTHORIZATION_VALPREFIX_BEARER + bearerToken));
}
}
return retVal;
}
protected void parseFhirContext(CommandLine theCommandLine) throws ParseException {
String version = theCommandLine.getOptionValue(FHIR_VERSION_OPTION);
if (isBlank(version)) {
throw new ParseException("Missing required option: -" + FHIR_VERSION_OPTION);
}
try {
FhirVersionEnum versionEnum = FhirVersionEnum.valueOf(version.toUpperCase());
myFhirCtx = versionEnum.newContext();
} catch (Exception e) {
throw new ParseException("Invalid FHIR version string: " + version);
}
}
public abstract void run(CommandLine theCommandLine) throws ParseException, ExecutionException;
} }

View File

@ -2,6 +2,10 @@ package ca.uhn.fhir.cli;
public class CommandFailureException extends Error { public class CommandFailureException extends Error {
public CommandFailureException(Throwable theCause) {
super(theCause.getMessage(), theCause);
}
public CommandFailureException(String theMessage) { public CommandFailureException(String theMessage) {
super(theMessage); super(theMessage);
} }

View File

@ -33,7 +33,6 @@ import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
@ -327,6 +326,8 @@ public class ExampleDataUploader extends BaseCommand {
opt.setRequired(false); opt.setRequired(false);
options.addOption(opt); options.addOption(opt);
addBasicAuthOption(options);
return options; return options;
} }
@ -573,8 +574,9 @@ public class ExampleDataUploader extends BaseCommand {
} }
@Override @Override
public void run(CommandLine theCommandLine) throws Exception { public void run(CommandLine theCommandLine) throws ParseException {
FhirContext ctx = getSpecVersionContext(theCommandLine); parseFhirContext(theCommandLine);
FhirContext ctx = getFhirContext();
String targetServer = theCommandLine.getOptionValue("t"); String targetServer = theCommandLine.getOptionValue("t");
if (isBlank(targetServer)) { if (isBlank(targetServer)) {
@ -613,17 +615,22 @@ public class ExampleDataUploader extends BaseCommand {
boolean cacheFile = theCommandLine.hasOption('c'); boolean cacheFile = theCommandLine.hasOption('c');
Collection<File> inputFiles = loadFile(ctx, specUrl, filepath, cacheFile); Collection<File> inputFiles = null;
try {
for (File inputFile : inputFiles) { inputFiles = loadFile(specUrl, filepath, cacheFile);
IBaseBundle bundle = getBundleFromFile(limit, inputFile, ctx); for (File inputFile : inputFiles) {
processBundle(ctx, bundle); IBaseBundle bundle = getBundleFromFile(limit, inputFile, ctx);
sendBundleToTarget(targetServer, ctx, bundle); processBundle(ctx, bundle);
sendBundleToTarget(targetServer, ctx, bundle, theCommandLine);
}
} catch (Exception e) {
throw new CommandFailureException(e);
} }
} }
private void sendBundleToTarget(String targetServer, FhirContext ctx, IBaseBundle bundle) throws Exception { private void sendBundleToTarget(String targetServer, FhirContext ctx, IBaseBundle bundle, CommandLine theCommandLine) throws Exception {
List<IBaseResource> resources = BundleUtil.toListOfResources(ctx, bundle); List<IBaseResource> resources = BundleUtil.toListOfResources(ctx, bundle);
for (Iterator<IBaseResource> iter = resources.iterator(); iter.hasNext(); ) { for (Iterator<IBaseResource> iter = resources.iterator(); iter.hasNext(); ) {
@ -707,7 +714,7 @@ public class ExampleDataUploader extends BaseCommand {
} else { } else {
ourLog.info("Uploading bundle to server: " + targetServer); ourLog.info("Uploading bundle to server: " + targetServer);
IGenericClient fhirClient = newClient(ctx, targetServer); IGenericClient fhirClient = newClient(theCommandLine);
fhirClient.registerInterceptor(new GZipContentInterceptor()); fhirClient.registerInterceptor(new GZipContentInterceptor());
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.igpacks.parser.IgPackParserDstu3; import ca.uhn.fhir.igpacks.parser.IgPackParserDstu3;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IGenericClient;
import net.sf.ehcache.transaction.xa.commands.Command;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option; import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
@ -16,6 +17,8 @@ import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collection; import java.util.Collection;
public class IgPackUploader extends BaseCommand { public class IgPackUploader extends BaseCommand {
@ -48,20 +51,31 @@ public class IgPackUploader extends BaseCommand {
} }
@Override @Override
public void run(CommandLine theCommandLine) throws ParseException, Exception { public void run(CommandLine theCommandLine) throws ParseException{
FhirContext ctx = getSpecVersionContext(theCommandLine); parseFhirContext(theCommandLine);
String targetServer = theCommandLine.getOptionValue("t"); IGenericClient client = newClient(theCommandLine);
IGenericClient client = ctx.newRestfulGenericClient(targetServer);
String url = theCommandLine.getOptionValue("u"); String url = theCommandLine.getOptionValue("u");
Collection<File> files = loadFile(ctx, url, null, false); Collection<File> files = null;
try {
files = loadFile(url, null, false);
} catch (IOException e) {
throw new CommandFailureException(e);
}
for (File nextFile : files) { for (File nextFile : files) {
FhirContext ctx = getFhirContext();
switch (ctx.getVersion().getVersion()) { switch (ctx.getVersion().getVersion()) {
case DSTU3: case DSTU3:
IgPackParserDstu3 packParser = new IgPackParserDstu3(ctx); IgPackParserDstu3 packParser = new IgPackParserDstu3(ctx);
IValidationSupport ig = packParser.parseIg(new FileInputStream(nextFile), nextFile.getName()); IValidationSupport ig = null;
try {
ig = packParser.parseIg(new FileInputStream(nextFile), nextFile.getName());
} catch (FileNotFoundException e) {
throw new CommandFailureException(e);
}
Iterable<IBaseResource> conformanceResources = ig.fetchAllConformanceResources(ctx); Iterable<IBaseResource> conformanceResources = ig.fetchAllConformanceResources(ctx);
for (IBaseResource nextResource : conformanceResources) { for (IBaseResource nextResource : conformanceResources) {
String nextResourceUrl = ((IPrimitiveType<?>)ctx.newTerser().getSingleValueOrNull(nextResource, "url")).getValueAsString(); String nextResourceUrl = ((IPrimitiveType<?>)ctx.newTerser().getSingleValueOrNull(nextResource, "url")).getValueAsString();

View File

@ -34,13 +34,14 @@ public class RunServerCommand extends BaseCommand {
private static final String OPTION_P = "p"; private static final String OPTION_P = "p";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RunServerCommand.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RunServerCommand.class);
public static final String RUN_SERVER_COMMAND = "run-server";
private int myPort; private int myPort;
private Server myServer; private Server myServer;
@Override @Override
public String getCommandName() { public String getCommandName() {
return "run-server"; return RUN_SERVER_COMMAND;
} }
@Override @Override
@ -68,6 +69,8 @@ public class RunServerCommand extends BaseCommand {
@Override @Override
public void run(CommandLine theCommandLine) throws ParseException { public void run(CommandLine theCommandLine) throws ParseException {
parseFhirContext(theCommandLine);
myPort = parseOptionInteger(theCommandLine, OPTION_P, DEFAULT_PORT); myPort = parseOptionInteger(theCommandLine, OPTION_P, DEFAULT_PORT);
if (theCommandLine.hasOption(OPTION_LOWMEM)) { if (theCommandLine.hasOption(OPTION_LOWMEM)) {
@ -104,7 +107,7 @@ public class RunServerCommand extends BaseCommand {
} }
} }
ContextHolder.setCtx(getSpecVersionContext(theCommandLine)); ContextHolder.setCtx(getFhirContext());
ourLog.info("Preparing HAPI FHIR JPA server on port {}", myPort); ourLog.info("Preparing HAPI FHIR JPA server on port {}", myPort);
File tempWarFile; File tempWarFile;

View File

@ -1,28 +1,32 @@
package ca.uhn.fhir.cli; package ca.uhn.fhir.cli;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import org.apache.commons.cli.*;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.BearerTokenAuthInterceptor; import ca.uhn.fhir.rest.client.interceptor.BearerTokenAuthInterceptor;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class UploadTerminologyCommand extends BaseCommand { public class UploadTerminologyCommand extends BaseCommand {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UploadTerminologyCommand.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UploadTerminologyCommand.class);
private static final String BASE_URL_PARAM = "t";
private static final String UPLOAD_EXTERNAL_CODE_SYSTEM = "upload-external-code-system";
@Override @Override
public String getCommandDescription() { public String getCommandDescription() {
return "Uploads a terminology package (e.g. a SNOMED CT ZIP file) to a HAPI JPA server. " return "Uploads a terminology package (e.g. a SNOMED CT ZIP file) to a server, using the $" + UPLOAD_EXTERNAL_CODE_SYSTEM + " operation.";
+ "Note that this command uses a custom operation that is only implemented on HAPI "
+ "JPA servers that have been configured to accept it.";
} }
@Override @Override
@ -45,28 +49,27 @@ public class UploadTerminologyCommand extends BaseCommand {
opt.setRequired(false); opt.setRequired(false);
options.addOption(opt); options.addOption(opt);
opt = new Option("d", "data", true, "Local *.zip containing file to use to upload"); opt = new Option("d", "data", true, "Local file to use to upload (can be a raw file or a ZIP containing the raw file)");
opt.setRequired(false); opt.setRequired(false);
options.addOption(opt); options.addOption(opt);
opt = new Option("b", "bearer-token", true, "Bearer token to add to the request"); addBasicAuthOption(options);
opt.setRequired(false);
options.addOption(opt);
opt = new Option("v", "verbose", false, "Verbose output"); opt = new Option("v", "verbose", false, "Verbose output");
opt.setRequired(false); opt.setRequired(false);
options.addOption(opt); options.addOption(opt);
return options; return options;
} }
@Override @Override
public void run(CommandLine theCommandLine) throws Exception { public void run(CommandLine theCommandLine) throws ParseException {
FhirContext ctx = getSpecVersionContext(theCommandLine); parseFhirContext(theCommandLine);
FhirContext ctx = getFhirContext();
String targetServer = theCommandLine.getOptionValue("t"); String targetServer = theCommandLine.getOptionValue(BASE_URL_PARAM);
if (isBlank(targetServer)) { if (isBlank(targetServer)) {
throw new ParseException("No target server (-t) specified"); throw new ParseException("No target server (-" + BASE_URL_PARAM + ") specified");
} else if (targetServer.startsWith("http") == false && targetServer.startsWith("file") == false) { } else if (targetServer.startsWith("http") == false && targetServer.startsWith("file") == false) {
throw new ParseException("Invalid target server specified, must begin with 'http' or 'file'"); throw new ParseException("Invalid target server specified, must begin with 'http' or 'file'");
} }
@ -75,15 +78,15 @@ public class UploadTerminologyCommand extends BaseCommand {
if (isBlank(termUrl)) { if (isBlank(termUrl)) {
throw new ParseException("No URL provided"); throw new ParseException("No URL provided");
} }
String[] datafile = theCommandLine.getOptionValues("d"); String[] datafile = theCommandLine.getOptionValues("d");
if (datafile == null || datafile.length == 0) { if (datafile == null || datafile.length == 0) {
throw new ParseException("No data file provided"); throw new ParseException("No data file provided");
} }
String bearerToken = theCommandLine.getOptionValue("b"); String bearerToken = theCommandLine.getOptionValue("b");
IGenericClient client = super.newClient(ctx, targetServer); IGenericClient client = super.newClient(theCommandLine);
IBaseParameters inputParameters; IBaseParameters inputParameters;
if (ctx.getVersion().getVersion() == FhirVersionEnum.DSTU3) { if (ctx.getVersion().getVersion() == FhirVersionEnum.DSTU3) {
Parameters p = new Parameters(); Parameters p = new Parameters();
@ -103,12 +106,12 @@ public class UploadTerminologyCommand extends BaseCommand {
if (theCommandLine.hasOption('v')) { if (theCommandLine.hasOption('v')) {
client.registerInterceptor(new LoggingInterceptor(true)); client.registerInterceptor(new LoggingInterceptor(true));
} }
ourLog.info("Beginning upload - This may take a while..."); ourLog.info("Beginning upload - This may take a while...");
IBaseParameters response = client IBaseParameters response = client
.operation() .operation()
.onServer() .onServer()
.named("upload-external-code-system") .named(UPLOAD_EXTERNAL_CODE_SYSTEM)
.withParameters(inputParameters) .withParameters(inputParameters)
.execute(); .execute();

View File

@ -1,22 +1,24 @@
package ca.uhn.fhir.cli; package ca.uhn.fhir.cli;
import static org.apache.commons.lang3.StringUtils.*; import ca.uhn.fhir.context.FhirContext;
import static org.fusesource.jansi.Ansi.ansi; import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.SingleValidationMessage;
import java.io.*; import ca.uhn.fhir.validation.ValidationResult;
import com.phloc.commons.io.file.FileUtils;
import org.apache.commons.cli.*; import org.apache.commons.cli.*;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.text.WordUtils; import org.apache.commons.lang3.text.WordUtils;
import org.fusesource.jansi.Ansi.Color; import org.fusesource.jansi.Ansi.Color;
import org.hl7.fhir.dstu3.hapi.validation.*; import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.dstu3.model.StructureDefinition; import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import com.phloc.commons.io.file.FileUtils; import java.io.*;
import ca.uhn.fhir.context.FhirContext; import static org.apache.commons.lang3.StringUtils.*;
import ca.uhn.fhir.validation.*; import static org.fusesource.jansi.Ansi.ansi;
public class ValidateCommand extends BaseCommand { public class ValidateCommand extends BaseCommand {
@ -54,7 +56,9 @@ public class ValidateCommand extends BaseCommand {
} }
@Override @Override
public void run(CommandLine theCommandLine) throws ParseException, Exception { public void run(CommandLine theCommandLine) throws ParseException {
parseFhirContext(theCommandLine);
String fileName = theCommandLine.getOptionValue("n"); String fileName = theCommandLine.getOptionValue("n");
String contents = theCommandLine.getOptionValue("c"); String contents = theCommandLine.getOptionValue("c");
if (isNotBlank(fileName) && isNotBlank(contents)) { if (isNotBlank(fileName) && isNotBlank(contents)) {
@ -68,7 +72,11 @@ public class ValidateCommand extends BaseCommand {
String encoding = theCommandLine.getOptionValue("e", "UTF-8"); String encoding = theCommandLine.getOptionValue("e", "UTF-8");
ourLog.info("Reading file '{}' using encoding {}", fileName, encoding); ourLog.info("Reading file '{}' using encoding {}", fileName, encoding);
contents = IOUtils.toString(new InputStreamReader(new FileInputStream(fileName), encoding)); try {
contents = IOUtils.toString(new InputStreamReader(new FileInputStream(fileName), encoding));
} catch (IOException e) {
throw new CommandFailureException(e);
}
ourLog.info("Fully read - Size is {}", FileUtils.getFileSizeDisplay(contents.length())); ourLog.info("Fully read - Size is {}", FileUtils.getFileSizeDisplay(contents.length()));
} }
@ -77,7 +85,7 @@ public class ValidateCommand extends BaseCommand {
throw new ParseException("Could not detect encoding (json/xml) of contents"); throw new ParseException("Could not detect encoding (json/xml) of contents");
} }
FhirContext ctx = getSpecVersionContext(theCommandLine); FhirContext ctx = getFhirContext();
FhirValidator val = ctx.newValidator(); FhirValidator val = ctx.newValidator();
IBaseResource localProfileResource = null; IBaseResource localProfileResource = null;

View File

@ -100,10 +100,12 @@ public class ValidationDataUploader extends BaseCommand {
addFhirVersionOption(options); addFhirVersionOption(options);
opt = new Option("t", "target", true, "Base URL for the target server (e.g. \"http://example.com/fhir\")"); opt = new Option(BASE_URL_PARAM, "target", true, "Base URL for the target server (e.g. \"http://example.com/fhir\")");
opt.setRequired(true); opt.setRequired(true);
options.addOption(opt); options.addOption(opt);
addBasicAuthOption(options);
opt = new Option("e", "exclude", true, "Exclude uploading the given resources, e.g. \"-e dicom-dcim,foo\""); opt = new Option("e", "exclude", true, "Exclude uploading the given resources, e.g. \"-e dicom-dcim,foo\"");
opt.setRequired(false); opt.setRequired(false);
options.addOption(opt); options.addOption(opt);
@ -113,6 +115,8 @@ public class ValidationDataUploader extends BaseCommand {
@Override @Override
public void run(CommandLine theCommandLine) throws ParseException { public void run(CommandLine theCommandLine) throws ParseException {
parseFhirContext(theCommandLine);
String targetServer = theCommandLine.getOptionValue("t"); String targetServer = theCommandLine.getOptionValue("t");
if (isBlank(targetServer)) { if (isBlank(targetServer)) {
throw new ParseException("No target server (-t) specified"); throw new ParseException("No target server (-t) specified");
@ -120,7 +124,7 @@ public class ValidationDataUploader extends BaseCommand {
throw new ParseException("Invalid target server specified, must begin with 'http'"); throw new ParseException("Invalid target server specified, must begin with 'http'");
} }
FhirContext ctx = getSpecVersionContext(theCommandLine); FhirContext ctx = getFhirContext();
String exclude = theCommandLine.getOptionValue("e"); String exclude = theCommandLine.getOptionValue("e");
if (isNotBlank(exclude)) { if (isNotBlank(exclude)) {
@ -134,18 +138,19 @@ public class ValidationDataUploader extends BaseCommand {
} }
if (ctx.getVersion().getVersion() == FhirVersionEnum.DSTU2) { if (ctx.getVersion().getVersion() == FhirVersionEnum.DSTU2) {
uploadDefinitionsDstu2(targetServer, ctx); uploadDefinitionsDstu2(theCommandLine, ctx);
} else if (ctx.getVersion().getVersion() == FhirVersionEnum.DSTU3) { } else if (ctx.getVersion().getVersion() == FhirVersionEnum.DSTU3) {
uploadDefinitionsDstu3(targetServer, ctx); uploadDefinitionsDstu3(theCommandLine, ctx);
} else if (ctx.getVersion().getVersion() == FhirVersionEnum.R4) { } else if (ctx.getVersion().getVersion() == FhirVersionEnum.R4) {
uploadDefinitionsR4(targetServer, ctx); uploadDefinitionsR4(theCommandLine, ctx);
} }
} }
private void uploadDefinitionsDstu2(String targetServer, FhirContext ctx) throws CommandFailureException { private void uploadDefinitionsDstu2(CommandLine theCommandLine, FhirContext ctx) throws CommandFailureException {
IGenericClient client = newClient(ctx, targetServer); IGenericClient client = newClient(theCommandLine);
ourLog.info("Uploading definitions to server: " + targetServer);
ourLog.info("Uploading definitions to server");
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
@ -242,9 +247,9 @@ public class ValidationDataUploader extends BaseCommand {
ourLog.info("Finished uploading definitions to server (took {} ms)", delay); ourLog.info("Finished uploading definitions to server (took {} ms)", delay);
} }
private void uploadDefinitionsDstu3(String targetServer, FhirContext ctx) throws CommandFailureException { private void uploadDefinitionsDstu3(CommandLine theCommandLine, FhirContext theCtx) throws CommandFailureException {
IGenericClient client = newClient(ctx, targetServer); IGenericClient client = newClient(theCommandLine);
ourLog.info("Uploading definitions to server: " + targetServer); ourLog.info("Uploading definitions to server");
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
int total = 0; int total = 0;
@ -253,12 +258,12 @@ public class ValidationDataUploader extends BaseCommand {
String vsContents; String vsContents;
try { try {
ctx.getVersion().getPathToSchemaDefinitions(); theCtx.getVersion().getPathToSchemaDefinitions();
vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/dstu3/model/valueset/" + "valuesets.xml"), "UTF-8"); vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/dstu3/model/valueset/" + "valuesets.xml"), "UTF-8");
} catch (IOException e) { } catch (IOException e) {
throw new CommandFailureException(e.toString()); throw new CommandFailureException(e.toString());
} }
bundle = ctx.newXmlParser().parseResource(org.hl7.fhir.dstu3.model.Bundle.class, vsContents); bundle = theCtx.newXmlParser().parseResource(org.hl7.fhir.dstu3.model.Bundle.class, vsContents);
filterBundle(bundle); filterBundle(bundle);
total = bundle.getEntry().size(); total = bundle.getEntry().size();
@ -267,7 +272,7 @@ public class ValidationDataUploader extends BaseCommand {
org.hl7.fhir.dstu3.model.Resource next = i.getResource(); org.hl7.fhir.dstu3.model.Resource next = i.getResource();
next.setId(next.getIdElement().toUnqualifiedVersionless()); next.setId(next.getIdElement().toUnqualifiedVersionless());
int bytes = ctx.newXmlParser().encodeResourceToString(next).length(); int bytes = theCtx.newXmlParser().encodeResourceToString(next).length();
ourLog.info("Uploading ValueSet {}/{} : {} ({} bytes}", new Object[] {count, total, next.getIdElement().getValue(), bytes}); ourLog.info("Uploading ValueSet {}/{} : {} ({} bytes}", new Object[] {count, total, next.getIdElement().getValue(), bytes});
try { try {
@ -287,7 +292,7 @@ public class ValidationDataUploader extends BaseCommand {
throw new CommandFailureException(e.toString()); throw new CommandFailureException(e.toString());
} }
bundle = ctx.newXmlParser().parseResource(org.hl7.fhir.dstu3.model.Bundle.class, vsContents); bundle = theCtx.newXmlParser().parseResource(org.hl7.fhir.dstu3.model.Bundle.class, vsContents);
filterBundle(bundle); filterBundle(bundle);
total = bundle.getEntry().size(); total = bundle.getEntry().size();
@ -310,7 +315,7 @@ public class ValidationDataUploader extends BaseCommand {
} catch (IOException e) { } catch (IOException e) {
throw new CommandFailureException(e.toString()); throw new CommandFailureException(e.toString());
} }
bundle = ctx.newXmlParser().parseResource(org.hl7.fhir.dstu3.model.Bundle.class, vsContents); bundle = theCtx.newXmlParser().parseResource(org.hl7.fhir.dstu3.model.Bundle.class, vsContents);
filterBundle(bundle); filterBundle(bundle);
total = bundle.getEntry().size(); total = bundle.getEntry().size();
count = 1; count = 1;
@ -329,9 +334,9 @@ public class ValidationDataUploader extends BaseCommand {
ourLog.info("Finished uploading ValueSets"); ourLog.info("Finished uploading ValueSets");
uploadDstu3Profiles(ctx, client, "profiles-resources"); uploadDstu3Profiles(theCtx, client, "profiles-resources");
uploadDstu3Profiles(ctx, client, "profiles-types"); uploadDstu3Profiles(theCtx, client, "profiles-types");
uploadDstu3Profiles(ctx, client, "profiles-others"); uploadDstu3Profiles(theCtx, client, "profiles-others");
ourLog.info("Finished uploading ValueSets"); ourLog.info("Finished uploading ValueSets");
@ -340,9 +345,9 @@ public class ValidationDataUploader extends BaseCommand {
ourLog.info("Finished uploading definitions to server (took {} ms)", delay); ourLog.info("Finished uploading definitions to server (took {} ms)", delay);
} }
private void uploadDefinitionsR4(String theTargetServer, FhirContext theCtx) throws CommandFailureException { private void uploadDefinitionsR4(CommandLine theCommandLine, FhirContext theCtx) throws CommandFailureException {
IGenericClient client = newClient(theCtx, theTargetServer); IGenericClient client = newClient(theCommandLine);
ourLog.info("Uploading definitions to server: " + theTargetServer); ourLog.info("Uploading definitions to server");
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
int total = 0; int total = 0;

View File

@ -1,30 +1,22 @@
package ca.uhn.fhir.cli; package ca.uhn.fhir.cli;
import static org.apache.commons.lang3.StringUtils.isBlank; import ca.uhn.fhir.model.primitive.IdDt;
import java.net.URI;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.*;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.client.WebSocketClient;
import ca.uhn.fhir.model.primitive.IdDt; import java.net.URI;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class WebsocketSubscribeCommand extends BaseCommand { public class WebsocketSubscribeCommand extends BaseCommand {
private static final org.slf4j.Logger LOG_RECV = org.slf4j.LoggerFactory.getLogger("websocket.RECV"); private static final org.slf4j.Logger LOG_RECV = org.slf4j.LoggerFactory.getLogger("websocket.RECV");
private static final org.slf4j.Logger LOG_SEND = org.slf4j.LoggerFactory.getLogger("websocket.SEND"); private static final org.slf4j.Logger LOG_SEND = org.slf4j.LoggerFactory.getLogger("websocket.SEND");
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketSubscribeCommand.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketSubscribeCommand.class);
private boolean myQuit; private boolean myQuit;
@ -38,6 +30,7 @@ public class WebsocketSubscribeCommand extends BaseCommand {
public String getCommandName() { public String getCommandName() {
return "subscription-client"; return "subscription-client";
} }
@Override @Override
public Options getOptions() { public Options getOptions() {
Options options = new Options(); Options options = new Options();
@ -45,15 +38,16 @@ public class WebsocketSubscribeCommand extends BaseCommand {
options.addOption("i", "subscriptionid", true, "Subscription ID, e.g. \"5235\""); options.addOption("i", "subscriptionid", true, "Subscription ID, e.g. \"5235\"");
return options; return options;
} }
@Override @Override
public void run(CommandLine theCommandLine) throws ParseException, Exception { public void run(CommandLine theCommandLine) throws ParseException {
String target = theCommandLine.getOptionValue("t"); String target = theCommandLine.getOptionValue("t");
if (isBlank(target) || (!target.startsWith("ws://") && !target.startsWith("wss://"))) { if (isBlank(target) || (!target.startsWith("ws://") && !target.startsWith("wss://"))) {
throw new ParseException("Target (-t) needs to be in the form \"ws://foo\" or \"wss://foo\""); throw new ParseException("Target (-t) needs to be in the form \"ws://foo\" or \"wss://foo\"");
} }
IdDt subsId = new IdDt(theCommandLine.getOptionValue("i")); IdDt subsId = new IdDt(theCommandLine.getOptionValue("i"));
WebSocketClient client = new WebSocketClient(); WebSocketClient client = new WebSocketClient();
SimpleEchoSocket socket = new SimpleEchoSocket(subsId.getIdPart()); SimpleEchoSocket socket = new SimpleEchoSocket(subsId.getIdPart());
try { try {
@ -68,6 +62,8 @@ public class WebsocketSubscribeCommand extends BaseCommand {
} }
ourLog.info("Shutting down websocket client"); ourLog.info("Shutting down websocket client");
} catch (Exception e) {
throw new CommandFailureException(e);
} finally { } finally {
try { try {
client.stop(); client.stop();
@ -76,7 +72,7 @@ public class WebsocketSubscribeCommand extends BaseCommand {
} }
} }
} }
/** /**
* Basic Echo Client Socket * Basic Echo Client Socket
*/ */
@ -84,7 +80,7 @@ public class WebsocketSubscribeCommand extends BaseCommand {
public class SimpleEchoSocket { public class SimpleEchoSocket {
private String mySubsId; private String mySubsId;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private Session session; private Session session;
@ -93,17 +89,6 @@ public class WebsocketSubscribeCommand extends BaseCommand {
mySubsId = theSubsId; mySubsId = theSubsId;
} }
@OnWebSocketError
public void onError(Throwable theError) {
ourLog.error("Websocket error: ", theError);
myQuit = true;
}
@OnWebSocketFrame
public void onFrame(Frame theFrame) {
ourLog.debug("Websocket frame: {}", theFrame);
}
@OnWebSocketClose @OnWebSocketClose
public void onClose(int statusCode, String reason) { public void onClose(int statusCode, String reason) {
ourLog.info("Received CLOSE status={} reason={}", statusCode, reason); ourLog.info("Received CLOSE status={} reason={}", statusCode, reason);
@ -123,10 +108,21 @@ public class WebsocketSubscribeCommand extends BaseCommand {
} }
} }
@OnWebSocketError
public void onError(Throwable theError) {
ourLog.error("Websocket error: ", theError);
myQuit = true;
}
@OnWebSocketFrame
public void onFrame(Frame theFrame) {
ourLog.debug("Websocket frame: {}", theFrame);
}
@OnWebSocketMessage @OnWebSocketMessage
public void onMessage(String theMsg) { public void onMessage(String theMsg) {
LOG_RECV.info("{}", theMsg); LOG_RECV.info("{}", theMsg);
} }
} }
} }

View File

@ -3,12 +3,15 @@
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<useJansi>true</useJansi> <useJansi>true</useJansi>
<encoder> <encoder>
<pattern>%boldGreen(%d{HH:mm:ss}) %white(%-5level) %logger{36} - %boldWhite(%msg%n) <pattern>%green(%d{yyyy-MM-dd}) %boldGreen(%d{HH:mm:ss}) %white([%thread]) %white(%-5level) %boldBlue(%logger{20}) %boldWhite(%msg%n)
</pattern> </pattern>
</encoder> </encoder>
</appender> </appender>
<logger name="ca.uhn.fhir" additivity="false" level="info"> <logger name="ca.uhn.fhir.cli" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.cdr.cli" additivity="false" level="info">
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />
</logger> </logger>
@ -20,6 +23,15 @@
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />
</logger> </logger>
<!-- These two are used by SynchronizeFhirServersCommand -->
<logger name="sync.SOURCE" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="sync.TARGET" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<root level="warn"> <root level="warn">
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />
</root> </root>

View File

@ -127,6 +127,9 @@ fi
SCRIPTDIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) SCRIPTDIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
COLUMNS=$(tput cols)
exec "$JAVACMD" \ exec "$JAVACMD" \
-Dcolumns=$COLUMNS \
$CLI_OPTS \ $CLI_OPTS \
-jar $SCRIPTDIR/hapi-fhir-cli.jar "$@" -jar $SCRIPTDIR/hapi-fhir-cli.jar "$@"

View File

@ -1,3 +1,5 @@
package ca.uhn.fhir.cli;
import org.junit.Test; import org.junit.Test;
public class InstallIgPackTest { public class InstallIgPackTest {

View File

@ -0,0 +1,15 @@
package ca.uhn.fhir.cli;
import org.junit.Test;
public class OptionsTest {
@Test
public void testOptions() {
App app = new App();
for (BaseCommand next : app.provideCommands()) {
next.getOptions();
}
}
}

View File

@ -1,8 +1,8 @@
package ca.uhn.fhir.cli;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.cli.App;
public class ValidateTest { public class ValidateTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateTest.class);
@ -16,7 +16,7 @@ public class ValidateTest {
String resourcePath = ValidateTest.class.getResource("/patient-uslab-example1.xml").getFile(); String resourcePath = ValidateTest.class.getResource("/patient-uslab-example1.xml").getFile();
ourLog.info(resourcePath); ourLog.info(resourcePath);
App.main(new String[] {"validate", "-p", "-n", resourcePath}); App.main(new String[] {"validate", "-v", "dstu3", "-p", "-n", resourcePath});
} }

View File

@ -593,6 +593,11 @@
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
<version>${commons_codec_version}</version> <version>${commons_codec_version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.14</version>
</dependency>
<dependency> <dependency>
<groupId>commons-io</groupId> <groupId>commons-io</groupId>
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>