927 cli tooling is needed for creating and maintaining conceptmaps using csvs (#934)

CLI tooling for import/export of ConceptMaps using CSVs has been implemented.
This commit is contained in:
Diederik Muylwyk 2018-05-10 11:12:45 -04:00 committed by GitHub
parent d97fb8f5cf
commit 14a070a47e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1971 additions and 61 deletions

View File

@ -0,0 +1,369 @@
package ca.uhn.fhir.cli;
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 org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.*;
public abstract class AbstractImportExportCsvConceptMapCommand extends BaseCommand {
// TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AbstractImportExportCsvConceptMapCommand.class);
protected static final String CONCEPTMAP_URL_PARAM = "u";
protected static final String CONCEPTMAP_URL_PARAM_LONGOPT = "url";
protected static final String CONCEPTMAP_URL_PARAM_NAME = "url";
protected static final String CONCEPTMAP_URL_PARAM_DESC = "The URL of the ConceptMap resource to be imported/exported (i.e. ConceptMap.url).";
protected static final String FILE_PARAM = "f";
protected static final String FILE_PARAM_LONGOPT = "filename";
protected static final String FILE_PARAM_NAME = "filename";
protected static final String FILE_PARAM_DESC = "The path and filename of the CSV file to be imported/exported (e.g. ./input.csv, ./output.csv, etc.).";
protected IGenericClient client;
protected String conceptMapUrl;
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);
}
@Override
public void run(CommandLine theCommandLine) throws ParseException, ExecutionException {
parseFhirContext(theCommandLine);
FhirContext ctx = getFhirContext();
String targetServer = theCommandLine.getOptionValue(BASE_URL_PARAM);
if (isBlank(targetServer)) {
throw new ParseException("No target server (-" + BASE_URL_PARAM + ") specified.");
} else if (!targetServer.startsWith("http") && !targetServer.startsWith("file")) {
throw new ParseException("Invalid target server specified, must begin with 'http' or 'file'.");
}
conceptMapUrl = theCommandLine.getOptionValue(CONCEPTMAP_URL_PARAM);
if (isBlank(conceptMapUrl)) {
throw new ParseException("No ConceptMap URL (" + CONCEPTMAP_URL_PARAM + ") specified.");
} else {
ourLog.info("Specified ConceptMap URL (ConceptMap.url): {}", conceptMapUrl);
}
file = theCommandLine.getOptionValue(FILE_PARAM);
if (isBlank(file)) {
throw new ParseException("No file (" + FILE_PARAM + ") specified.");
}
if (!file.endsWith(".csv")) {
file = file.concat(".csv");
}
parseAdditionalParameters(theCommandLine);
client = super.newClient(theCommandLine);
fhirVersion = ctx.getVersion().getVersion();
if (fhirVersion != FhirVersionEnum.DSTU3
&& fhirVersion != FhirVersionEnum.R4) {
throw new ParseException("This command does not support FHIR version " + fhirVersion + ".");
}
if (theCommandLine.hasOption(VERBOSE_LOGGING_PARAM)) {
client.registerInterceptor(new LoggingInterceptor(true));
}
process();
}
protected void parseAdditionalParameters(CommandLine theCommandLine) throws ParseException {}
protected abstract void process() throws ParseException, ExecutionException;
protected enum Header {
SOURCE_CODE_SYSTEM,
SOURCE_CODE_SYSTEM_VERSION,
TARGET_CODE_SYSTEM,
TARGET_CODE_SYSTEM_VERSION,
SOURCE_CODE,
SOURCE_DISPLAY,
TARGET_CODE,
TARGET_DISPLAY,
EQUIVALENCE,
COMMENT
}
protected class TemporaryConceptMapGroup {
private String source;
private String sourceVersion;
private String target;
private String targetVersion;
public TemporaryConceptMapGroup() {
}
public TemporaryConceptMapGroup(String theSource, String theSourceVersion, String theTarget, String theTargetVersion) {
this.source = theSource;
this.sourceVersion = theSourceVersion;
this.target = theTarget;
this.targetVersion = theTargetVersion;
}
public boolean hasSource() {
return isNotBlank(source);
}
public String getSource() {
return source;
}
public TemporaryConceptMapGroup setSource(String theSource) {
this.source = theSource;
return this;
}
public boolean hasSourceVersion() {
return isNotBlank(sourceVersion);
}
public String getSourceVersion() {
return sourceVersion;
}
public TemporaryConceptMapGroup setSourceVersion(String theSourceVersion) {
this.sourceVersion = theSourceVersion;
return this;
}
public boolean hasTarget() {
return isNotBlank(target);
}
public String getTarget() {
return target;
}
public TemporaryConceptMapGroup setTarget(String theTarget) {
this.target = theTarget;
return this;
}
public boolean hasTargetVersion() {
return isNotBlank(targetVersion);
}
public String getTargetVersion() {
return targetVersion;
}
public TemporaryConceptMapGroup setTargetVersion(String theTargetVersion) {
this.targetVersion = theTargetVersion;
return this;
}
public boolean hasValues() {
return !isAllBlank(getSource(), getSourceVersion(), getTarget(), getTargetVersion());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TemporaryConceptMapGroup)) return false;
TemporaryConceptMapGroup that = (TemporaryConceptMapGroup) o;
return new EqualsBuilder()
.append(getSource(), that.getSource())
.append(getSourceVersion(), that.getSourceVersion())
.append(getTarget(), that.getTarget())
.append(getTargetVersion(), that.getTargetVersion())
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(getSource())
.append(getSourceVersion())
.append(getTarget())
.append(getTargetVersion())
.toHashCode();
}
}
protected class TemporaryConceptMapGroupElement {
private String code;
private String display;
public TemporaryConceptMapGroupElement() {
}
public TemporaryConceptMapGroupElement(String theCode, String theDisplay) {
this.code = theCode;
this.display = theDisplay;
}
public boolean hasCode() {
return isNotBlank(code);
}
public String getCode() {
return code;
}
public TemporaryConceptMapGroupElement setCode(String theCode) {
this.code = theCode;
return this;
}
public boolean hasDisplay() {
return isNotBlank(display);
}
public String getDisplay() {
return display;
}
public TemporaryConceptMapGroupElement setDisplay(String theDisplay) {
this.display = theDisplay;
return this;
}
public boolean hasValues() {
return !isAllBlank(getCode(), getDisplay());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TemporaryConceptMapGroupElement)) return false;
TemporaryConceptMapGroupElement that = (TemporaryConceptMapGroupElement) o;
return new EqualsBuilder()
.append(getCode(), that.getCode())
.append(getDisplay(), that.getDisplay())
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(getCode())
.append(getDisplay())
.toHashCode();
}
}
protected class TemporaryConceptMapGroupElementTarget {
private String code;
private String display;
private String equivalence;
private String comment;
public TemporaryConceptMapGroupElementTarget() {
}
public TemporaryConceptMapGroupElementTarget(String theCode, String theDisplay, String theEquivalence, String theComment) {
this.code = theCode;
this.display = theDisplay;
this.equivalence = theEquivalence;
this.comment = theComment;
}
public boolean hasCode() {
return isNotBlank(code);
}
public String getCode() {
return code;
}
public TemporaryConceptMapGroupElementTarget setCode(String theCode) {
this.code = theCode;
return this;
}
public boolean hasDisplay() {
return isNotBlank(display);
}
public String getDisplay() {
return display;
}
public TemporaryConceptMapGroupElementTarget setDisplay(String theDisplay) {
this.display = theDisplay;
return this;
}
public boolean hasEquivalence() {
return isNotBlank(equivalence);
}
public String getEquivalence() {
return equivalence;
}
public TemporaryConceptMapGroupElementTarget setEquivalence(String theEquivalence) {
this.equivalence = theEquivalence;
return this;
}
public boolean hasComment() {
return isNotBlank(comment);
}
public String getComment() {
return comment;
}
public TemporaryConceptMapGroupElementTarget setComment(String theComment) {
this.comment = theComment;
return this;
}
public boolean hasValues() {
return !isAllBlank(getCode(), getDisplay(), getEquivalence(), getComment());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TemporaryConceptMapGroupElementTarget)) return false;
TemporaryConceptMapGroupElementTarget that = (TemporaryConceptMapGroupElementTarget) o;
return new EqualsBuilder()
.append(getCode(), that.getCode())
.append(getDisplay(), that.getDisplay())
.append(getEquivalence(), that.getEquivalence())
.append(getComment(), that.getComment())
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(getCode())
.append(getDisplay())
.append(getEquivalence())
.append(getComment())
.toHashCode();
}
}
}

View File

@ -132,6 +132,8 @@ public abstract class BaseApp {
commands.add(new WebsocketSubscribeCommand()); commands.add(new WebsocketSubscribeCommand());
commands.add(new UploadTerminologyCommand()); commands.add(new UploadTerminologyCommand());
commands.add(new IgPackUploader()); commands.add(new IgPackUploader());
commands.add(new ExportConceptMapToCsvCommand());
commands.add(new ImportCsvToConceptMapCommand());
return commands; return commands;
} }

