package ca.uhn.fhir.cli; import static org.apache.commons.lang3.StringUtils.isBlank; import java.io.IOException; import java.util.Collections; import java.util.Comparator; 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.IOUtils; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.CapabilityStatement; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.instance.model.api.IIdType; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.model.dstu2.resource.StructureDefinition; import ca.uhn.fhir.model.dstu2.resource.ValueSet; import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; public class ValidationDataUploader extends BaseCommand { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidationDataUploader.class); @Override public String getCommandDescription() { return "Uploads the conformance resources (StructureDefinition and ValueSet) from the official FHIR definitions."; } @Override public String getCommandName() { return "upload-definitions"; } @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); return options; } @Override public void run(CommandLine theCommandLine) throws ParseException { String targetServer = theCommandLine.getOptionValue("t"); if (isBlank(targetServer)) { throw new ParseException("No target server (-t) specified"); } else if (targetServer.startsWith("http") == false) { throw new ParseException("Invalid target server specified, must begin with 'http'"); } FhirContext ctx = getSpecVersionContext(theCommandLine); if (ctx.getVersion().getVersion() == FhirVersionEnum.DSTU2) { uploadDefinitionsDstu2(targetServer, ctx); } else if (ctx.getVersion().getVersion() == FhirVersionEnum.DSTU3){ uploadDefinitionsDstu3(targetServer, ctx); } } private void uploadDefinitionsDstu2(String targetServer, FhirContext ctx) throws CommandFailureException { IGenericClient client = newClient(ctx, targetServer); ourLog.info("Uploading definitions to server: " + targetServer); long start = System.currentTimeMillis(); String vsContents; try { ctx.getVersion().getPathToSchemaDefinitions(); vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/instance/model/valueset/"+"valuesets.xml"), "UTF-8"); } catch (IOException e) { throw new CommandFailureException(e.toString()); } Bundle bundle = ctx.newXmlParser().parseResource(Bundle.class, vsContents); int total = bundle.getEntry().size(); int count = 1; for (Entry i : bundle.getEntry()) { ValueSet next = (ValueSet) i.getResource(); next.setId(next.getIdElement().toUnqualifiedVersionless()); ourLog.info("Uploading ValueSet {}/{} : {}", new Object[] { count, total, next.getIdElement().getValue() }); client.update().resource(next).execute(); count++; } try { vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/instance/model/valueset/"+"v3-codesystems.xml"), "UTF-8"); } catch (IOException e) { throw new CommandFailureException(e.toString()); } bundle = ctx.newXmlParser().parseResource(Bundle.class, vsContents); total = bundle.getEntry().size(); count = 1; for (Entry i : bundle.getEntry()) { ValueSet next = (ValueSet) i.getResource(); next.setId(next.getIdElement().toUnqualifiedVersionless()); ourLog.info("Uploading v3-codesystems ValueSet {}/{} : {}", new Object[] { count, total, next.getIdElement().getValue() }); client.update().resource(next).execute(); count++; } try { vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/instance/model/valueset/"+"v2-tables.xml"), "UTF-8"); } catch (IOException e) { throw new CommandFailureException(e.toString()); } bundle = ctx.newXmlParser().parseResource(Bundle.class, vsContents); total = bundle.getEntry().size(); count = 1; for (Entry i : bundle.getEntry()) { ValueSet next = (ValueSet) i.getResource(); next.setId(next.getIdElement().toUnqualifiedVersionless()); ourLog.info("Uploading v2-tables ValueSet {}/{} : {}", new Object[] { count, total, next.getIdElement().getValue() }); client.update().resource(next).execute(); count++; } ourLog.info("Finished uploading ValueSets"); ResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver(); Resource[] mappingLocations; try { mappingLocations = patternResolver.getResources("classpath*:org/hl7/fhir/instance/model/profile/"+"*.profile.xml"); } catch (IOException e) { throw new CommandFailureException(e.toString()); } total = mappingLocations.length; count = 1; for (Resource i : mappingLocations) { StructureDefinition next; try { next = ctx.newXmlParser().parseResource(StructureDefinition.class, IOUtils.toString(i.getInputStream(), "UTF-8")); } catch (Exception e) { throw new CommandFailureException(e.toString()); } next.setId(next.getIdElement().toUnqualifiedVersionless()); ourLog.info("Uploading StructureDefinition {}/{} : {}", new Object[] { count, total, next.getIdElement().getValue() }); try { client.update().resource(next).execute(); } catch (Exception e) { ourLog.warn("Failed to upload {} - {}", next.getIdElement().getValue(), e.getMessage()); } count++; } ourLog.info("Finished uploading ValueSets"); long delay = System.currentTimeMillis() - start; ourLog.info("Finished uploading definitions to server (took {} ms)", delay); } private void uploadDefinitionsDstu3(String targetServer, FhirContext ctx) throws CommandFailureException { IGenericClient client = newClient(ctx, targetServer); ourLog.info("Uploading definitions to server: " + targetServer); long start = System.currentTimeMillis(); int total = 0; int count = 0; org.hl7.fhir.dstu3.model.Bundle bundle; String vsContents; try { ctx.getVersion().getPathToSchemaDefinitions(); vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/instance/model/dstu3/valueset/"+"valuesets.xml"), "UTF-8"); } catch (IOException e) { throw new CommandFailureException(e.toString()); } bundle = ctx.newXmlParser().parseResource(org.hl7.fhir.dstu3.model.Bundle.class, vsContents); total = bundle.getEntry().size(); count = 1; for (BundleEntryComponent i : bundle.getEntry()) { org.hl7.fhir.dstu3.model.Resource next = i.getResource(); next.setId(next.getIdElement().toUnqualifiedVersionless()); int bytes = ctx.newXmlParser().encodeResourceToString(next).length(); ourLog.info("Uploading ValueSet {}/{} : {} ({} bytes}", new Object[] { count, total, next.getIdElement().getValue(), bytes }); try { IIdType id = client.update().resource(next).execute().getId(); ourLog.info(" - Got ID: {}", id.getValue()); } catch (UnprocessableEntityException e) { ourLog.warn("UnprocessableEntityException: " + e.toString()); } count++; } try { vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/instance/model/dstu3/valueset/"+"v3-codesystems.xml"), "UTF-8"); } catch (IOException e) { throw new CommandFailureException(e.toString()); } bundle = ctx.newXmlParser().parseResource(org.hl7.fhir.dstu3.model.Bundle.class, vsContents); total = bundle.getEntry().size(); count = 1; for (BundleEntryComponent i : bundle.getEntry()) { org.hl7.fhir.dstu3.model.Resource next = i.getResource(); next.setId(next.getIdElement().toUnqualifiedVersionless()); ourLog.info("Uploading v3-codesystems ValueSet {}/{} : {}", new Object[] { count, total, next.getIdElement().getValue() }); client.update().resource(next).execute(); count++; } try { vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/instance/model/dstu3/valueset/"+"v2-tables.xml"), "UTF-8"); } catch (IOException e) { throw new CommandFailureException(e.toString()); } bundle = ctx.newXmlParser().parseResource(org.hl7.fhir.dstu3.model.Bundle.class, vsContents); total = bundle.getEntry().size(); count = 1; for (BundleEntryComponent i : bundle.getEntry()) { org.hl7.fhir.dstu3.model.Resource next = i.getResource(); if (next.getIdElement().isIdPartValidLong()) { next.setIdElement(new IdType("v2-"+ next.getIdElement().getIdPart())); } next.setId(next.getIdElement().toUnqualifiedVersionless()); ourLog.info("Uploading v2-tables ValueSet {}/{} : {}", new Object[] { count, total, next.getIdElement().getValue() }); client.update().resource(next).execute(); count++; } ourLog.info("Finished uploading ValueSets"); uploadDstu3Profiles(ctx, client, "profiles-resources"); uploadDstu3Profiles(ctx, client, "profiles-types"); uploadDstu3Profiles(ctx, client, "profiles-others"); ourLog.info("Finished uploading ValueSets"); long delay = System.currentTimeMillis() - start; ourLog.info("Finished uploading definitions to server (took {} ms)", delay); } private void uploadDstu3Profiles(FhirContext ctx, IGenericClient client, String name) throws CommandFailureException { int total; int count; org.hl7.fhir.dstu3.model.Bundle bundle; ourLog.info("Uploading " + name); String vsContents; try { vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/instance/model/dstu3/profile/" + name + ".xml"), "UTF-8"); } catch (IOException e) { throw new CommandFailureException(e.toString()); } bundle = ctx.newXmlParser().parseResource(org.hl7.fhir.dstu3.model.Bundle.class, vsContents); total = bundle.getEntry().size(); count = 1; Collections.sort(bundle.getEntry(), new Comparator<BundleEntryComponent>() { @Override public int compare(BundleEntryComponent theO1, BundleEntryComponent theO2) { if (theO1.getResource() == null && theO2.getResource() == null) { return 0; } if (theO1.getResource() == null) { return 1; } if (theO2.getResource() == null) { return -1; } // StructureDefinition, then OperationDefinition, then CompartmentDefinition return theO2.getResource().getClass().getName().compareTo(theO1.getResource().getClass().getName()); }}); for (BundleEntryComponent i : bundle.getEntry()) { org.hl7.fhir.dstu3.model.Resource next = i.getResource(); next.setId(next.getIdElement().toUnqualifiedVersionless()); if (next instanceof CapabilityStatement) { continue; } ourLog.info("Uploading {} StructureDefinition {}/{} : {}", new Object[] { name, count, total, next.getIdElement().getValue() }); client.update().resource(next).execute(); count++; } } }