From cd2af980641c16bddced30419a991aa578ef60fc Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Fri, 27 May 2016 08:56:36 -0400 Subject: [PATCH] Add method to upload SCT to JPA server --- .../src/main/java/ca/uhn/fhir/cli/App.java | 1 + .../fhir/cli/UploadTerminologyCommand.java | 159 ++++++++++++++++++ hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml | 4 + .../ca/uhn/fhir/jpa/demo/JpaServerDemo.java | 11 +- .../TerminologyUploaderProviderDstu3.java | 28 ++- .../dstu3/TerminologyProviderDstu3Test.java | 38 ++++- 6 files changed, 228 insertions(+), 13 deletions(-) create mode 100644 hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/UploadTerminologyCommand.java diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/App.java b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/App.java index 86eea43daf8..edd27fee83b 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/App.java +++ b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/App.java @@ -40,6 +40,7 @@ public class App { ourCommands.add(new ValidateCommand()); ourCommands.add(new ValidationDataUploader()); ourCommands.add(new WebsocketSubscribeCommand()); + ourCommands.add(new UploadTerminologyCommand()); Collections.sort(ourCommands); } diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/UploadTerminologyCommand.java b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/UploadTerminologyCommand.java new file mode 100644 index 00000000000..f758c163b1b --- /dev/null +++ b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/UploadTerminologyCommand.java @@ -0,0 +1,159 @@ +package ca.uhn.fhir.cli; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +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.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.time.DateUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.fusesource.jansi.Ansi; +import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport; +import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.dstu3.model.Attachment; +import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.dstu3.model.Bundle.BundleType; +import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Parameters; +import org.hl7.fhir.dstu3.model.Resource; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.omg.Dynamic.Parameter; + +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.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu2.resource.Bundle; +import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; +import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryRequest; +import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.valueset.BundleTypeEnum; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.rest.client.apache.GZipContentInterceptor; +import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import ca.uhn.fhir.util.BundleUtil; +import ca.uhn.fhir.util.ResourceReferenceInfo; +import ca.uhn.fhir.validation.FhirValidator; +import ca.uhn.fhir.validation.ValidationResult; + +public class UploadTerminologyCommand extends BaseCommand { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UploadTerminologyCommand.class); + + @Override + public String getCommandDescription() { + //@formatter:off + return "Uploads a terminology package (e.g. a SNOMED CT ZIP file) to a HAPI JPA server. " + + "Note that this command uses a custom operation that is only implemented on HAPI " + + "JPA servers that have been configured to accept it."; + //@formatter:on + } + + @Override + public String getCommandName() { + return "upload-terminology"; + } + + @Override + public Options getOptions() { + Options options = new Options(); + 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("u", "url", true, "The code system URL associated with this upload (e.g. " + IHapiTerminologyLoaderSvc.SCT_URL + ")"); + opt.setRequired(false); + options.addOption(opt); + + opt = new Option("d", "data", true, "Local *.zip containing file to use to upload"); + opt.setRequired(false); + options.addOption(opt); + + return options; + } + + @Override + public void run(CommandLine theCommandLine) throws Exception { + FhirContext ctx = getSpecVersionContext(theCommandLine); + + 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) { + throw new ParseException("Invalid target server specified, must begin with 'http' or 'file'"); + } + + String termUrl = theCommandLine.getOptionValue("u"); + if (isBlank(termUrl)) { + throw new ParseException("No URL provided"); + } + + String datafile = theCommandLine.getOptionValue("d"); + if (isBlank(datafile)) { + throw new ParseException("No data file provided"); + } + + IGenericClient client = super.newClient(ctx, targetServer); + IBaseParameters inputParameters; + if (ctx.getVersion().getVersion() == FhirVersionEnum.DSTU3) { + Parameters p = new Parameters(); + p.addParameter().setName("url").setValue(new StringType(termUrl)); + p.addParameter().setName("localfile").setValue(new StringType(datafile)); + inputParameters = p; + } else { + throw new ParseException("This command does not support FHIR version " + ctx.getVersion().getVersion()); + } + + ourLog.info("Beginning upload - This may take a while..."); + IBaseParameters response = client + .operation() + .onServer() + .named("upload-external-code-system") + .withParameters(inputParameters) + .execute(); + + ourLog.info("Upload complete!"); + } + +} diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml index 675bd694df2..71b6a859710 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml @@ -210,6 +210,10 @@ src/main/webapp/WEB-INF/web.xml true + + WEB-INF/lib/Saxon-HE-*, + WEB-INF/lib/hapi-* + diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java index d13c143a3a6..e05a1d1f30e 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.demo; +import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -19,6 +20,7 @@ import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu1; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; +import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.model.dstu2.resource.Bundle; @@ -74,13 +76,14 @@ public class JpaServerDemo extends RestfulServer { * The system provider implements non-resource-type methods, such as * transaction, and global history. */ - Object systemProvider; + List systemProvider = new ArrayList(); if (fhirVersion == FhirVersionEnum.DSTU1) { - systemProvider = myAppCtx.getBean("mySystemProviderDstu1", JpaSystemProviderDstu1.class); + systemProvider.add(myAppCtx.getBean("mySystemProviderDstu1", JpaSystemProviderDstu1.class)); } else if (fhirVersion == FhirVersionEnum.DSTU2) { - systemProvider = myAppCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class); + systemProvider.add(myAppCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class)); } else if (fhirVersion == FhirVersionEnum.DSTU3) { - systemProvider = myAppCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class); + systemProvider.add(myAppCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class)); + systemProvider.add(myAppCtx.getBean(TerminologyUploaderProviderDstu3.class)); } else { throw new IllegalStateException(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyUploaderProviderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyUploaderProviderDstu3.java index 6d97de56bdd..5eedc2f9cc2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyUploaderProviderDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyUploaderProviderDstu3.java @@ -1,12 +1,20 @@ package ca.uhn.fhir.jpa.provider.dstu3; import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.LockableFileWriter; import org.hl7.fhir.dstu3.model.Attachment; import org.hl7.fhir.dstu3.model.IntegerType; import org.hl7.fhir.dstu3.model.Parameters; +import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.UriType; import org.springframework.beans.factory.annotation.Autowired; @@ -16,9 +24,11 @@ import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc.UploadStatistics; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.method.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class TerminologyUploaderProviderDstu3 extends BaseJpaProvider { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyUploaderProviderDstu3.class); @Autowired private IHapiTerminologyLoaderSvc myTerminologyLoaderSvc; @@ -30,18 +40,28 @@ public class TerminologyUploaderProviderDstu3 extends BaseJpaProvider { public Parameters lookup( HttpServletRequest theServletRequest, @OperationParam(name="url", min=1) UriType theUrl, - @OperationParam(name="package", min=1) Attachment thePackage, + @OperationParam(name="package", min=0) Attachment thePackage, + @OperationParam(name="localfile", min=0) StringType theLocalFile, RequestDetails theRequestDetails ) { //@formatter:on startRequest(theServletRequest); try { - if (thePackage == null || thePackage.getData() == null || thePackage.getData().length == 0) { - throw new InvalidRequestException("Missing mandatory 'package' parameter, or package had no data"); + byte[] data; + if (theLocalFile != null && isNotBlank(theLocalFile.getValue())) { + ourLog.info("Reading in local file: {}", theLocalFile.getValue()); + try { + data = IOUtils.toByteArray(new FileInputStream(theLocalFile.getValue())); + } catch (IOException e) { + throw new InternalErrorException(e); + } + } else if (thePackage == null || thePackage.getData() == null || thePackage.getData().length == 0) { + throw new InvalidRequestException("No 'localfile' or 'package' parameter, or package had no data"); + } else { + data = thePackage.getData(); } - byte[] data = thePackage.getData(); String url = theUrl != null ? theUrl.getValueAsString() : null; url = defaultString(url); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyProviderDstu3Test.java index e7b214202ff..63d61feaa22 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyProviderDstu3Test.java @@ -1,9 +1,13 @@ package ca.uhn.fhir.jpa.provider.dstu3; import static org.hamcrest.Matchers.greaterThan; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -12,12 +16,10 @@ import java.util.zip.ZipOutputStream; import org.apache.commons.io.IOUtils; import org.hl7.fhir.dstu3.model.Attachment; -import org.hl7.fhir.dstu3.model.BooleanType; -import org.hl7.fhir.dstu3.model.CodeType; import org.hl7.fhir.dstu3.model.IntegerType; import org.hl7.fhir.dstu3.model.Parameters; +import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.UriType; -import org.hl7.fhir.dstu3.model.ValueSet; import org.junit.AfterClass; import org.junit.Test; @@ -56,6 +58,32 @@ public class TerminologyProviderDstu3Test extends BaseResourceProviderDstu3Test assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1)); } + @Test + public void testUploadSctLocalFile() throws Exception { + byte[] packageBytes = createSctZip(); + File tempFile = File.createTempFile("tmp", ".zip"); + tempFile.deleteOnExit(); + + FileOutputStream fos = new FileOutputStream(tempFile); + fos.write(packageBytes); + fos.close(); + + //@formatter:off + Parameters respParam = ourClient + .operation() + .onServer() + .named("upload-external-code-system") + .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URL)) + .andParameter("localfile", new StringType(tempFile.getAbsolutePath())) + .execute(); + //@formatter:on + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1)); + } + @Test public void testUploadInvalidUrl() throws Exception { byte[] packageBytes = createSctZip(); @@ -107,7 +135,7 @@ public class TerminologyProviderDstu3Test extends BaseResourceProviderDstu3Test .execute(); fail(); } catch (InvalidRequestException e) { - assertEquals("HTTP 400 Bad Request: Missing mandatory 'package' parameter, or package had no data", e.getMessage()); + assertEquals("HTTP 400 Bad Request: No 'localfile' or 'package' parameter, or package had no data", e.getMessage()); } //@formatter:on }