View File

@ -57,21 +57,42 @@ import java.util.zip.GZIPInputStream;
import static org.apache.commons.lang3.StringUtils.*; 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"; // TODO: Don't use qualified names for loggers in HAPI CLI.
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;
protected static final String BASE_URL_PARAM = "t";
protected static final String BASE_URL_PARAM_LONGOPT = "target";
protected static final String BASE_URL_PARAM_NAME = "target";
protected static final String BASE_URL_PARAM_DESC = "Base URL for the target server (e.g. \"http://example.com/fhir\").";
protected static final String BASIC_AUTH_PARAM = "b";
protected static final String BASIC_AUTH_PARAM_LONGOPT = "basic-auth";
protected static final String BASIC_AUTH_PARAM_NAME = "basic-auth";
protected static final String BASIC_AUTH_PARAM_DESC = "If specified, this parameter supplies a username and password (in the format \"username:password\") to include in an HTTP Basic Auth header.";
protected static final String BEARER_TOKEN_PARAM_LONGOPT = "bearer-token";
protected static final String BEARER_TOKEN_PARAM_NAME = "bearer-token";
protected static final String BEARER_TOKEN_PARAM_DESC = "If specified, this parameter supplies a Bearer Token to supply with the request.";
protected static final String FHIR_VERSION_PARAM = "v";
protected static final String FHIR_VERSION_PARAM_LONGOPT = "fhir-version";
protected static final String FHIR_VERSION_PARAM_NAME = "version";
protected static final String FHIR_VERSION_PARAM_DESC = "The FHIR version being used. Valid values: ";
protected static final String VERBOSE_LOGGING_PARAM = "l";
protected static final String VERBOSE_LOGGING_PARAM_LONGOPT = "logging";
protected static final String VERBOSE_LOGGING_PARAM_DESC = "If specified, verbose logging will be used.";
protected FhirContext myFhirCtx;
public BaseCommand() { public BaseCommand() {
super(); super();
} }
protected void addBaseUrlOption(Options theOptions) {
addRequiredOption(theOptions, BASE_URL_PARAM, BASE_URL_PARAM_LONGOPT, BASE_URL_PARAM_NAME, BASE_URL_PARAM_DESC);
}
protected void addBasicAuthOption(Options theOptions) { 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, BASIC_AUTH_PARAM, BASIC_AUTH_PARAM_LONGOPT, BASIC_AUTH_PARAM_NAME, BASIC_AUTH_PARAM_DESC);
addOptionalOption(theOptions, null, BEARER_TOKEN_LONGOPT, true, "If specified, this parameter supplies a Bearer Token to supply with the request"); addOptionalOption(theOptions, null, BEARER_TOKEN_PARAM_LONGOPT, BEARER_TOKEN_PARAM_NAME, BEARER_TOKEN_PARAM_DESC);
} }
protected void addFhirVersionOption(Options theOptions) { protected void addFhirVersionOption(Options theOptions) {
@ -80,7 +101,11 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
.map(t -> t.name().toLowerCase()) .map(t -> t.name().toLowerCase())
.sorted() .sorted()
.collect(Collectors.joining(", ")); .collect(Collectors.joining(", "));
addRequiredOption(theOptions, FHIR_VERSION_OPTION, "fhir-version", "version", "The FHIR version being used. Valid values: " + versions); addRequiredOption(theOptions, FHIR_VERSION_PARAM, FHIR_VERSION_PARAM_LONGOPT, FHIR_VERSION_PARAM_NAME, FHIR_VERSION_PARAM_DESC + versions);
}
protected void addVerboseLoggingOption(Options theOptions) {
addOptionalOption(theOptions, VERBOSE_LOGGING_PARAM, VERBOSE_LOGGING_PARAM_LONGOPT, false, VERBOSE_LOGGING_PARAM_DESC);
} }
private void addOption(Options theOptions, boolean theRequired, String theOpt, String theLong, boolean theHasArgument, String theArgumentName, String theDescription) { private void addOption(Options theOptions, boolean theRequired, String theOpt, String theLong, boolean theHasArgument, String theArgumentName, String theDescription) {
@ -117,9 +142,7 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
} }
protected void addRequiredOption(Options theOptions, String theOpt, String theLong, String theArgumentName, String theDescription) { protected void addRequiredOption(Options theOptions, String theOpt, String theLong, String theArgumentName, String theDescription) {
boolean hasArgument = isNotBlank(theArgumentName); addOption(theOptions, true, theOpt, theLong, isNotBlank(theArgumentName), theArgumentName, theDescription);
boolean required = true;
addOption(theOptions, required, theOpt, theLong, hasArgument, theArgumentName, theDescription);
} }
@Override @Override
@ -182,7 +205,7 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
* @return Returns the complete authorization header value using the "-b" option * @return Returns the complete authorization header value using the "-b" option
*/ */
protected String getAndParseOptionBasicAuthHeader(CommandLine theCommandLine) { protected String getAndParseOptionBasicAuthHeader(CommandLine theCommandLine) {
return getAndParseOptionBasicAuthHeader(theCommandLine, BASIC_AUTH_OPTION); return getAndParseOptionBasicAuthHeader(theCommandLine, BASIC_AUTH_PARAM);
} }
/** /**
@ -307,12 +330,17 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
return inputFiles; return inputFiles;
} }
protected IGenericClient newClient(CommandLine theCommandLine) { protected IGenericClient newClient(CommandLine theCommandLine) throws ParseException {
return newClient(theCommandLine, BASE_URL_PARAM, BASIC_AUTH_OPTION, BEARER_TOKEN_LONGOPT); return newClient(theCommandLine, BASE_URL_PARAM, BASIC_AUTH_PARAM, BEARER_TOKEN_PARAM_LONGOPT);
} }
protected IGenericClient newClient(CommandLine theCommandLine, String theBaseUrlParamName, String theBasicAuthOptionName, String theBearerTokenOptionName) { protected IGenericClient newClient(CommandLine theCommandLine, String theBaseUrlParamName, String theBasicAuthOptionName, String theBearerTokenOptionName) throws ParseException {
String baseUrl = theCommandLine.getOptionValue(theBaseUrlParamName); String baseUrl = theCommandLine.getOptionValue(theBaseUrlParamName);
if (isBlank(baseUrl)) {
throw new ParseException("No target server (-" + BASE_URL_PARAM + ") specified.");
} else if (!baseUrl.startsWith("http") && !baseUrl.startsWith("file")) {
throw new ParseException("Invalid target server specified, must begin with 'http' or 'file'.");
}
myFhirCtx.getRestfulClientFactory().setSocketTimeout(10 * 60 * 1000); myFhirCtx.getRestfulClientFactory().setSocketTimeout(10 * 60 * 1000);
IGenericClient retVal = myFhirCtx.newRestfulGenericClient(baseUrl); IGenericClient retVal = myFhirCtx.newRestfulGenericClient(baseUrl);
@ -333,9 +361,9 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
} }
protected void parseFhirContext(CommandLine theCommandLine) throws ParseException { protected void parseFhirContext(CommandLine theCommandLine) throws ParseException {
String version = theCommandLine.getOptionValue(FHIR_VERSION_OPTION); String version = theCommandLine.getOptionValue(FHIR_VERSION_PARAM);
if (isBlank(version)) { if (isBlank(version)) {
throw new ParseException("Missing required option: -" + FHIR_VERSION_OPTION); throw new ParseException("Missing required option: -" + FHIR_VERSION_PARAM);
} }
try { try {
@ -348,5 +376,4 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
public abstract void run(CommandLine theCommandLine) throws ParseException, ExecutionException; public abstract void run(CommandLine theCommandLine) throws ParseException, ExecutionException;
} }

View File

@ -63,7 +63,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class ExampleDataUploader extends BaseCommand { public class ExampleDataUploader extends BaseCommand {
// TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleDataUploader.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleDataUploader.class);
private IBaseBundle getBundleFromFile(Integer theLimit, File theSuppliedFile, FhirContext theCtx) throws ParseException, IOException { private IBaseBundle getBundleFromFile(Integer theLimit, File theSuppliedFile, FhirContext theCtx) throws ParseException, IOException {

View File

@ -0,0 +1,196 @@
package ca.uhn.fhir.cli;
/*-
* #%L
* HAPI FHIR - Command Line Client - API
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent;
import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.defaultString;
public class ExportConceptMapToCsvCommand extends AbstractImportExportCsvConceptMapCommand {
// TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommand.class);
@Override
public String getCommandDescription() {
return "Exports a specific ConceptMap resource to a CSV file.";
}
@Override
public String getCommandName() {
return "export-conceptmap-to-csv";
}
@Override
public Options getOptions() {
Options options = new Options();
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;
}
@Override
protected void process() throws ParseException {
searchForConceptMapByUrl();
}
private void searchForConceptMapByUrl() {
ourLog.info("Searching for ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
if (fhirVersion == FhirVersionEnum.DSTU3) {
org.hl7.fhir.dstu3.model.Bundle response = client
.search()
.forResource(org.hl7.fhir.dstu3.model.ConceptMap.class)
.where(org.hl7.fhir.dstu3.model.ConceptMap.URL.matches().value(conceptMapUrl))
.returnBundle(org.hl7.fhir.dstu3.model.Bundle.class)
.execute();
if (response.hasEntry()) {
ourLog.info("Found ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
org.hl7.fhir.dstu3.model.ConceptMap conceptMap = (org.hl7.fhir.dstu3.model.ConceptMap) response.getEntryFirstRep().getResource();
convertConceptMapToCsv(conceptMap);
} else {
ourLog.info("No ConceptMap exists with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
}
} else if (fhirVersion == FhirVersionEnum.R4) {
Bundle response = client
.search()
.forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value(conceptMapUrl))
.returnBundle(Bundle.class)
.execute();
if (response.hasEntry()) {
ourLog.info("Found ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
ConceptMap conceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
convertConceptMapToCsv(conceptMap);
} else {
ourLog.info("No ConceptMap exists with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
}
}
}
private void convertConceptMapToCsv(org.hl7.fhir.dstu3.model.ConceptMap theConceptMap) {
ourLog.info("Exporting ConceptMap to CSV...");
BufferedWriter bufferedWriter = null;
CSVPrinter csvPrinter = null;
try {
bufferedWriter = Files.newBufferedWriter(Paths.get(file));
csvPrinter = new CSVPrinter(
bufferedWriter,
CSVFormat
.DEFAULT
.withRecordSeparator("\n")
.withHeader(Header.class));
for (org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupComponent group : theConceptMap.getGroup()) {
for (org.hl7.fhir.dstu3.model.ConceptMap.SourceElementComponent element : group.getElement()) {
for (org.hl7.fhir.dstu3.model.ConceptMap.TargetElementComponent target : element.getTarget()) {
List<String> columns = new ArrayList<>();
columns.add(defaultString(group.getSource()));
columns.add(defaultString(group.getSourceVersion()));
columns.add(defaultString(group.getTarget()));
columns.add(defaultString(group.getTargetVersion()));
columns.add(defaultString(element.getCode()));
columns.add(defaultString(element.getDisplay()));
columns.add(defaultString(target.getCode()));
columns.add(defaultString(target.getDisplay()));
columns.add(defaultString(target.getEquivalence().toCode()));
columns.add(defaultString(target.getComment()));
csvPrinter.print(columns);
}
}
}
} catch (IOException ioe) {
throw new InternalErrorException(ioe);
} finally {
IOUtils.closeQuietly(csvPrinter);
IOUtils.closeQuietly(bufferedWriter);
}
ourLog.info("Finished exporting to {}", file);
}
private void convertConceptMapToCsv(ConceptMap theConceptMap) {
ourLog.info("Exporting ConceptMap to CSV...");
Writer writer = null;
CSVPrinter csvPrinter = null;
try {
writer = Files.newBufferedWriter(Paths.get(file));
csvPrinter = new CSVPrinter(
writer,
CSVFormat
.DEFAULT
.withRecordSeparator("\n")
.withHeader(Header.class));
for (ConceptMapGroupComponent group : theConceptMap.getGroup()) {
for (SourceElementComponent element : group.getElement()) {
for (ConceptMap.TargetElementComponent target : element.getTarget()) {
List<String> columns = new ArrayList<>();
columns.add(defaultString(group.getSource()));
columns.add(defaultString(group.getSourceVersion()));
columns.add(defaultString(group.getTarget()));
columns.add(defaultString(group.getTargetVersion()));
columns.add(defaultString(element.getCode()));
columns.add(defaultString(element.getDisplay()));
columns.add(defaultString(target.getCode()));
columns.add(defaultString(target.getDisplay()));
columns.add(defaultString(target.getEquivalence().toCode()));
columns.add(defaultString(target.getComment()));
csvPrinter.printRecord(columns);
}
}
}
} catch (IOException ioe) {
throw new InternalErrorException(ioe);
} finally {
IOUtils.closeQuietly(csvPrinter);
IOUtils.closeQuietly(writer);
}
ourLog.info("Finished exporting to {}", file);
}
}

View File

@ -23,7 +23,6 @@ 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;
@ -42,6 +41,7 @@ import java.io.IOException;
import java.util.Collection; import java.util.Collection;
public class IgPackUploader extends BaseCommand { public class IgPackUploader extends BaseCommand {
// TODO: Don't use qualified names for loggers in HAPI CLI.
private static final Logger ourLog = LoggerFactory.getLogger(IgPackUploader.class); private static final Logger ourLog = LoggerFactory.getLogger(IgPackUploader.class);
@Override @Override

View File

@ -0,0 +1,336 @@
package ca.uhn.fhir.cli;
/*-
* #%L
* HAPI FHIR - Command Line Client - API
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.convertors.VersionConvertor_30_40;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent;
import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent;
import org.hl7.fhir.r4.model.ConceptMap.TargetElementComponent;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.UriType;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ExecutionException;
import static org.apache.commons.lang3.StringUtils.*;
public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConceptMapCommand {
// TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ImportCsvToConceptMapCommand.class);
protected static final String SOURCE_VALUE_SET_PARAM = "i";
protected static final String SOURCE_VALUE_SET_PARAM_LONGOPT = "input";
protected static final String SOURCE_VALUE_SET_PARAM_NAME = "input";
protected static final String SOURCE_VALUE_SET_PARAM_DESC = "The source value set of the ConceptMap to be imported (i.e. ConceptMap.sourceUri).";
protected static final String TARGET_VALUE_SET_PARAM = "o";
protected static final String TARGET_VALUE_SET_PARAM_LONGOPT = "output";
protected static final String TARGET_VALUE_SET_PARAM_NAME = "output";
protected static final String TARGET_VALUE_SET_PARAM_DESC = "The target value set of the ConceptMap to be imported (i.e. ConceptMap.targetUri).";
protected String sourceValueSet;
protected String targetValueSet;
private boolean hasElements;
private boolean hasTargets;
@Override
public String getCommandDescription() {
return "Imports a CSV file to a ConceptMap resource.";
}
@Override
public String getCommandName() {
return "import-csv-to-conceptmap";
}
@Override
public Options getOptions() {
Options options = new Options();
this.addFhirVersionOption(options);
addBaseUrlOption(options);
addRequiredOption(options, CONCEPTMAP_URL_PARAM, CONCEPTMAP_URL_PARAM_LONGOPT, CONCEPTMAP_URL_PARAM_NAME, CONCEPTMAP_URL_PARAM_DESC);
// </editor-fold desc="Additional parameters.">
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);
// </editor-fold>
addRequiredOption(options, FILE_PARAM, FILE_PARAM_LONGOPT, FILE_PARAM_NAME, FILE_PARAM_DESC);
addBasicAuthOption(options);
addVerboseLoggingOption(options);
return options;
}
@Override
protected void parseAdditionalParameters(CommandLine theCommandLine) throws ParseException {
sourceValueSet = theCommandLine.getOptionValue(SOURCE_VALUE_SET_PARAM);
if (isBlank(sourceValueSet)) {
ourLog.info("Source value set is not specified (i.e. ConceptMap.sourceUri).");
} else {
ourLog.info("Specified source value set (i.e. ConceptMap.sourceUri): {}", sourceValueSet);
}
targetValueSet = theCommandLine.getOptionValue(TARGET_VALUE_SET_PARAM);
if (isBlank(targetValueSet)) {
ourLog.info("Target value set is not specified (i.e. ConceptMap.targetUri).");
} else {
ourLog.info("Specified target value set (i.e. ConceptMap.targetUri): {}", targetValueSet);
}
}
@Override
protected void process() throws ParseException, ExecutionException {
searchForConceptMapByUrl();
}
private void searchForConceptMapByUrl() throws ParseException, ExecutionException {
if (fhirVersion == FhirVersionEnum.DSTU3) {
org.hl7.fhir.dstu3.model.ConceptMap conceptMap = convertCsvToConceptMapDstu3();
ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
MethodOutcome methodOutcome = client
.update()
.resource(conceptMap)
.conditional()
.where(org.hl7.fhir.dstu3.model.ConceptMap.URL.matches().value(conceptMapUrl))
.execute();
if (Boolean.TRUE.equals(methodOutcome.getCreated())) {
ourLog.info("Created new ConceptMap: {}", methodOutcome.getId().getValue());
} else {
ourLog.info("Updated existing ConceptMap: {}", methodOutcome.getId().getValue());
}
} else if (fhirVersion == FhirVersionEnum.R4) {
ConceptMap conceptMap = convertCsvToConceptMapR4();
ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
MethodOutcome methodOutcome = client
.update()
.resource(conceptMap)
.conditional()
.where(ConceptMap.URL.matches().value(conceptMapUrl))
.execute();
if (Boolean.TRUE.equals(methodOutcome.getCreated())) {
ourLog.info("Created new ConceptMap: {}", methodOutcome.getId().getValue());
} else {
ourLog.info("Updated existing ConceptMap: {}", methodOutcome.getId().getValue());
}
}
}
private org.hl7.fhir.dstu3.model.ConceptMap convertCsvToConceptMapDstu3() throws ParseException, ExecutionException {
try {
return VersionConvertor_30_40.convertConceptMap(convertCsvToConceptMapR4());
} catch (FHIRException fe) {
throw new ExecutionException(fe);
}
}
private ConceptMap convertCsvToConceptMapR4() throws ParseException, ExecutionException {
ourLog.info("Converting CSV to ConceptMap...");
ConceptMap retVal = new ConceptMap();
Reader reader = null;
CSVParser csvParser = null;
try {
reader = Files.newBufferedReader(Paths.get(file));
csvParser = new CSVParser(
reader,
CSVFormat
.DEFAULT
.withRecordSeparator("\n")
.withHeader(Header.class)
.withFirstRecordAsHeader()
.withIgnoreHeaderCase()
.withTrim());
retVal.setUrl(conceptMapUrl);
if (isNotBlank(sourceValueSet)) {
retVal.setSource(new UriType(sourceValueSet));
}
if (isNotBlank(targetValueSet)) {
retVal.setTarget(new UriType(targetValueSet));
}
TemporaryConceptMapGroup temporaryConceptMapGroup;
TemporaryConceptMapGroupElement temporaryConceptMapGroupElement;
Map<TemporaryConceptMapGroup, Map<TemporaryConceptMapGroupElement, Set<TemporaryConceptMapGroupElementTarget>>> groupMap = parseCsvRecords(csvParser);
Map<TemporaryConceptMapGroupElement, Set<TemporaryConceptMapGroupElementTarget>> elementMap;
Set<TemporaryConceptMapGroupElementTarget> targetSet;
ConceptMapGroupComponent conceptMapGroupComponent;
SourceElementComponent sourceElementComponent;
TargetElementComponent targetElementComponent;
for (Map.Entry<TemporaryConceptMapGroup, Map<TemporaryConceptMapGroupElement, Set<TemporaryConceptMapGroupElementTarget>>> groupEntry : groupMap.entrySet()) {
hasElements = false;
hasTargets = false;
temporaryConceptMapGroup = groupEntry.getKey();
conceptMapGroupComponent = new ConceptMapGroupComponent();
if (temporaryConceptMapGroup.hasSource()) {
conceptMapGroupComponent.setSource(temporaryConceptMapGroup.getSource());
}
if (temporaryConceptMapGroup.hasSourceVersion()) {
conceptMapGroupComponent.setSourceVersion(temporaryConceptMapGroup.getSourceVersion());
}
if (temporaryConceptMapGroup.hasTarget()) {
conceptMapGroupComponent.setTarget(temporaryConceptMapGroup.getTarget());
}
if (temporaryConceptMapGroup.hasTargetVersion()) {
conceptMapGroupComponent.setTargetVersion(temporaryConceptMapGroup.getTargetVersion());
}
elementMap = groupEntry.getValue();
for (Map.Entry<TemporaryConceptMapGroupElement, Set<TemporaryConceptMapGroupElementTarget>> elementEntry : elementMap.entrySet()) {
temporaryConceptMapGroupElement = elementEntry.getKey();
sourceElementComponent = new SourceElementComponent();
if (temporaryConceptMapGroupElement.hasCode()) {
sourceElementComponent.setCode(temporaryConceptMapGroupElement.getCode());
}
if (temporaryConceptMapGroupElement.hasDisplay()) {
sourceElementComponent.setDisplay(temporaryConceptMapGroupElement.getDisplay());
}
targetSet = elementEntry.getValue();
for (TemporaryConceptMapGroupElementTarget temporaryConceptMapGroupElementTarget : targetSet) {
targetElementComponent = new TargetElementComponent();
if (temporaryConceptMapGroupElementTarget.hasCode()) {
targetElementComponent.setCode(temporaryConceptMapGroupElementTarget.getCode());
}
if (temporaryConceptMapGroupElementTarget.hasDisplay()) {
targetElementComponent.setDisplay(temporaryConceptMapGroupElementTarget.getDisplay());
}
if (temporaryConceptMapGroupElementTarget.hasEquivalence()) {
try {
targetElementComponent.setEquivalence(Enumerations.ConceptMapEquivalence.fromCode(temporaryConceptMapGroupElementTarget.getEquivalence()));
} catch (FHIRException fe) {
throw new ExecutionException(fe);
}
}
if (temporaryConceptMapGroupElementTarget.hasComment()) {
targetElementComponent.setComment(temporaryConceptMapGroupElementTarget.getComment());
}
if (temporaryConceptMapGroupElementTarget.hasValues()) {
sourceElementComponent.addTarget(targetElementComponent);
hasTargets = true;
}
}
if (temporaryConceptMapGroupElement.hasValues() || hasTargets) {
conceptMapGroupComponent.addElement(sourceElementComponent);
hasElements = true;
}
}
if (temporaryConceptMapGroup.hasValues() || hasElements || hasTargets) {
retVal.addGroup(conceptMapGroupComponent);
}
}
} catch (IOException e) {
throw new InternalErrorException(e);
} finally {
IOUtils.closeQuietly(csvParser);
IOUtils.closeQuietly(reader);
}
ourLog.info("Finished converting CSV to ConceptMap.");
return retVal;
}
private Map<TemporaryConceptMapGroup, Map<TemporaryConceptMapGroupElement, Set<TemporaryConceptMapGroupElementTarget>>> parseCsvRecords(CSVParser theCsvParser) {
Map<TemporaryConceptMapGroup, Map<TemporaryConceptMapGroupElement, Set<TemporaryConceptMapGroupElementTarget>>> retVal = new LinkedHashMap<>();
TemporaryConceptMapGroup group;
TemporaryConceptMapGroupElement element;
TemporaryConceptMapGroupElementTarget target;
Map<TemporaryConceptMapGroupElement, Set<TemporaryConceptMapGroupElementTarget>> elementMap;
Set<TemporaryConceptMapGroupElementTarget> targetSet;
for (CSVRecord csvRecord : theCsvParser) {
group = new TemporaryConceptMapGroup(
defaultString(csvRecord.get(Header.SOURCE_CODE_SYSTEM)),
defaultString(csvRecord.get(Header.SOURCE_CODE_SYSTEM_VERSION)),
defaultString(csvRecord.get(Header.TARGET_CODE_SYSTEM)),
defaultString(csvRecord.get(Header.TARGET_CODE_SYSTEM_VERSION)));
element = new TemporaryConceptMapGroupElement(
defaultString(csvRecord.get(Header.SOURCE_CODE)),
defaultString(csvRecord.get(Header.SOURCE_DISPLAY)));
target = new TemporaryConceptMapGroupElementTarget(
defaultString(csvRecord.get(Header.TARGET_CODE)),
defaultString(csvRecord.get(Header.TARGET_DISPLAY)),
defaultString(csvRecord.get(Header.EQUIVALENCE)),
defaultString(csvRecord.get(Header.COMMENT)));
if (!retVal.containsKey(group)) {
targetSet = new LinkedHashSet<>();
targetSet.add(target);
elementMap = new LinkedHashMap<>();
elementMap.put(element, targetSet);
retVal.put(group, elementMap);
} else if (!retVal.get(group).containsKey(element)) {
targetSet = new LinkedHashSet<>();
targetSet.add(target);
retVal.get(group).put(element, targetSet);
} else {
retVal.get(group).get(element).add(target);
}
}
return retVal;
}
}

View File

@ -35,6 +35,7 @@ public class LoadingValidationSupportDstu2 implements IValidationSupport {
private FhirContext myCtx = FhirContext.forDstu2Hl7Org(); private FhirContext myCtx = FhirContext.forDstu2Hl7Org();
// TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoadingValidationSupportDstu2.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoadingValidationSupportDstu2.class);
@Override @Override

View File

@ -38,6 +38,7 @@ public class LoadingValidationSupportDstu3 implements IValidationSupport {
private FhirContext myCtx = FhirContext.forDstu3(); private FhirContext myCtx = FhirContext.forDstu3();
// TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoadingValidationSupportDstu3.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoadingValidationSupportDstu3.class);
@Override @Override

View File

@ -34,7 +34,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IValidationSupport { public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IValidationSupport {
// TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoadingValidationSupportR4.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoadingValidationSupportR4.class);
private FhirContext myCtx = FhirContext.forR4(); private FhirContext myCtx = FhirContext.forR4();

View File

@ -20,17 +20,11 @@ package ca.uhn.fhir.cli;
* #L% * #L%
*/ */
import java.io.BufferedOutputStream; import ca.uhn.fhir.jpa.dao.DaoConfig;
import java.io.File; import ca.uhn.fhir.jpa.demo.ContextHolder;
import java.io.FileOutputStream; import ca.uhn.fhir.jpa.demo.FhirServerConfig;
import java.io.IOException; import ca.uhn.fhir.jpa.demo.FhirServerConfigDstu3;
import java.io.InputStream; import ca.uhn.fhir.jpa.demo.FhirServerConfigR4;
import java.io.OutputStream;
import java.net.SocketException;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
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;
@ -41,8 +35,10 @@ import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import ca.uhn.fhir.jpa.dao.DaoConfig; import javax.servlet.ServletContextEvent;
import ca.uhn.fhir.jpa.demo.*; import javax.servlet.ServletContextListener;
import java.io.*;
import java.net.SocketException;
public class RunServerCommand extends BaseCommand { public class RunServerCommand extends BaseCommand {
@ -53,6 +49,7 @@ public class RunServerCommand extends BaseCommand {
private static final int DEFAULT_PORT = 8080; private static final int DEFAULT_PORT = 8080;
private static final String OPTION_P = "p"; private static final String OPTION_P = "p";
// TODO: Don't use qualified names for loggers in HAPI CLI.
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"; public static final String RUN_SERVER_COMMAND = "run-server";
private int myPort; private int myPort;

View File

@ -24,10 +24,8 @@ 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.LoggingInterceptor; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
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.hl7.fhir.dstu3.model.Parameters; import org.hl7.fhir.dstu3.model.Parameters;
@ -36,12 +34,10 @@ import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseParameters;
import static org.apache.commons.lang3.StringUtils.isBlank; 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 {
// TODO: Don't use qualified names for loggers in HAPI CLI.
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"; private static final String UPLOAD_EXTERNAL_CODE_SYSTEM = "upload-external-code-system";
@Override @Override
@ -59,10 +55,11 @@ public class UploadTerminologyCommand extends BaseCommand {
Options options = new Options(); Options options = new Options();
addFhirVersionOption(options); addFhirVersionOption(options);
addRequiredOption(options, "t", "target", true, "Base URL for the target server (e.g. \"http://example.com/fhir\")"); addBaseUrlOption(options);
addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + IHapiTerminologyLoaderSvc.SCT_URI + ")"); addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + IHapiTerminologyLoaderSvc.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, "d", "data", true, "Local file to use to upload (can be a raw file or a ZIP containing the raw file)");
addBasicAuthOption(options); addBasicAuthOption(options);
addVerboseLoggingOption(options);
return options; return options;
} }
@ -72,13 +69,6 @@ public class UploadTerminologyCommand extends BaseCommand {
parseFhirContext(theCommandLine); parseFhirContext(theCommandLine);
FhirContext ctx = getFhirContext(); FhirContext ctx = getFhirContext();
String targetServer = theCommandLine.getOptionValue(BASE_URL_PARAM);
if (isBlank(targetServer)) {
throw new ParseException("No target server (-" + BASE_URL_PARAM + ") specified");
} else if (targetServer.startsWith("http") == false && targetServer.startsWith("file") == false) {
throw new ParseException("Invalid target server specified, must begin with 'http' or 'file'");
}
String termUrl = theCommandLine.getOptionValue("u"); String termUrl = theCommandLine.getOptionValue("u");
if (isBlank(termUrl)) { if (isBlank(termUrl)) {
throw new ParseException("No URL provided"); throw new ParseException("No URL provided");
@ -89,8 +79,6 @@ public class UploadTerminologyCommand extends BaseCommand {
throw new ParseException("No data file provided"); throw new ParseException("No data file provided");
} }
String bearerToken = theCommandLine.getOptionValue("b");
IGenericClient client = super.newClient(theCommandLine); IGenericClient client = super.newClient(theCommandLine);
IBaseParameters inputParameters; IBaseParameters inputParameters;
if (ctx.getVersion().getVersion() == FhirVersionEnum.DSTU3) { if (ctx.getVersion().getVersion() == FhirVersionEnum.DSTU3) {
@ -104,11 +92,7 @@ public class UploadTerminologyCommand extends BaseCommand {
throw new ParseException("This command does not support FHIR version " + ctx.getVersion().getVersion()); throw new ParseException("This command does not support FHIR version " + ctx.getVersion().getVersion());
} }
if (isNotBlank(bearerToken)) { if (theCommandLine.hasOption(VERBOSE_LOGGING_PARAM)) {
client.registerInterceptor(new BearerTokenAuthInterceptor(bearerToken));
}
if (theCommandLine.hasOption('v')) {
client.registerInterceptor(new LoggingInterceptor(true)); client.registerInterceptor(new LoggingInterceptor(true));
} }

View File

@ -41,7 +41,7 @@ import static org.apache.commons.lang3.StringUtils.*;
import static org.fusesource.jansi.Ansi.ansi; import static org.fusesource.jansi.Ansi.ansi;
public class ValidateCommand extends BaseCommand { public class ValidateCommand extends BaseCommand {
// TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateCommand.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateCommand.class);
@Override @Override

View File

@ -53,7 +53,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class ValidationDataUploader extends BaseCommand { public class ValidationDataUploader extends BaseCommand {
// TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidationDataUploader.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidationDataUploader.class);
private ArrayList<IIdType> myExcludes = new ArrayList<>(); private ArrayList<IIdType> myExcludes = new ArrayList<>();
@ -167,7 +167,7 @@ public class ValidationDataUploader extends BaseCommand {
} }
private void uploadDefinitionsDstu2(CommandLine theCommandLine, FhirContext ctx) throws CommandFailureException { private void uploadDefinitionsDstu2(CommandLine theCommandLine, FhirContext ctx) throws CommandFailureException, ParseException {
IGenericClient client = newClient(theCommandLine); IGenericClient client = newClient(theCommandLine);
ourLog.info("Uploading definitions to server"); ourLog.info("Uploading definitions to server");
@ -267,7 +267,7 @@ 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(CommandLine theCommandLine, FhirContext theCtx) throws CommandFailureException { private void uploadDefinitionsDstu3(CommandLine theCommandLine, FhirContext theCtx) throws CommandFailureException, ParseException {
IGenericClient client = newClient(theCommandLine); IGenericClient client = newClient(theCommandLine);
ourLog.info("Uploading definitions to server"); ourLog.info("Uploading definitions to server");
@ -365,7 +365,7 @@ 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(CommandLine theCommandLine, FhirContext theCtx) throws CommandFailureException { private void uploadDefinitionsR4(CommandLine theCommandLine, FhirContext theCtx) throws CommandFailureException, ParseException {
IGenericClient client = newClient(theCommandLine); IGenericClient client = newClient(theCommandLine);
ourLog.info("Uploading definitions to server"); ourLog.info("Uploading definitions to server");

View File

@ -37,6 +37,7 @@ 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");
// TODO: Don't use qualified names for loggers in HAPI CLI.
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;

View File

@ -0,0 +1,282 @@
package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence;
import org.hl7.fhir.r4.model.UriType;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
public class ExportConceptMapToCsvCommandTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommandTest.class);
private static final String CM_URL = "http://example.com/conceptmap";
private static final String VS_URL_1 = "http://example.com/valueset/1";
private static final String VS_URL_2 = "http://example.com/valueset/2";
private static final String CS_URL_1 = "http://example.com/codesystem/1";
private static final String CS_URL_2 = "http://example.com/codesystem/2";
private static final String CS_URL_3 = "http://example.com/codesystem/3";
private static final String FILE = "./target/output.csv";
private static String ourBase;
private static IGenericClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4();
private static int ourPort;
private static Server ourServer;
static {
System.setProperty("test", "true");
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
ServletHandler servletHandler = new ServletHandler();
RestfulServer restfulServer = new RestfulServer(ourCtx);
restfulServer.registerInterceptor(new VerboseLoggingInterceptor());
restfulServer.setResourceProviders(new HashMapResourceProviderConceptMapR4(ourCtx));
ServletHolder servletHolder = new ServletHolder(restfulServer);
servletHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(servletHandler);
ourServer.start();
ourBase = "http://localhost:" + ourPort;
ourClient = ourCtx.newRestfulGenericClient(ourBase);
ourClient.create().resource(createConceptMap()).execute();
}
@Test
public void testExportConceptMapToCsvCommand() throws IOException {
ourLog.info("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
App.main(new String[] {"export-conceptmap-to-csv",
"-v", "r4",
"-t", ourBase,
"-u", CM_URL,
"-f", FILE,
"-l"});
String expected = "SOURCE_CODE_SYSTEM,SOURCE_CODE_SYSTEM_VERSION,TARGET_CODE_SYSTEM,TARGET_CODE_SYSTEM_VERSION,SOURCE_CODE,SOURCE_DISPLAY,TARGET_CODE,TARGET_DISPLAY,EQUIVALENCE,COMMENT\n" +
"http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/2,Version 2t,Code 1a,Display 1a,Code 2a,Display 2a,equal,2a This is a comment.\n" +
"http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/2,Version 2t,Code 1b,Display 1b,Code 2b,Display 2b,equal,2b This is a comment.\n" +
"http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/2,Version 2t,Code 1c,Display 1c,Code 2c,Display 2c,equal,2c This is a comment.\n" +
"http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/2,Version 2t,Code 1d,Display 1d,Code 2d,Display 2d,equal,2d This is a comment.\n" +
"http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/3,Version 3t,Code 1a,Display 1a,Code 3a,Display 3a,equal,3a This is a comment.\n" +
"http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/3,Version 3t,Code 1b,Display 1b,Code 3b,Display 3b,equal,3b This is a comment.\n" +
"http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/3,Version 3t,Code 1c,Display 1c,Code 3c,Display 3c,equal,3c This is a comment.\n" +
"http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/3,Version 3t,Code 1d,Display 1d,Code 3d,Display 3d,equal,3d This is a comment.\n" +
"http://example.com/codesystem/2,Version 2s,http://example.com/codesystem/3,Version 3t,Code 2a,Display 2a,Code 3a,Display 3a,equal,3a This is a comment.\n" +
"http://example.com/codesystem/2,Version 2s,http://example.com/codesystem/3,Version 3t,Code 2b,Display 2b,Code 3b,Display 3b,equal,3b This is a comment.\n" +
"http://example.com/codesystem/2,Version 2s,http://example.com/codesystem/3,Version 3t,Code 2c,Display 2c,Code 3c,Display 3c,equal,3c This is a comment.\n" +
"http://example.com/codesystem/2,Version 2s,http://example.com/codesystem/3,Version 3t,Code 2d,Display 2d,Code 3d,Display 3d,equal,3d This is a comment.\n";
String result = IOUtils.toString(new FileInputStream(FILE), Charsets.UTF_8);
assertEquals(expected, result);
FileUtils.deleteQuietly(new File(FILE));
}
static ConceptMap createConceptMap() {
ConceptMap conceptMap = new ConceptMap();
conceptMap
.setUrl(CM_URL)
.setSource(new UriType(VS_URL_1))
.setTarget(new UriType(VS_URL_2));
ConceptMap.ConceptMapGroupComponent group = conceptMap.addGroup();
group
.setSource(CS_URL_1)
.setSourceVersion("Version 1s")
.setTarget(CS_URL_2)
.setTargetVersion("Version 2t");
ConceptMap.SourceElementComponent element = group.addElement();
element
.setCode("Code 1a")
.setDisplay("Display 1a");
ConceptMap.TargetElementComponent target = element.addTarget();
target
.setCode("Code 2a")
.setDisplay("Display 2a")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("2a This is a comment.");
element = group.addElement();
element
.setCode("Code 1b")
.setDisplay("Display 1b");
target = element.addTarget();
target
.setCode("Code 2b")
.setDisplay("Display 2b")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("2b This is a comment.");
element = group.addElement();
element
.setCode("Code 1c")
.setDisplay("Display 1c");
target = element.addTarget();
target
.setCode("Code 2c")
.setDisplay("Display 2c")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("2c This is a comment.");
element = group.addElement();
element
.setCode("Code 1d")
.setDisplay("Display 1d");
target = element.addTarget();
target
.setCode("Code 2d")
.setDisplay("Display 2d")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("2d This is a comment.");
group = conceptMap.addGroup();
group
.setSource(CS_URL_1)
.setSourceVersion("Version 1s")
.setTarget(CS_URL_3)
.setTargetVersion("Version 3t");
element = group.addElement();
element
.setCode("Code 1a")
.setDisplay("Display 1a");
target = element.addTarget();
target
.setCode("Code 3a")
.setDisplay("Display 3a")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3a This is a comment.");
element = group.addElement();
element
.setCode("Code 1b")
.setDisplay("Display 1b");
target = element.addTarget();
target
.setCode("Code 3b")
.setDisplay("Display 3b")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3b This is a comment.");
element = group.addElement();
element
.setCode("Code 1c")
.setDisplay("Display 1c");
target = element.addTarget();
target
.setCode("Code 3c")
.setDisplay("Display 3c")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3c This is a comment.");
element = group.addElement();
element
.setCode("Code 1d")
.setDisplay("Display 1d");
target = element.addTarget();
target
.setCode("Code 3d")
.setDisplay("Display 3d")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3d This is a comment.");
group = conceptMap.addGroup();
group
.setSource(CS_URL_2)
.setSourceVersion("Version 2s")
.setTarget(CS_URL_3)
.setTargetVersion("Version 3t");
element = group.addElement();
element
.setCode("Code 2a")
.setDisplay("Display 2a");
target = element.addTarget();
target
.setCode("Code 3a")
.setDisplay("Display 3a")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3a This is a comment.");
element = group.addElement();
element
.setCode("Code 2b")
.setDisplay("Display 2b");
target = element.addTarget();
target
.setCode("Code 3b")
.setDisplay("Display 3b")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3b This is a comment.");
element = group.addElement();
element
.setCode("Code 2c")
.setDisplay("Display 2c");
target = element.addTarget();
target
.setCode("Code 3c")
.setDisplay("Display 3c")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3c This is a comment.");
element = group.addElement();
element
.setCode("Code 2d")
.setDisplay("Display 2d");
target = element.addTarget();
target
.setCode("Code 3d")
.setDisplay("Display 3d")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3d This is a comment.");
return conceptMap;
}
}

View File

@ -0,0 +1,127 @@
package ca.uhn.fhir.cli;
/*-
* #%L
* HAPI FHIR - Server Framework
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.provider.AbstractHashMapResourceProvider;
import com.google.common.base.Charsets;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.IdType;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* This is a subclass to implement FHIR operations specific to R4 ConceptMap
* resources. Its superclass, {@link AbstractHashMapResourceProvider}, is a simple
* implementation of the resource provider interface that uses a HashMap to
* store all resources in memory.
* <p>
* This subclass currently supports the following FHIR operations:
* </p>
* <ul>
* <li>Search for R4 ConceptMap resources by ConceptMap.url</li>
* <li>Conditional update for R4 ConceptMap resources by ConceptMap.url</li>
* </ul>
*/
public class HashMapResourceProviderConceptMapR4 extends AbstractHashMapResourceProvider<ConceptMap> {
@SuppressWarnings("unchecked")
public HashMapResourceProviderConceptMapR4(FhirContext theFhirContext) {
super(theFhirContext, ConceptMap.class);
FhirVersionEnum fhirVersion = theFhirContext.getVersion().getVersion();
if (fhirVersion != FhirVersionEnum.R4) {
throw new IllegalStateException("Requires FHIR version R4. Unsupported FHIR version provided: " + fhirVersion);
}
}
@Search
public List<ConceptMap> searchByUrl(
@RequiredParam(name=ConceptMap.SP_URL) String theConceptMapUrl) {
List<ConceptMap> retVal = new ArrayList<>();
for (TreeMap<Long, ConceptMap> next : myIdToVersionToResourceMap.values()) {
if (!next.isEmpty()) {
ConceptMap conceptMap = next.lastEntry().getValue();
if (theConceptMapUrl.equals(conceptMap.getUrl()))
retVal.add(conceptMap);
break;
}
}
return retVal;
}
@Update
public MethodOutcome updateConceptMapConditional(
@ResourceParam ConceptMap theConceptMap,
@IdParam IdType theId,
@ConditionalUrlParam String theConditional) {
MethodOutcome methodOutcome = new MethodOutcome();
if (theConditional != null) {
String url = null;
try {
List<NameValuePair> params = URLEncodedUtils.parse(new URI(theConditional), Charsets.UTF_8);
for (NameValuePair param : params) {
if (param.getName().equalsIgnoreCase("url")) {
url = param.getValue();
break;
}
}
} catch (URISyntaxException urise) {
throw new InvalidRequestException(urise);
}
if (isNotBlank(url)) {
List<ConceptMap> conceptMaps = searchByUrl(url);
if (!conceptMaps.isEmpty()) {
methodOutcome = update(conceptMaps.get(0));
} else {
methodOutcome = create(theConceptMap);
}
}
} else {
methodOutcome = update(theConceptMap);
}
return methodOutcome;
}
}

View File

@ -0,0 +1,371 @@
package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent;
import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent;
import org.hl7.fhir.r4.model.ConceptMap.TargetElementComponent;
import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import static org.junit.Assert.*;
public class ImportCsvToConceptMapCommandTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ImportCsvToConceptMapCommandTest.class);
private static final String CM_URL = "http://example.com/conceptmap";
private static final String VS_URL_1 = "http://example.com/valueset/1";
private static final String VS_URL_2 = "http://example.com/valueset/2";
private static final String CS_URL_1 = "http://example.com/codesystem/1";
private static final String CS_URL_2 = "http://example.com/codesystem/2";
private static final String CS_URL_3 = "http://example.com/codesystem/3";
private static final String FILENAME = "import-csv-to-conceptmap-command-test-input.csv";
private static String file;
private static String ourBase;
private static IGenericClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4();
private static int ourPort;
private static Server ourServer;
private static RestfulServer restfulServer;
private static HashMapResourceProviderConceptMapR4 hashMapResourceProviderConceptMapR4;
static {
System.setProperty("test", "true");
}
@After
public void afterClearResourceProvider() {
HashMapResourceProviderConceptMapR4 resourceProvider = (HashMapResourceProviderConceptMapR4) restfulServer.getResourceProviders().iterator().next();
resourceProvider.clear();
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
ServletHandler servletHandler = new ServletHandler();
restfulServer = new RestfulServer(ourCtx);
restfulServer.registerInterceptor(new VerboseLoggingInterceptor());
restfulServer.setResourceProviders(new HashMapResourceProviderConceptMapR4(ourCtx));
ServletHolder servletHolder = new ServletHolder(restfulServer);
servletHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(servletHandler);
ourServer.start();
ourBase = "http://localhost:" + ourPort;
ourClient = ourCtx.newRestfulGenericClient(ourBase);
}
@Test
public void testConditionalUpdateResultsInCreate() {
ConceptMap conceptMap = ExportConceptMapToCsvCommandTest.createConceptMap();
String conceptMapUrl = conceptMap.getUrl();
ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
MethodOutcome methodOutcome = ourClient
.update()
.resource(conceptMap)
.conditional()
.where(ConceptMap.URL.matches().value(conceptMapUrl))
.execute();
// Do not simplify to assertEquals(...)
assertTrue(Boolean.TRUE.equals(methodOutcome.getCreated()));
}
@Test
public void testConditionalUpdateResultsInUpdate() {
ConceptMap conceptMap = ExportConceptMapToCsvCommandTest.createConceptMap();
ourClient.create().resource(conceptMap).execute();
String conceptMapUrl = conceptMap.getUrl();
ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
MethodOutcome methodOutcome = ourClient
.update()
.resource(conceptMap)
.conditional()
.where(ConceptMap.URL.matches().value(conceptMapUrl))
.execute();
// Do not simplify to assertEquals(...)
assertTrue(!Boolean.TRUE.equals(methodOutcome.getCreated()));
}
@Test
public void testNonConditionalUpdate() {
ConceptMap conceptMap = ExportConceptMapToCsvCommandTest.createConceptMap();
ourClient.create().resource(conceptMap).execute();
Bundle response = ourClient
.search()
.forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value(CM_URL))
.returnBundle(Bundle.class)
.execute();
ConceptMap resultConceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
MethodOutcome methodOutcome = ourClient
.update()
.resource(resultConceptMap)
.withId(resultConceptMap.getIdElement())
.execute();
assertNull(methodOutcome.getCreated());
// Do not simplify to assertEquals(...)
assertTrue(!Boolean.TRUE.equals(methodOutcome.getCreated()));
}
@Test
public void testImportCsvToConceptMapCommand() throws FHIRException {
ClassLoader classLoader = getClass().getClassLoader();
File fileToImport = new File(classLoader.getResource(FILENAME).getFile());
ImportCsvToConceptMapCommandTest.file = fileToImport.getAbsolutePath();
App.main(new String[] {"import-csv-to-conceptmap",
"-v", "r4",
"-t", ourBase,
"-u", CM_URL,
"-i", VS_URL_1,
"-o", VS_URL_2,
"-f", file,
"-l"});
Bundle response = ourClient
.search()
.forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value(CM_URL))
.returnBundle(Bundle.class)
.execute();
ConceptMap conceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/1", conceptMap.getId());
assertEquals(CM_URL, conceptMap.getUrl());
assertEquals(VS_URL_1, conceptMap.getSourceUriType().getValueAsString());
assertEquals(VS_URL_2, conceptMap.getTargetUriType().getValueAsString());
assertEquals(3, conceptMap.getGroup().size());
ConceptMapGroupComponent group = conceptMap.getGroup().get(0);
assertEquals(CS_URL_1, group.getSource());
assertEquals("Version 1s", group.getSourceVersion());
assertEquals(CS_URL_2, group.getTarget());
assertEquals("Version 2t", group.getTargetVersion());
assertEquals(4, group.getElement().size());
SourceElementComponent source = group.getElement().get(0);
assertEquals("Code 1a", source.getCode());
assertEquals("Display 1a", source.getDisplay());
assertEquals(1, source.getTarget().size());
TargetElementComponent target = source.getTarget().get(0);
assertEquals("Code 2a", target.getCode());
assertEquals("Display 2a", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("2a This is a comment.", target.getComment());
source = group.getElement().get(1);
assertEquals("Code 1b", source.getCode());
assertEquals("Display 1b", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 2b", target.getCode());
assertEquals("Display 2b", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("2b This is a comment.", target.getComment());
source = group.getElement().get(2);
assertEquals("Code 1c", source.getCode());
assertEquals("Display 1c", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 2c", target.getCode());
assertEquals("Display 2c", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("2c This is a comment.", target.getComment());
source = group.getElement().get(3);
assertEquals("Code 1d", source.getCode());
assertEquals("Display 1d", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 2d", target.getCode());
assertEquals("Display 2d", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("2d This is a comment.", target.getComment());
group = conceptMap.getGroup().get(1);
assertEquals(CS_URL_1, group.getSource());
assertEquals("Version 1s", group.getSourceVersion());
assertEquals(CS_URL_3, group.getTarget());
assertEquals("Version 3t", group.getTargetVersion());
assertEquals(4, group.getElement().size());
source = group.getElement().get(0);
assertEquals("Code 1a", source.getCode());
assertEquals("Display 1a", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3a", target.getCode());
assertEquals("Display 3a", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3a This is a comment.", target.getComment());
source = group.getElement().get(1);
assertEquals("Code 1b", source.getCode());
assertEquals("Display 1b", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3b", target.getCode());
assertEquals("Display 3b", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3b This is a comment.", target.getComment());
source = group.getElement().get(2);
assertEquals("Code 1c", source.getCode());
assertEquals("Display 1c", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3c", target.getCode());
assertEquals("Display 3c", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3c This is a comment.", target.getComment());
source = group.getElement().get(3);
assertEquals("Code 1d", source.getCode());
assertEquals("Display 1d", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3d", target.getCode());
assertEquals("Display 3d", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3d This is a comment.", target.getComment());
group = conceptMap.getGroup().get(2);
assertEquals(CS_URL_2, group.getSource());
assertEquals("Version 2s", group.getSourceVersion());
assertEquals(CS_URL_3, group.getTarget());
assertEquals("Version 3t", group.getTargetVersion());
assertEquals(4, group.getElement().size());
source = group.getElement().get(0);
assertEquals("Code 2a", source.getCode());
assertEquals("Display 2a", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3a", target.getCode());
assertEquals("Display 3a", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3a This is a comment.", target.getComment());
source = group.getElement().get(1);
assertEquals("Code 2b", source.getCode());
assertEquals("Display 2b", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3b", target.getCode());
assertEquals("Display 3b", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3b This is a comment.", target.getComment());
source = group.getElement().get(2);
assertEquals("Code 2c", source.getCode());
assertEquals("Display 2c", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3c", target.getCode());
assertEquals("Display 3c", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3c This is a comment.", target.getComment());
source = group.getElement().get(3);
assertEquals("Code 2d", source.getCode());
assertEquals("Display 2d", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3d", target.getCode());
assertEquals("Display 3d", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3d This is a comment.", target.getComment());
App.main(new String[] {"import-csv-to-conceptmap",
"-v", "r4",
"-t", ourBase,
"-u", CM_URL,
"-i", VS_URL_1,
"-o", VS_URL_2,
"-f", file,
"-l"});
response = ourClient
.search()
.forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value(CM_URL))
.returnBundle(Bundle.class)
.execute();
conceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/2", conceptMap.getId());
}
}

View File

@ -0,0 +1,13 @@
SOURCE_CODE_SYSTEM,SOURCE_CODE_SYSTEM_VERSION,TARGET_CODE_SYSTEM,TARGET_CODE_SYSTEM_VERSION,SOURCE_CODE,SOURCE_DISPLAY,TARGET_CODE,TARGET_DISPLAY,EQUIVALENCE,COMMENT
http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/2,Version 2t,Code 1a,Display 1a,Code 2a,Display 2a,equal,2a This is a comment.
http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/2,Version 2t,Code 1b,Display 1b,Code 2b,Display 2b,equal,2b This is a comment.
http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/2,Version 2t,Code 1c,Display 1c,Code 2c,Display 2c,equal,2c This is a comment.
http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/2,Version 2t,Code 1d,Display 1d,Code 2d,Display 2d,equal,2d This is a comment.
http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/3,Version 3t,Code 1a,Display 1a,Code 3a,Display 3a,equal,3a This is a comment.
http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/3,Version 3t,Code 1b,Display 1b,Code 3b,Display 3b,equal,3b This is a comment.
http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/3,Version 3t,Code 1c,Display 1c,Code 3c,Display 3c,equal,3c This is a comment.
http://example.com/codesystem/1,Version 1s,http://example.com/codesystem/3,Version 3t,Code 1d,Display 1d,Code 3d,Display 3d,equal,3d This is a comment.
http://example.com/codesystem/2,Version 2s,http://example.com/codesystem/3,Version 3t,Code 2a,Display 2a,Code 3a,Display 3a,equal,3a This is a comment.
http://example.com/codesystem/2,Version 2s,http://example.com/codesystem/3,Version 3t,Code 2b,Display 2b,Code 3b,Display 3b,equal,3b This is a comment.
http://example.com/codesystem/2,Version 2s,http://example.com/codesystem/3,Version 3t,Code 2c,Display 2c,Code 3c,Display 3c,equal,3c This is a comment.
http://example.com/codesystem/2,Version 2s,http://example.com/codesystem/3,Version 3t,Code 2d,Display 2d,Code 3d,Display 3d,equal,3d This is a comment.
1 SOURCE_CODE_SYSTEM SOURCE_CODE_SYSTEM_VERSION TARGET_CODE_SYSTEM TARGET_CODE_SYSTEM_VERSION SOURCE_CODE SOURCE_DISPLAY TARGET_CODE TARGET_DISPLAY EQUIVALENCE COMMENT
2 http://example.com/codesystem/1 Version 1s http://example.com/codesystem/2 Version 2t Code 1a Display 1a Code 2a Display 2a equal 2a This is a comment.
3 http://example.com/codesystem/1 Version 1s http://example.com/codesystem/2 Version 2t Code 1b Display 1b Code 2b Display 2b equal 2b This is a comment.
4 http://example.com/codesystem/1 Version 1s http://example.com/codesystem/2 Version 2t Code 1c Display 1c Code 2c Display 2c equal 2c This is a comment.
5 http://example.com/codesystem/1 Version 1s http://example.com/codesystem/2 Version 2t Code 1d Display 1d Code 2d Display 2d equal 2d This is a comment.
6 http://example.com/codesystem/1 Version 1s http://example.com/codesystem/3 Version 3t Code 1a Display 1a Code 3a Display 3a equal 3a This is a comment.
7 http://example.com/codesystem/1 Version 1s http://example.com/codesystem/3 Version 3t Code 1b Display 1b Code 3b Display 3b equal 3b This is a comment.
8 http://example.com/codesystem/1 Version 1s http://example.com/codesystem/3 Version 3t Code 1c Display 1c Code 3c Display 3c equal 3c This is a comment.
9 http://example.com/codesystem/1 Version 1s http://example.com/codesystem/3 Version 3t Code 1d Display 1d Code 3d Display 3d equal 3d This is a comment.
10 http://example.com/codesystem/2 Version 2s http://example.com/codesystem/3 Version 3t Code 2a Display 2a Code 3a Display 3a equal 3a This is a comment.
11 http://example.com/codesystem/2 Version 2s http://example.com/codesystem/3 Version 3t Code 2b Display 2b Code 3b Display 3b equal 3b This is a comment.
12 http://example.com/codesystem/2 Version 2s http://example.com/codesystem/3 Version 3t Code 2c Display 2c Code 3c Display 3c equal 3c This is a comment.
13 http://example.com/codesystem/2 Version 2s http://example.com/codesystem/3 Version 3t Code 2d Display 2d Code 3d Display 3d equal 3d This is a comment.

View File

@ -0,0 +1,198 @@
package ca.uhn.fhir.rest.server.provider;
/*-
* #%L
* HAPI FHIR - Server Framework
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* This class is a simple implementation of the resource provider
* interface that uses a HashMap to store all resources in memory.
* It is essentially a copy of {@link ca.uhn.fhir.rest.server.provider.HashMapResourceProvider}
* with the {@link Update} and {@link ResourceParam} annotations removed from method
* {@link ca.uhn.fhir.rest.server.provider.HashMapResourceProvider#update(IBaseResource)}.
* Non-generic subclasses of this abstract class may implement their own annotated methods (e.g. a conditional
* update method specifically for ConceptMap resources).
* <p>
* This class currently supports the following FHIR operations:
* </p>
* <ul>
* <li>Create</li>
* <li>Update existing resource</li>
* <li>Update non-existing resource (e.g. create with client-supplied ID)</li>
* <li>Delete</li>
* <li>Search by resource type with no parameters</li>
* </ul>
*
* @param <T> The resource type to support
*/
public class AbstractHashMapResourceProvider<T extends IBaseResource> implements IResourceProvider {
private static final Logger ourLog = LoggerFactory.getLogger(AbstractHashMapResourceProvider.class);
private final Class<T> myResourceType;
private final FhirContext myFhirContext;
private final String myResourceName;
protected Map<String, TreeMap<Long, T>> myIdToVersionToResourceMap = new HashMap<>();
private long myNextId;
/**
* Constructor
*
* @param theFhirContext The FHIR context
* @param theResourceType The resource type to support
*/
@SuppressWarnings("WeakerAccess")
public AbstractHashMapResourceProvider(FhirContext theFhirContext, Class<T> theResourceType) {
myFhirContext = theFhirContext;
myResourceType = theResourceType;
myResourceName = myFhirContext.getResourceDefinition(theResourceType).getName();
clear();
}
/**
* Clear all data held in this resource provider
*/
public void clear() {
myNextId = 1;
myIdToVersionToResourceMap.clear();
}
@Create
public MethodOutcome create(@ResourceParam T theResource) {
long idPart = myNextId++;
String idPartAsString = Long.toString(idPart);
Long versionIdPart = 1L;
IIdType id = store(theResource, idPartAsString, versionIdPart);
return new MethodOutcome()
.setCreated(true)
.setId(id);
}
@Delete
public MethodOutcome delete(@IdParam IIdType theId) {
TreeMap<Long, T> versions = myIdToVersionToResourceMap.get(theId.getIdPart());
if (versions == null || versions.isEmpty()) {
throw new ResourceNotFoundException(theId);
}
long nextVersion = versions.lastEntry().getKey() + 1L;
IIdType id = store(null, theId.getIdPart(), nextVersion);
return new MethodOutcome()
.setId(id);
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return myResourceType;
}
private synchronized TreeMap<Long, T> getVersionToResource(String theIdPart) {
if (!myIdToVersionToResourceMap.containsKey(theIdPart)) {
myIdToVersionToResourceMap.put(theIdPart, new TreeMap<Long, T>());
}
return myIdToVersionToResourceMap.get(theIdPart);
}
@Read(version = true)
public IBaseResource read(@IdParam IIdType theId) {
TreeMap<Long, T> versions = myIdToVersionToResourceMap.get(theId.getIdPart());
if (versions == null || versions.isEmpty()) {
throw new ResourceNotFoundException(theId);
}
if (theId.hasVersionIdPart()) {
Long versionId = theId.getVersionIdPartAsLong();
if (!versions.containsKey(versionId)) {
throw new ResourceNotFoundException(theId);
} else {
T resource = versions.get(versionId);
if (resource == null) {
throw new ResourceGoneException(theId);
}
return resource;
}
} else {
return versions.lastEntry().getValue();
}
}
@Search
public List<IBaseResource> search() {
List<IBaseResource> retVal = new ArrayList<>();
for (TreeMap<Long, T> next : myIdToVersionToResourceMap.values()) {
if (next.isEmpty() == false) {
retVal.add(next.lastEntry().getValue());
}
}
return retVal;
}
private IIdType store(@ResourceParam T theResource, String theIdPart, Long theVersionIdPart) {
IIdType id = myFhirContext.getVersion().newIdType();
id.setParts(null, myResourceName, theIdPart, Long.toString(theVersionIdPart));
if (theResource != null) {
theResource.setId(id);
}
TreeMap<Long, T> versionToResource = getVersionToResource(theIdPart);
versionToResource.put(theVersionIdPart, theResource);
ourLog.info("Storing resource with ID: {}", id.getValue());
return id;
}
public MethodOutcome update(T theResource) {
String idPartAsString = theResource.getIdElement().getIdPart();
TreeMap<Long, T> versionToResource = getVersionToResource(idPartAsString);
Long versionIdPart;
Boolean created;
if (versionToResource.isEmpty()) {
versionIdPart = 1L;
created = true;
} else {
versionIdPart = versionToResource.lastKey() + 1L;
created = false;
}
IIdType id = store(theResource, idPartAsString, versionIdPart);
return new MethodOutcome()
.setCreated(created)
.setId(id);
}
}

View File

@ -54,7 +54,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
private final Class<T> myResourceType; private final Class<T> myResourceType;
private final FhirContext myFhirContext; private final FhirContext myFhirContext;
private final String myResourceName; private final String myResourceName;
private Map<String, TreeMap<Long, T>> myIdToVersionToResourceMap = new HashMap<>(); protected Map<String, TreeMap<Long, T>> myIdToVersionToResourceMap = new HashMap<>();
private long myNextId; private long myNextId;
/** /**

View File

@ -124,6 +124,11 @@
The <![CDATA[<code>ConceptMap</code>]] operation <![CDATA[<code>$translate</code>]]> has been The <![CDATA[<code>ConceptMap</code>]] operation <![CDATA[<code>$translate</code>]]> has been
implemented. implemented.
</action> </action>
<action type="add" issue="927">
HAPI-FHIR_CLI now includes two new commands: one for importing and populating a
<![CDATA[<code>ConceptMap</code>]]> resource from a CSV; and one for exporting a
<![CDATA[<code>ConceptMap</code>]]> resource to a CSV.
</action>
</release> </release>
<release version="3.3.0" date="2018-03-29"> <release version="3.3.0" date="2018-03-29">
<action type="add"> <action type="add">