diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/ValidationResult.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/ValidationResult.java index 674398f8951..fdef37adfef 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/ValidationResult.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/ValidationResult.java @@ -20,17 +20,16 @@ package ca.uhn.fhir.validation; * #L% */ -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.util.OperationOutcomeUtil; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import java.util.Collections; import java.util.List; -import org.hl7.fhir.instance.model.api.IBase; -import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.util.OperationOutcomeUtil; +import static org.apache.commons.lang3.StringUtils.isNotBlank; /** * Encapsulates the results of validation @@ -39,18 +38,22 @@ import ca.uhn.fhir.util.OperationOutcomeUtil; * @since 0.7 */ public class ValidationResult { + public static final int ERROR_DISPLAY_LIMIT_DEFAULT = 1; + private final FhirContext myCtx; private final boolean myIsSuccessful; private final List myMessages; + private int myErrorDisplayLimit = ERROR_DISPLAY_LIMIT_DEFAULT; + public ValidationResult(FhirContext theCtx, List theMessages) { boolean successful = true; myCtx = theCtx; myMessages = theMessages; for (SingleValidationMessage next : myMessages) { - next.getSeverity(); if (next.getSeverity() == null || next.getSeverity().ordinal() > ResultSeverityEnum.WARNING.ordinal()) { successful = false; + break; } } myIsSuccessful = successful; @@ -72,22 +75,36 @@ public class ValidationResult { return myIsSuccessful; } + private String toDescription() { - StringBuilder b = new StringBuilder(100); - if (myMessages.size() > 0) { - if (myMessages.get(0).getSeverity() != null) { - b.append(myMessages.get(0).getSeverity().name()); + if (myMessages.isEmpty()) { + return "No issues"; + } + + StringBuilder b = new StringBuilder(100 * myMessages.size()); + int shownMsgQty = Math.min(myErrorDisplayLimit, myMessages.size()); + for (int i = 0; i < shownMsgQty; i++) { + SingleValidationMessage nextMsg = myMessages.get(i); + b.append(ourNewLine); + if (i == 0) { + if (shownMsgQty < myMessages.size()) { + b.append("(showing first ").append(shownMsgQty).append(" messages out of ") + .append(myMessages.size()).append(" total)").append(ourNewLine); + } + } + if (nextMsg.getSeverity() != null) { + b.append(nextMsg.getSeverity().name()); b.append(" - "); } - b.append(myMessages.get(0).getMessage()); + b.append(nextMsg.getMessage()); b.append(" - "); - b.append(myMessages.get(0).getLocationString()); - } else { - b.append("No issues"); + b.append(nextMsg.getLocationString()); } + return b.toString(); } + /** * @deprecated Use {@link #toOperationOutcome()} instead since this method returns a view. * {@link #toOperationOutcome()} is identical to this method, but has a more suitable name so this method @@ -156,4 +173,11 @@ public class ValidationResult { public FhirContext getContext() { return myCtx; } + + public int getErrorDisplayLimit() { return myErrorDisplayLimit; } + + public void setErrorDisplayLimit(int theErrorDisplayLimit) { myErrorDisplayLimit = theErrorDisplayLimit; } + + + private static final String ourNewLine = System.getProperty("line.separator"); } diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/validation/ValidationResultTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/validation/ValidationResultTest.java new file mode 100644 index 00000000000..5b9c217aee6 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/validation/ValidationResultTest.java @@ -0,0 +1,61 @@ +package ca.uhn.fhir.validation; + +import ca.uhn.fhir.context.FhirContext; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +class ValidationResultTest { + + private final @Mock FhirContext myFhirContext = mock(FhirContext.class); + + @Test + void testLessThanDefaultDisplayQty() { + List validationMessages = getTestValidationErrors(2); + ValidationResult vr = new ValidationResult(myFhirContext, validationMessages); + String toStringValue = vr.toString(); + assertTrue(toStringValue.contains("Error message #" + 1)); + assertFalse(toStringValue.contains("Error message #" + 2)); + } + + @Test + void testMoreThanDefaultDisplayQty() { + List validationMessages = + getTestValidationErrors(ValidationResult.ERROR_DISPLAY_LIMIT_DEFAULT * 2); + ValidationResult vr = new ValidationResult(myFhirContext, validationMessages); + String toStringValue = vr.toString(); + assertTrue(toStringValue.contains("Error message #" + ValidationResult.ERROR_DISPLAY_LIMIT_DEFAULT)); + assertFalse(toStringValue.contains("Error message #" + (ValidationResult.ERROR_DISPLAY_LIMIT_DEFAULT + 1))); + } + + @Test + void testDisplayLimitSet() { + List validationMessages = getTestValidationErrors(10); + ValidationResult vr = new ValidationResult(myFhirContext, validationMessages); + vr.setErrorDisplayLimit(8); + String toStringValue = vr.toString(); + assertTrue(toStringValue.contains("Error message #" + 8)); + assertFalse(toStringValue.contains("Error message #" + 9)); + } + + private List getTestValidationErrors(int theSize) { + List msgList = new ArrayList<>(); + for (int i = 0; i < theSize; i++) { + SingleValidationMessage validationMsg = new SingleValidationMessage(); + validationMsg.setLocationCol(i); + validationMsg.setLocationLine(1); + validationMsg.setLocationString("Error #" + (i+1)); + validationMsg.setMessage("Error message #" + (i+1)); + validationMsg.setSeverity(ResultSeverityEnum.ERROR); + msgList.add(validationMsg); + } + return msgList; + } + +} diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/AbstractImportExportCsvConceptMapCommand.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/AbstractImportExportCsvConceptMapCommand.java index 52975d907fb..043a9d11cc7 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/AbstractImportExportCsvConceptMapCommand.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/AbstractImportExportCsvConceptMapCommand.java @@ -24,8 +24,8 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import com.google.common.collect.Sets; import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.io.ByteOrderMark; import org.apache.commons.io.input.BOMInputStream; @@ -39,13 +39,14 @@ import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.util.Arrays; +import java.util.Collection; import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isAllBlank; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; -public abstract class AbstractImportExportCsvConceptMapCommand extends BaseCommand { +public abstract class AbstractImportExportCsvConceptMapCommand extends BaseRequestGeneratingCommand { // TODO: Don't use qualified names for loggers in HAPI CLI. private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AbstractImportExportCsvConceptMapCommand.class); @@ -63,14 +64,11 @@ public abstract class AbstractImportExportCsvConceptMapCommand extends BaseComma protected FhirVersionEnum fhirVersion; protected String file; + @Override - protected void addFhirVersionOption(Options theOptions) { - String versions = Arrays.stream(FhirVersionEnum.values()) - .filter(t -> t != FhirVersionEnum.DSTU2_1 && t != FhirVersionEnum.DSTU2_HL7ORG && t != FhirVersionEnum.DSTU2) - .map(t -> t.name().toLowerCase()) - .sorted() - .collect(Collectors.joining(", ")); - addRequiredOption(theOptions, FHIR_VERSION_PARAM, FHIR_VERSION_PARAM_LONGOPT, FHIR_VERSION_PARAM_NAME, FHIR_VERSION_PARAM_DESC + versions); + protected Collection getFilterOutVersions() { + return Sets.newHashSet(FhirVersionEnum.DSTU2_1, + FhirVersionEnum.DSTU2_HL7ORG, FhirVersionEnum.DSTU2); } protected BufferedReader getBufferedReader() throws IOException { @@ -139,7 +137,7 @@ public abstract class AbstractImportExportCsvConceptMapCommand extends BaseComma process(); } - protected void parseAdditionalParameters(CommandLine theCommandLine) throws ParseException {} + protected void parseAdditionalParameters(CommandLine theCommandLine) {} protected abstract void process() throws ParseException, ExecutionException; diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java index 863c80b9218..dd4b652f239 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java @@ -43,6 +43,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.fusesource.jansi.Ansi.ansi; @@ -216,43 +217,14 @@ public abstract class BaseApp { } 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) { - String message = "Unknown command: " + theArgs[1]; - System.err.println(message); - exitDueToProblem(message); - return; - } - logCommandUsage(command); + processHelp(theArgs); return; } - BaseCommand command = null; - for (BaseCommand nextCommand : ourCommands) { - if (nextCommand.getCommandName().equals(theArgs[0])) { - command = nextCommand; - break; - } - } + Optional commandOpt = parseCommand(theArgs); + if (! commandOpt.isPresent()) return; - if (command == null) { - String message = "Unrecognized command: " + ansi().bold().fg(Ansi.Color.RED) + theArgs[0] + ansi().boldOff().fg(Ansi.Color.WHITE); - printMessageToStdout(message); - printMessageToStdout(""); - logUsage(); - exitDueToProblem(message); - return; - } + BaseCommand command = commandOpt.get(); myShutdownHook = new MyShutdownHook(command); Runtime.getRuntime().addShutdownHook(myShutdownHook); @@ -304,11 +276,44 @@ public abstract class BaseApp { } catch (Throwable t) { ourLog.error("Error during execution: ", t); runCleanupHookAndUnregister(); - exitDueToException(new CommandFailureException("Error: " + t.toString(), t)); + exitDueToException(new CommandFailureException("Error: " + t, t)); } } + private Optional parseCommand(String[] theArgs) { + Optional commandOpt = getNextCommand(theArgs); + + if (! commandOpt.isPresent()) { + String message = "Unrecognized command: " + ansi().bold().fg(Ansi.Color.RED) + theArgs[0] + ansi().boldOff().fg(Ansi.Color.WHITE); + printMessageToStdout(message); + printMessageToStdout(""); + logUsage(); + exitDueToProblem(message); + } + return commandOpt; + } + + private Optional getNextCommand(String[] theArgs) { + return ourCommands.stream().filter(cmd -> cmd.getCommandName().equals(theArgs[0])).findFirst(); + } + + private void processHelp(String[] theArgs) { + if (theArgs.length < 2) { + logUsage(); + return; + } + Optional commandOpt = getNextCommand(theArgs); + if (! commandOpt.isPresent()) { + String message = "Unknown command: " + theArgs[1]; + System.err.println(message); + exitDueToProblem(message); + return; + } + logCommandUsage(commandOpt.get()); + } + + private void exitDueToProblem(String theDescription) { if ("true".equals(System.getProperty("test"))) { throw new Error(theDescription); @@ -380,6 +385,7 @@ public abstract class BaseApp { configurator.setContext((LoggerContext) LoggerFactory.getILoggerFactory()); ((LoggerContext) LoggerFactory.getILoggerFactory()).reset(); configurator.doConfigure(App.class.getResourceAsStream("/logback-cli-on.xml")); + ourLog.info("Logging configuration set from file logback-cli-on.xml"); } catch (JoranException e) { e.printStackTrace(); } @@ -391,6 +397,7 @@ public abstract class BaseApp { configurator.setContext((LoggerContext) LoggerFactory.getILoggerFactory()); ((LoggerContext) LoggerFactory.getILoggerFactory()).reset(); configurator.doConfigure(App.class.getResourceAsStream("/logback-cli-on-debug.xml")); + ourLog.info("Logging configuration set from file logback-cli-on-debug.xml"); } catch (JoranException e) { e.printStackTrace(); } diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseCommand.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseCommand.java index 2e511c26433..43b6e97dc41 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseCommand.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseCommand.java @@ -26,13 +26,20 @@ import ca.uhn.fhir.rest.api.Constants; 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.*; +import com.google.common.collect.Sets; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionGroup; +import org.apache.commons.cli.Options; +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.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; @@ -42,13 +49,27 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Base64Utils; -import java.io.*; -import java.util.*; +import java.io.BufferedReader; +import java.io.Console; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import java.util.zip.GZIPInputStream; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.trim; import static org.fusesource.jansi.Ansi.ansi; public abstract class BaseCommand implements Comparable { @@ -109,7 +130,7 @@ public abstract class BaseCommand implements Comparable { try { retVal = reader.readLine(); } catch (IOException e) { - throw new ParseException("Failed to read input from user: " + e.toString()); + throw new ParseException("Failed to read input from user: " + e); } } else { retVal = new String(console.readPassword()); @@ -120,9 +141,13 @@ public abstract class BaseCommand implements Comparable { return retVal; } + protected Collection getFilterOutVersions() { + return Sets.newHashSet(FhirVersionEnum.DSTU2_1, FhirVersionEnum.DSTU2_HL7ORG); + } + protected void addFhirVersionOption(Options theOptions) { String versions = Arrays.stream(FhirVersionEnum.values()) - .filter(t -> t != FhirVersionEnum.DSTU2_1 && t != FhirVersionEnum.DSTU2_HL7ORG) + .filter(t -> ! getFilterOutVersions().contains(t)) .map(t -> t.name().toLowerCase()) .sorted() .collect(Collectors.joining(", ")); @@ -275,12 +300,33 @@ public abstract class BaseCommand implements Comparable { byte[] basicAuth = optionValue.getBytes(); String base64EncodedBasicAuth = Base64Utils.encodeToString(basicAuth); basicAuthHeaderValue = Constants.HEADER_AUTHORIZATION_VALPREFIX_BASIC + base64EncodedBasicAuth; - } else { - basicAuthHeaderValue = null; } return basicAuthHeaderValue; } + + protected Pair parseNameValueParameter( + String separator, String theParamName, String theParam) throws ParseException { + + String errorMsg = "Parameter " + theParamName + " must be in the format: \"name:value\""; + + if (! theParam.contains(separator)) { + throw new ParseException(errorMsg); + } + + String[] nameValue = theParam.split(separator); + if (nameValue.length != 2) { + throw new ParseException(errorMsg); + } + + if (StringUtils.isBlank(nameValue[0]) || StringUtils.isBlank(nameValue[1])) { + throw new ParseException(errorMsg); + } + + return Pair.of(nameValue[0], nameValue[1]); + } + + public T getAndParseOptionEnum(CommandLine theCommandLine, String theOption, Class theEnumClass, boolean theRequired, T theDefault) throws ParseException { String val = theCommandLine.getOptionValue(theOption); if (isBlank(val)) { diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseRequestGeneratingCommand.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseRequestGeneratingCommand.java new file mode 100644 index 00000000000..4cc4b669bf2 --- /dev/null +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseRequestGeneratingCommand.java @@ -0,0 +1,114 @@ +package ca.uhn.fhir.cli; + +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.AdditionalRequestHeadersInterceptor; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public abstract class BaseRequestGeneratingCommand extends BaseCommand { + + public enum BaseRequestGeneratingCommandOptions { + VERSION, + BASE_URL, + BASIC_AUTH, + VERBOSE_LOGGING, + HEADER_PASSTHROUGH + } + + + protected static final String HEADER_PASSTHROUGH = "hp"; + protected static final String HEADER_PASSTHROUGH_NAME = "header"; + protected static final String HEADER_PASSTHROUGH_LONGOPT = "header-passthrough"; + + + @Override + public Options getOptions() { + return getSomeOptions(Collections.emptySet()); + } + + /** + * Allows child classes to obtain a subset of the parent-defined options + */ + protected Options getSomeOptions(Collection theExcludeOptions) { + Options options = new Options(); + + if (! theExcludeOptions.contains(BaseRequestGeneratingCommandOptions.VERSION)) { + addFhirVersionOption(options); + } + + if (! theExcludeOptions.contains(BaseRequestGeneratingCommandOptions.BASE_URL)) { + addBaseUrlOption(options); + } + + if (! theExcludeOptions.contains(BaseRequestGeneratingCommandOptions.BASIC_AUTH)) { + addBasicAuthOption(options); + } + + if (! theExcludeOptions.contains(BaseRequestGeneratingCommandOptions.VERBOSE_LOGGING)) { + addVerboseLoggingOption(options); + } + + if (! theExcludeOptions.contains(BaseRequestGeneratingCommandOptions.HEADER_PASSTHROUGH)) { + addHeaderPassthroughOption(options); + } + + return options; + } + + + @Override + protected IGenericClient newClientWithBaseUrl(CommandLine theCommandLine, String theBaseUrl, + String theBasicAuthOptionName, String theBearerTokenOptionName) throws ParseException { + + IGenericClient client = super.newClientWithBaseUrl( + theCommandLine, theBaseUrl, theBasicAuthOptionName, theBearerTokenOptionName); + registerHeaderPassthrough(theCommandLine, client); + + return client; + } + + + private void registerHeaderPassthrough(CommandLine theCommandLine, IGenericClient theClient) throws ParseException { + if (theCommandLine.hasOption(HEADER_PASSTHROUGH)) { + theClient.registerInterceptor( + new AdditionalRequestHeadersInterceptor( + getAndParseOptionHeadersPassthrough(theCommandLine, HEADER_PASSTHROUGH))); + } + + } + + private void addHeaderPassthroughOption(Options theOptions) { + addOptionalOption(theOptions, HEADER_PASSTHROUGH, HEADER_PASSTHROUGH_LONGOPT, HEADER_PASSTHROUGH_NAME, + "If specified, this argument specifies headers to include in the generated request"); + } + + /** + * @return Returns the optional pass-through header name and value + */ + protected Map> getAndParseOptionHeadersPassthrough( + CommandLine theCommandLine, String theOptionName) throws ParseException { + + if (! theCommandLine.hasOption(theOptionName)) { + return Collections.emptyMap(); + } + + Map> headersMap = new HashMap<>(); + for (String nextOptionValue: theCommandLine.getOptionValues(theOptionName)) { + Pair nextHeader = parseNameValueParameter(":", theOptionName, nextOptionValue); + headersMap.compute(nextHeader.getKey(), (k, v) -> v == null ? new ArrayList<>() : v).add(nextHeader.getValue()); + } + + return headersMap; + } + +} diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java index ba22eacfbda..9926b82e3af 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.cli; import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu2.resource.Bundle; @@ -72,7 +73,7 @@ import java.util.zip.ZipInputStream; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; -public class ExampleDataUploader extends BaseCommand { +public class ExampleDataUploader extends BaseRequestGeneratingCommand { // TODO: Don't use qualified names for loggers in HAPI CLI. private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleDataUploader.class); @@ -200,8 +201,8 @@ public class ExampleDataUploader extends BaseCommand { ourLog.info("Found example {} - {} - {} chars", nextEntry.getName(), parsed.getClass().getSimpleName(), exampleString.length()); ValidationResult result = val.validateWithResult(parsed); - if (result.isSuccessful() == false) { - ourLog.info("FAILED to validate example {} - {}", nextEntry.getName(), result.toString()); + if (! result.isSuccessful()) { + ourLog.info("FAILED to validate example {} - {}", nextEntry.getName(), result); continue; } @@ -284,8 +285,8 @@ public class ExampleDataUploader extends BaseCommand { ourLog.info("Found example {} - {} - {} chars", nextEntry.getName(), parsed.getClass().getSimpleName(), exampleString.length()); ValidationResult result = val.validateWithResult(parsed); - if (result.isSuccessful() == false) { - ourLog.info("FAILED to validate example {} - {}", nextEntry.getName(), result.toString()); + if (! result.isSuccessful()) { + ourLog.info("FAILED to validate example {} - {}", nextEntry.getName(), result); continue; } @@ -334,15 +335,9 @@ public class ExampleDataUploader extends BaseCommand { @Override public Options getOptions() { - Options options = new Options(); + Options options = super.getOptions(); Option opt; - addFhirVersionOption(options); - - opt = new Option("t", "target", true, "Base URL for the target server (e.g. \"http://example.com/fhir\")"); - opt.setRequired(true); - options.addOption(opt); - opt = new Option("l", "limit", true, "Sets a limit to the number of resources the uploader will try to upload"); opt.setRequired(false); options.addOption(opt); @@ -356,11 +351,17 @@ public class ExampleDataUploader extends BaseCommand { opt.setRequired(false); options.addOption(opt); - addBasicAuthOption(options); - return options; } + + @Override + protected Collection getFilterOutVersions() { + Collection filterOutCollection = super.getFilterOutVersions(); + filterOutCollection.add(FhirVersionEnum.R5); + return filterOutCollection; + } + private void processBundle(FhirContext ctx, IBaseBundle bundle) { switch (ctx.getVersion().getVersion()) { case DSTU2: @@ -609,7 +610,7 @@ public class ExampleDataUploader extends BaseCommand { String targetServer = theCommandLine.getOptionValue("t"); if (isBlank(targetServer)) { throw new ParseException("No target server (-t) specified"); - } else if (targetServer.startsWith("http") == false && targetServer.startsWith("file") == false) { + } else if (! targetServer.startsWith("http") && ! targetServer.startsWith("file")) { throw new ParseException("Invalid target server specified, must begin with 'http' or 'file'"); } @@ -697,7 +698,7 @@ public class ExampleDataUploader extends BaseCommand { String nextTarget = nextRefResourceType + "/EX" + nextRefIdPart; nextRef.getResourceReference().setResource(null); nextRef.getResourceReference().setReference(nextTarget); - if (checkedTargets.add(nextTarget) == false) { + if (! checkedTargets.add(nextTarget)) { continue; } diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExportConceptMapToCsvCommand.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExportConceptMapToCsvCommand.java index 63dcef920ab..3a4300d5546 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExportConceptMapToCsvCommand.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExportConceptMapToCsvCommand.java @@ -60,14 +60,10 @@ public class ExportConceptMapToCsvCommand extends AbstractImportExportCsvConcept @Override public Options getOptions() { - Options options = new Options(); + Options options = super.getOptions(); - this.addFhirVersionOption(options); - addBaseUrlOption(options); addRequiredOption(options, CONCEPTMAP_URL_PARAM, CONCEPTMAP_URL_PARAM_LONGOPT, CONCEPTMAP_URL_PARAM_NAME, CONCEPTMAP_URL_PARAM_DESC); addRequiredOption(options, FILE_PARAM, FILE_PARAM_LONGOPT, FILE_PARAM_NAME, FILE_PARAM_DESC); - addBasicAuthOption(options); - addVerboseLoggingOption(options); return options; } diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ImportCsvToConceptMapCommand.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ImportCsvToConceptMapCommand.java index f9c786a0ab6..86166b29baa 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ImportCsvToConceptMapCommand.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ImportCsvToConceptMapCommand.java @@ -80,18 +80,14 @@ public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConcept @Override public Options getOptions() { - Options options = new Options(); + Options options = super.getOptions(); - this.addFhirVersionOption(options); - addBaseUrlOption(options); addRequiredOption(options, CONCEPTMAP_URL_PARAM, CONCEPTMAP_URL_PARAM_LONGOPT, CONCEPTMAP_URL_PARAM_NAME, CONCEPTMAP_URL_PARAM_DESC); // addOptionalOption(options, SOURCE_VALUE_SET_PARAM, SOURCE_VALUE_SET_PARAM_LONGOPT, SOURCE_VALUE_SET_PARAM_NAME, SOURCE_VALUE_SET_PARAM_DESC); addOptionalOption(options, TARGET_VALUE_SET_PARAM, TARGET_VALUE_SET_PARAM_LONGOPT, TARGET_VALUE_SET_PARAM_NAME, TARGET_VALUE_SET_PARAM_DESC); // addRequiredOption(options, FILE_PARAM, FILE_PARAM_LONGOPT, FILE_PARAM_NAME, FILE_PARAM_DESC); - addBasicAuthOption(options); - addVerboseLoggingOption(options); return options; } diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/UploadTerminologyCommand.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/UploadTerminologyCommand.java index 6ffcf4b7c7c..0269f30f617 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/UploadTerminologyCommand.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/UploadTerminologyCommand.java @@ -42,13 +42,18 @@ import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.ICompositeType; import org.hl7.fhir.r4.model.CodeSystem; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import static org.apache.commons.lang3.StringUtils.isBlank; -public class UploadTerminologyCommand extends BaseCommand { +public class UploadTerminologyCommand extends BaseRequestGeneratingCommand { static final String UPLOAD_TERMINOLOGY = "upload-terminology"; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UploadTerminologyCommand.class); private static final long DEFAULT_TRANSFER_SIZE_LIMIT = 10 * FileUtils.ONE_MB; @@ -66,15 +71,11 @@ public class UploadTerminologyCommand extends BaseCommand { @Override public Options getOptions() { - Options options = new Options(); + Options options = super.getOptions(); - addFhirVersionOption(options); - addBaseUrlOption(options); addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + ITermLoaderSvc.SCT_URI + ")"); addOptionalOption(options, "d", "data", true, "Local file to use to upload (can be a raw file or a ZIP containing the raw file)"); addOptionalOption(options, "m", "mode", true, "The upload mode: SNAPSHOT (default), ADD, REMOVE"); - addBasicAuthOption(options); - addVerboseLoggingOption(options); return options; } @@ -101,33 +102,35 @@ public class UploadTerminologyCommand extends BaseCommand { throw new ParseException("No data file provided"); } - IGenericClient client = super.newClient(theCommandLine); - IBaseParameters inputParameters = ParametersUtil.newInstance(myFhirCtx); + IGenericClient client = newClient(theCommandLine); if (theCommandLine.hasOption(VERBOSE_LOGGING_PARAM)) { client.registerInterceptor(new LoggingInterceptor(true)); } + String requestName = null; switch (mode) { case SNAPSHOT: - invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_UPLOAD_EXTERNAL_CODE_SYSTEM); + requestName = JpaConstants.OPERATION_UPLOAD_EXTERNAL_CODE_SYSTEM; break; case ADD: - invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD); + requestName = JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD; break; case REMOVE: - invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE); + requestName = JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE; break; } - + invokeOperation(termUrl, datafile, client, requestName); } - private void invokeOperation(String theTermUrl, String[] theDatafile, IGenericClient theClient, IBaseParameters theInputParameters, String theOperationName) throws ParseException { + private void invokeOperation(String theTermUrl, String[] theDatafile, IGenericClient theClient, String theOperationName) throws ParseException { + IBaseParameters inputParameters = ParametersUtil.newInstance(myFhirCtx); + boolean isDeltaOperation = theOperationName.equals(JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD) || theOperationName.equals(JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE); - ParametersUtil.addParameterToParametersUri(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_SYSTEM, theTermUrl); + ParametersUtil.addParameterToParametersUri(myFhirCtx, inputParameters, TerminologyUploaderProvider.PARAM_SYSTEM, theTermUrl); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, Charsets.UTF_8); @@ -152,7 +155,7 @@ public class UploadTerminologyCommand extends BaseCommand { } CodeSystem resource = encoding.newParser(myFhirCtx).parseResource(CodeSystem.class, contents); - ParametersUtil.addParameterToParameters(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_CODESYSTEM, resource); + ParametersUtil.addParameterToParameters(myFhirCtx, inputParameters, TerminologyUploaderProvider.PARAM_CODESYSTEM, resource); } else { @@ -174,7 +177,7 @@ public class UploadTerminologyCommand extends BaseCommand { ourLog.info("Adding ZIP file: {}", nextDataFile); String fileName = "file:" + nextDataFile; - addFileToRequestBundle(theInputParameters, fileName, IOUtils.toByteArray(fileInputStream)); + addFileToRequestBundle(inputParameters, fileName, IOUtils.toByteArray(fileInputStream)); } else { @@ -194,13 +197,13 @@ public class UploadTerminologyCommand extends BaseCommand { byte[] compressedBytes = byteArrayOutputStream.toByteArray(); ourLog.info("Compressed {} bytes in {} file(s) into {} bytes", FileUtil.formatFileSize(compressedSourceBytesCount), compressedFileCount, FileUtil.formatFileSize(compressedBytes.length)); - addFileToRequestBundle(theInputParameters, "file:/files.zip", compressedBytes); + addFileToRequestBundle(inputParameters, "file:/files.zip", compressedBytes); } ourLog.info("Beginning upload - This may take a while..."); if (ourLog.isDebugEnabled() || "true".equals(System.getProperty("test"))) { - ourLog.info("Submitting parameters: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theInputParameters)); + ourLog.info("Submitting parameters: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(inputParameters)); } IBaseParameters response; @@ -209,7 +212,7 @@ public class UploadTerminologyCommand extends BaseCommand { .operation() .onType(myFhirCtx.getResourceDefinition("CodeSystem").getImplementingClass()) .named(theOperationName) - .withParameters(theInputParameters) + .withParameters(inputParameters) .execute(); } catch (BaseServerResponseException e) { if (e.getOperationOutcome() != null) { diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/BaseRequestGeneratingCommandTest.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/BaseRequestGeneratingCommandTest.java new file mode 100644 index 00000000000..6c023c82fa7 --- /dev/null +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/BaseRequestGeneratingCommandTest.java @@ -0,0 +1,117 @@ +package ca.uhn.fhir.cli; + +import com.google.common.collect.Lists; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Options; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BaseRequestGeneratingCommandTest { + + private final BaseRequestGeneratingCommand tested = new BaseRequestGeneratingCommandChild(); + + private final List allOptions = + Arrays.asList(BaseRequestGeneratingCommand.BaseRequestGeneratingCommandOptions.values()); + + @Test + void getOptions() { + Options options = tested.getOptions(); + assertEquals(6, options.getOptions().size()); + assertTrue(options.hasShortOption(BaseCommand.FHIR_VERSION_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BASE_URL_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BASIC_AUTH_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BEARER_TOKEN_PARAM_NAME)); + assertTrue(options.hasShortOption(BaseCommand.VERBOSE_LOGGING_PARAM)); + assertTrue(options.hasShortOption(BaseRequestGeneratingCommand.HEADER_PASSTHROUGH)); + } + + @Test + void getSomeOptionsNoVersion() { + Options options = tested.getSomeOptions( + Collections.singleton(BaseRequestGeneratingCommand.BaseRequestGeneratingCommandOptions.VERSION)); + assertEquals(5, options.getOptions().size()); + assertTrue(options.hasShortOption(BaseCommand.BASE_URL_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BASIC_AUTH_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BEARER_TOKEN_PARAM_NAME)); + assertTrue(options.hasShortOption(BaseCommand.VERBOSE_LOGGING_PARAM)); + assertTrue(options.hasShortOption(BaseRequestGeneratingCommand.HEADER_PASSTHROUGH)); + } + + @Test + void getSomeOptionsNoBaseUrl() { + Options options = tested.getSomeOptions( + Collections.singleton(BaseRequestGeneratingCommand.BaseRequestGeneratingCommandOptions.BASE_URL)); + assertEquals(5, options.getOptions().size()); + assertTrue(options.hasShortOption(BaseCommand.FHIR_VERSION_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BASIC_AUTH_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BEARER_TOKEN_PARAM_NAME)); + assertTrue(options.hasShortOption(BaseCommand.VERBOSE_LOGGING_PARAM)); + assertTrue(options.hasShortOption(BaseRequestGeneratingCommand.HEADER_PASSTHROUGH)); + } + + @Test + void getSomeOptionsNoBasicAuth() { + Options options = tested.getSomeOptions( + Collections.singleton(BaseRequestGeneratingCommand.BaseRequestGeneratingCommandOptions.BASIC_AUTH)); + assertEquals(4, options.getOptions().size()); + assertTrue(options.hasShortOption(BaseCommand.FHIR_VERSION_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BASE_URL_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.VERBOSE_LOGGING_PARAM)); + assertTrue(options.hasShortOption(BaseRequestGeneratingCommand.HEADER_PASSTHROUGH)); + } + + @Test + void getSomeOptionsNoVerboseLogging() { + Options options = tested.getSomeOptions( + Collections.singleton(BaseRequestGeneratingCommand.BaseRequestGeneratingCommandOptions.VERBOSE_LOGGING)); + assertEquals(5, options.getOptions().size()); + assertTrue(options.hasShortOption(BaseCommand.FHIR_VERSION_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BASE_URL_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BASIC_AUTH_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BEARER_TOKEN_PARAM_NAME)); + assertTrue(options.hasShortOption(BaseRequestGeneratingCommand.HEADER_PASSTHROUGH)); + } + + @Test + void getSomeOptionsNoHeaderPassthrough() { + Options options = tested.getSomeOptions( + Collections.singleton(BaseRequestGeneratingCommand.BaseRequestGeneratingCommandOptions.HEADER_PASSTHROUGH)); + assertEquals(5, options.getOptions().size()); + assertTrue(options.hasShortOption(BaseCommand.FHIR_VERSION_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BASE_URL_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BASIC_AUTH_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BEARER_TOKEN_PARAM_NAME)); + assertTrue(options.hasShortOption(BaseCommand.VERBOSE_LOGGING_PARAM)); + } + + @Test + void getSomeOptionsExcludeTwo() { + Options options = tested.getSomeOptions(Lists.newArrayList( + BaseRequestGeneratingCommand.BaseRequestGeneratingCommandOptions.VERSION, + BaseRequestGeneratingCommand.BaseRequestGeneratingCommandOptions.HEADER_PASSTHROUGH)); + assertEquals(4, options.getOptions().size()); + assertTrue(options.hasShortOption(BaseCommand.BASE_URL_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BASIC_AUTH_PARAM)); + assertTrue(options.hasShortOption(BaseCommand.BEARER_TOKEN_PARAM_NAME)); + assertTrue(options.hasShortOption(BaseCommand.VERBOSE_LOGGING_PARAM)); + } + + + private static class BaseRequestGeneratingCommandChild extends BaseRequestGeneratingCommand { + + @Override + public String getCommandDescription() { return null; } + + @Override + public String getCommandName() { return null; } + + @Override + public void run(CommandLine theCommandLine) { } + } +} diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/ExampleDataUploaderTest.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/ExampleDataUploaderTest.java new file mode 100644 index 00000000000..8b8fa7578f4 --- /dev/null +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/ExampleDataUploaderTest.java @@ -0,0 +1,86 @@ +package ca.uhn.fhir.cli; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor; +import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.ParseException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.io.File; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ExampleDataUploaderTest { + + private FhirContext myCtx = FhirContext.forR4(); + + @RegisterExtension + public final RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(myCtx); + + private final CapturingInterceptor myCapturingInterceptor = new CapturingInterceptor(); + private final ExampleDataUploader testedCommand = new RequestCapturingExampleDataUploader(myCapturingInterceptor); + + private String inputFilePath; + + @BeforeEach + public void before() { + String resourcesPath = new File("src/test/resources").getAbsolutePath(); + inputFilePath = resourcesPath + "/sample.json.zip"; + } + + + + @Test + public void testHeaderPassthrough() throws ParseException { + String headerKey = "test-header-key"; + String headerValue = "test header value"; + + String[] args = new String[] { + "-v", "r4", // BaseRequestGeneratingCommandTest required + "-t", "http://localhost:8000", // BaseRequestGeneratingCommandTest required + "-d", inputFilePath, + "-hp", headerKey + ":" + headerValue // optional + }; + + final CommandLine commandLine = new DefaultParser().parse(testedCommand.getOptions(), args, true); + testedCommand.run(commandLine); + + assertNotNull(myCapturingInterceptor.getLastRequest()); + Map> allHeaders = myCapturingInterceptor.getLastRequest().getAllHeaders(); + assertFalse(allHeaders.isEmpty()); + + assertTrue(allHeaders.containsKey(headerKey)); + assertEquals(1, allHeaders.get(headerKey).size()); + + assertThat(allHeaders.get(headerKey), hasItems(headerValue)); + } + + + private static class RequestCapturingExampleDataUploader extends ExampleDataUploader { + private final CapturingInterceptor myCapturingInterceptor; + + public RequestCapturingExampleDataUploader(CapturingInterceptor theCapturingInterceptor) { + myCapturingInterceptor = theCapturingInterceptor; + } + + @Override + protected IGenericClient newClient(CommandLine theCommandLine) throws ParseException { + IGenericClient client = super.newClient(theCommandLine); + client.getInterceptorService().registerInterceptor(myCapturingInterceptor); + return client; + } + } + +} diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/UploadTerminologyCommandTest.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/UploadTerminologyCommandTest.java index 0e7288f36ea..e006d1317c6 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/UploadTerminologyCommandTest.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/UploadTerminologyCommandTest.java @@ -1,13 +1,19 @@ package ca.uhn.fhir.cli; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.test.BaseTest; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.term.UploadStatistics; import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor; import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.test.BaseTest; import ca.uhn.fhir.test.utilities.JettyUtil; +import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; import com.google.common.base.Charsets; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.ParseException; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.eclipse.jetty.server.Server; @@ -19,26 +25,42 @@ import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Patient; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; import java.util.List; +import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.matchesPattern; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.anyList; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class UploadTerminologyCommandTest extends BaseTest { @@ -323,6 +345,139 @@ public class UploadTerminologyCommandTest extends BaseTest { assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100)); } + @Nested + public class HeaderPassthroughOptionTests { + + @RegisterExtension + public final RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(myCtx); + + private final String headerKey1 = "test-header-key-1"; + private final String headerValue1 = "test header value-1"; + + private final CapturingInterceptor myCapturingInterceptor = new CapturingInterceptor(); + private final UploadTerminologyCommand testedCommand = + new RequestCapturingUploadTerminologyCommand(myCapturingInterceptor); + + @BeforeEach + public void before() { + when(myTermLoaderSvc.loadCustom(eq("http://foo"), anyList(), any())) + .thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101"))); + + TerminologyUploaderProvider provider = new TerminologyUploaderProvider(myCtx, myTermLoaderSvc); + myRestfulServerExtension.registerProvider(provider); + } + + + @Test + public void oneHeader() throws Exception { + String[] args = new String[] { + "-v", "r4", + "-m", "SNAPSHOT", + "-t", "http://localhost:" + myRestfulServerExtension.getPort(), + "-u", "http://foo", + "-d", myConceptsFileName, + "-d", myHierarchyFileName, + "-hp", "\"" + headerKey1 + ":" + headerValue1 + "\"" + }; + + writeConceptAndHierarchyFiles(); + final CommandLine commandLine = new DefaultParser().parse(testedCommand.getOptions(), args, true); + testedCommand.run(commandLine); + + assertNotNull(myCapturingInterceptor.getLastRequest()); + Map> allHeaders = myCapturingInterceptor.getLastRequest().getAllHeaders(); + assertFalse(allHeaders.isEmpty()); + + assertTrue(allHeaders.containsKey(headerKey1)); + assertEquals(1, allHeaders.get(headerKey1).size()); + + assertThat(allHeaders.get(headerKey1), hasItems(headerValue1)); + } + + + @Test + public void twoHeadersSameKey() throws Exception { + final String headerValue2 = "test header value-2"; + + String[] args = new String[] { + "-v", "r4", + "-m", "SNAPSHOT", + "-t", "http://localhost:" + myRestfulServerExtension.getPort(), + "-u", "http://foo", + "-d", myConceptsFileName, + "-d", myHierarchyFileName, + "-hp", "\"" + headerKey1 + ":" + headerValue1 + "\"", + "-hp", "\"" + headerKey1 + ":" + headerValue2 + "\"" + }; + + writeConceptAndHierarchyFiles(); + final CommandLine commandLine = new DefaultParser().parse(testedCommand.getOptions(), args, true); + testedCommand.run(commandLine); + + assertNotNull(myCapturingInterceptor.getLastRequest()); + Map> allHeaders = myCapturingInterceptor.getLastRequest().getAllHeaders(); + assertFalse(allHeaders.isEmpty()); + assertEquals(2, allHeaders.get(headerKey1).size()); + + assertTrue(allHeaders.containsKey(headerKey1)); + assertEquals(2, allHeaders.get(headerKey1).size()); + + assertEquals(headerValue1, allHeaders.get(headerKey1).get(0)); + assertEquals(headerValue2, allHeaders.get(headerKey1).get(1)); + } + + @Test + public void twoHeadersDifferentKeys() throws Exception { + final String headerKey2 = "test-header-key-2"; + final String headerValue2 = "test header value-2"; + + String[] args = new String[] { + "-v", "r4", + "-m", "SNAPSHOT", + "-t", "http://localhost:" + myRestfulServerExtension.getPort(), + "-u", "http://foo", + "-d", myConceptsFileName, + "-d", myHierarchyFileName, + "-hp", "\"" + headerKey1 + ":" + headerValue1 + "\"", + "-hp", "\"" + headerKey2 + ":" + headerValue2 + "\"" + }; + + writeConceptAndHierarchyFiles(); + final CommandLine commandLine = new DefaultParser().parse(testedCommand.getOptions(), args, true); + testedCommand.run(commandLine); + + assertNotNull(myCapturingInterceptor.getLastRequest()); + Map> allHeaders = myCapturingInterceptor.getLastRequest().getAllHeaders(); + assertFalse(allHeaders.isEmpty()); + + assertTrue(allHeaders.containsKey(headerKey1)); + assertEquals(1, allHeaders.get(headerKey1).size()); + assertThat(allHeaders.get(headerKey1), hasItems(headerValue1)); + + assertTrue(allHeaders.containsKey(headerKey2)); + assertEquals(1, allHeaders.get(headerKey2).size()); + assertThat(allHeaders.get(headerKey2), hasItems(headerValue2)); + } + + + private class RequestCapturingUploadTerminologyCommand extends UploadTerminologyCommand { + private CapturingInterceptor myCapturingInterceptor; + + public RequestCapturingUploadTerminologyCommand(CapturingInterceptor theCapturingInterceptor) { + myCapturingInterceptor = theCapturingInterceptor; + } + + @Override + protected IGenericClient newClient(CommandLine theCommandLine) throws ParseException { + IGenericClient client = super.newClient(theCommandLine); + client.getInterceptorService().registerInterceptor(myCapturingInterceptor); + return client; + } + } + } + + + private void writeArchiveFile(File... theFiles) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, Charsets.UTF_8); diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/resources/sample.json.zip b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/resources/sample.json.zip new file mode 100644 index 00000000000..6579a4bf3a9 Binary files /dev/null and b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/resources/sample.json.zip differ