All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hl7.fhir.validation.cli.services.ValidationService Maven / Gradle / Ivy

The newest version!
package org.hl7.fhir.validation.cli.services;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.Nonnull;

import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
import org.hl7.fhir.r5.context.ContextUtilities;
import org.hl7.fhir.r5.context.SimpleWorkerContext;
import org.hl7.fhir.r5.context.SystemOutLoggingService;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.LanguageUtils;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.elementmodel.ValidatedFragment;
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
import org.hl7.fhir.r5.formats.IParser;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.liquid.BaseTableWrapper;
import org.hl7.fhir.r5.liquid.GlobalObject.GlobalObjectRandomFunction;
import org.hl7.fhir.r5.liquid.LiquidEngine;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.ConceptMap;
import org.hl7.fhir.r5.model.DateTimeType;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureMap;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.profilemodel.gen.PECodeGenerator;
import org.hl7.fhir.r5.profilemodel.gen.PECodeGenerator.ExtensionPolicy;
import org.hl7.fhir.r5.renderers.spreadsheets.CodeSystemSpreadsheetGenerator;
import org.hl7.fhir.r5.renderers.spreadsheets.ConceptMapSpreadsheetGenerator;
import org.hl7.fhir.r5.renderers.spreadsheets.StructureDefinitionSpreadsheetGenerator;
import org.hl7.fhir.r5.renderers.spreadsheets.ValueSetSpreadsheetGenerator;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
import org.hl7.fhir.r5.terminologies.client.TerminologyClientManager.InternalLogEvent;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache;
import org.hl7.fhir.r5.testfactory.TestDataFactory;
import org.hl7.fhir.r5.testfactory.TestDataHostServices;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.hl7.fhir.utilities.FhirPublication;
import org.hl7.fhir.utilities.SystemExitManager;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.TimeTracker;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
import org.hl7.fhir.utilities.i18n.JsonLangFileProducer;
import org.hl7.fhir.utilities.i18n.LanguageFileProducer.LanguageProducerLanguageSession;
import org.hl7.fhir.utilities.i18n.LanguageFileProducer.LanguageProducerSession;
import org.hl7.fhir.utilities.i18n.LanguageFileProducer.TranslationUnit;
import org.hl7.fhir.utilities.i18n.PoGetTextProducer;
import org.hl7.fhir.utilities.i18n.XLIFFProducer;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.parser.JsonParser;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.validation.IgLoader;
import org.hl7.fhir.validation.ResourceChecker;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.ValidationRecord;
import org.hl7.fhir.validation.ValidatorUtils;
import org.hl7.fhir.validation.ValidatorUtils.SourceFile;
import org.hl7.fhir.validation.cli.model.CliContext;
import org.hl7.fhir.validation.cli.model.FileInfo;
import org.hl7.fhir.validation.cli.model.ValidatedFragments;
import org.hl7.fhir.validation.cli.model.ValidationOutcome;
import org.hl7.fhir.validation.cli.model.ValidationRequest;
import org.hl7.fhir.validation.cli.model.ValidationResponse;
import org.hl7.fhir.validation.cli.renderers.CSVRenderer;
import org.hl7.fhir.validation.cli.renderers.CompactRenderer;
import org.hl7.fhir.validation.cli.renderers.DefaultRenderer;
import org.hl7.fhir.validation.cli.renderers.ESLintCompactRenderer;
import org.hl7.fhir.validation.cli.renderers.NativeRenderer;
import org.hl7.fhir.validation.cli.renderers.ValidationOutputRenderer;
import org.hl7.fhir.validation.cli.utils.Common;
import org.hl7.fhir.validation.cli.utils.EngineMode;
import org.hl7.fhir.validation.cli.utils.VersionSourceInformation;
import org.hl7.fhir.validation.instance.advisor.BasePolicyAdvisorForFullValidation;
import org.hl7.fhir.validation.instance.advisor.JsonDrivenPolicyAdvisor;
import org.hl7.fhir.validation.instance.advisor.TextDrivenPolicyAdvisor;

public class ValidationService {

  private final SessionCache sessionCache;
  private String runDate;

  private final Map baseEngines = new ConcurrentHashMap<>();

  public void putBaseEngine(String key, CliContext cliContext) throws IOException, URISyntaxException {
    if (cliContext.getSv() == null) {
      throw new IllegalArgumentException("Cannot create a base engine without an explicit version");
    }
    String definitions = VersionUtilities.packageForVersion(cliContext.getSv()) + "#" + VersionUtilities.getCurrentVersion(cliContext.getSv());

    ValidationEngine baseEngine = buildValidationEngine(cliContext, definitions, new TimeTracker());
    baseEngines.put(key, baseEngine);
  }

  public ValidationEngine getBaseEngine(String key) {
    return baseEngines.get(key);
  }

  public Set getBaseEngineKeys() { return baseEngines.keySet(); }

  public boolean hasBaseEngineForKey(String key) { return baseEngines.containsKey(key); }

  public ValidationService() {
    sessionCache = new PassiveExpiringSessionCache();
    runDate = new SimpleDateFormat("hh:mm:ss", new Locale("en", "US")).format(new Date());
  }



  public ValidationService(SessionCache cache) {
    this.sessionCache = cache;
  }

  public ValidationResponse validateSources(ValidationRequest request) throws Exception {

    TimeTracker timeTracker = new TimeTracker();
    String sessionId = initializeValidator(request.getCliContext(), null, timeTracker, request.sessionId);
    ValidationEngine validationEngine = sessionCache.fetchSessionValidatorEngine(sessionId);

    /* Cached validation engines already have expensive setup like loading definitions complete. But it wouldn't make
       sense to rebuild a whole engine to change the language, so we manually change it here.
     */
    validationEngine.setLanguage(request.getCliContext().getLang());
    validationEngine.setLocale(request.getCliContext().getLocale());
    if (request.getCliContext().getProfiles().size() > 0) {
      System.out.println("  .. validate " + request.listSourceFiles() + " against " + request.getCliContext().getProfiles().toString());
    } else {
      System.out.println("  .. validate " + request.listSourceFiles());
    }

    ValidationResponse response = new ValidationResponse().setSessionId(sessionId).setValidationTimes(new HashMap<>());

    for (FileInfo fileToValidate : request.getFilesToValidate()) {
      if (fileToValidate.getFileType() == null) {
        Manager.FhirFormat format = ResourceChecker.checkIsResource(validationEngine.getContext(),
          false,
          fileToValidate.getFileContent().getBytes(),
          fileToValidate.getFileName(),
          false);
        if (format != null) {
          fileToValidate.setFileType(format.getExtension());
        }
      }

      List messages = new ArrayList<>();

      if (fileToValidate.getFileType() == null) {
          ValidationOutcome outcome = getValidationOutcomeForUnknownFileFormat(
            new FileInfo(fileToValidate.getFileName(), fileToValidate.getFileContent(), null));
          response.addOutcome(outcome);
      } else {
        ValidatedFragments validatedFragments = validationEngine.validateAsFragments(fileToValidate.getFileContent().getBytes(), Manager.FhirFormat.getFhirFormat(fileToValidate.getFileType()),
          request.getCliContext().getProfiles(), messages);

        List validationOutcomes = getValidationOutcomesFromValidatedFragments(fileToValidate, validatedFragments);
        for (ValidationOutcome validationOutcome : validationOutcomes) {
          response.addOutcome(validationOutcome);
        }

        if (request.getCliContext().isShowTimes()) {
          response.getValidationTimes().put(fileToValidate.getFileName(), validatedFragments.getValidationTime());
        }
        
      }
    }

    System.out.println("  Max Memory: "+Runtime.getRuntime().maxMemory());
    return response;
  }

  private List getValidationOutcomesFromValidatedFragments(FileInfo fileToValidate, ValidatedFragments validatedFragments) {
    List outcomes = new LinkedList<>();
    if (validatedFragments.getValidatedFragments().size() == 1 && !validatedFragments.getValidatedFragments().get(0).isDerivedContent()) {
      ValidatedFragment validatedFragment = validatedFragments.getValidatedFragments().get(0);
      ValidationOutcome outcome = new ValidationOutcome();
      FileInfo fileInfo = new FileInfo(
        fileToValidate.getFileName(),
        new String(validatedFragment.getContent()),
        validatedFragment.getExtension());
      outcome.setMessages(validatedFragment.getErrors());
      outcome.setFileInfo(fileInfo);
      outcomes.add(outcome);
    } else {
      for (ValidatedFragment validatedFragment : validatedFragments.getValidatedFragments()) {
        ValidationOutcome outcome = new ValidationOutcome();
        FileInfo fileInfo = new FileInfo(
          validatedFragment.getFilename(),
          new String(validatedFragment.getContent()),
          validatedFragment.getExtension());
        outcome.setMessages(validatedFragment.getErrors());
        outcome.setFileInfo(fileInfo);
        outcomes.add(outcome);
      }
    }
    return outcomes;
  }

  private ValidationOutcome getValidationOutcomeForUnknownFileFormat(FileInfo fileInfo) {
    ValidationOutcome outcome = new ValidationOutcome();

    List errorList = new ArrayList<>() {{
      add(new ValidationMessage().setType(ValidationMessage.IssueType.EXCEPTION).setLevel(ValidationMessage.IssueSeverity.FATAL).setMessage("Unable to infer format from file. Please check that your file is in a valid FHIR format."));

    } };
    outcome.setMessages(errorList);
    outcome.setFileInfo(fileInfo);
    return outcome;
  }

  public VersionSourceInformation scanForVersions(CliContext cliContext) throws IOException {
    VersionSourceInformation versions = new VersionSourceInformation();
    IgLoader igLoader = new IgLoader(
      new FilesystemPackageCacheManager.Builder().build(),
      new SimpleWorkerContext.SimpleWorkerContextBuilder().fromNothing(),
      null);
    for (String src : cliContext.getIgs()) {
      igLoader.scanForIgVersion(src, cliContext.isRecursive(), versions);
    }
    igLoader.scanForVersions(cliContext.getSources(), versions);
    return versions;
  }

  public void validateSources(CliContext cliContext, ValidationEngine validator, ValidatorWatchMode watch, int watchScanDelay, int watchSettleTime) throws Exception {
    if (cliContext.getProfiles().size() > 0) {
      System.out.println("  Profiles: " + cliContext.getProfiles());
    }
    IgLoader igLoader = new IgLoader(validator.getPcm(), validator.getContext(), validator.getVersion());
        
    List records = new ArrayList<>();
    List refs = new ArrayList<>();

    int ec = 0;
    boolean first = true;

    do {
      long start = System.currentTimeMillis();
      Resource r = validator.validate(cliContext.getSources(), cliContext.getProfiles(), refs, records, igLoader, watch == ValidatorWatchMode.ALL, watchSettleTime, first);
      first = false;
      boolean statusNeeded = false;
      if (r != null) {
        statusNeeded = true;
        MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
        System.out.println("Done. " + validator.getContext().clock().report()+". Memory = "+Utilities.describeSize(mbean.getHeapMemoryUsage().getUsed()+mbean.getNonHeapMemoryUsage().getUsed()));
        System.out.println();

        PrintStream dst = null;
        ValidationOutputRenderer renderer = makeValidationOutputRenderer(cliContext);
        renderer.setCrumbTrails(validator.isCrumbTrails());
        renderer.setShowMessageIds(validator.isShowMessageIds());
        renderer.setRunDate(runDate);
        if (renderer.isSingleFile()) {
          if (cliContext.getOutput() == null) {
            dst = System.out;
          } else {
            dst = new PrintStream(ManagedFileAccess.outStream(Utilities.path(cliContext.getOutput())));
          }
          renderer.setOutput(dst);
        } else {
          File dir = ManagedFileAccess.file(cliContext.getOutput());
          if (!dir.isDirectory()) {
            throw new Error("The output location "+dir.getAbsolutePath()+" must be an existing directory for the output style "+renderer.getStyleCode());
          }
          renderer.setFolder(dir);
        }

        if (r instanceof Bundle) {
          if (renderer.handlesBundleDirectly()) {
            renderer.render((Bundle) r);
          } else {
            renderer.start(((Bundle) r).getEntry().size() > 1);
            for (Bundle.BundleEntryComponent e : ((Bundle) r).getEntry()) {
              OperationOutcome op = (OperationOutcome) e.getResource();
              ec = ec + countErrors(op);
              renderer.render(op);
            }
            renderer.finish();
          }
        } else if (r == null) {
          ec = ec + 1;
          System.out.println("No output from validation - nothing to validate");
        } else {
          renderer.start(false);
          OperationOutcome op = (OperationOutcome) r;
          ec = countErrors(op);
          renderer.render((OperationOutcome) r);
          renderer.finish();
        }

        if (cliContext.getOutput() != null && dst != null) {
          dst.close();
        }

        if (cliContext.getHtmlOutput() != null) {
          String html = new HTMLOutputGenerator(records).generate(System.currentTimeMillis() - start);
          TextFile.stringToFile(html, cliContext.getHtmlOutput());
          System.out.println("HTML Summary in " + cliContext.getHtmlOutput());
        }

        if (cliContext.isShowTerminologyRouting()) {
          System.out.println("");
          System.out.println("Terminology Routing Dump ---------------------------------------");
          if (validator.getContext().getTxClientManager().getInternalLog().isEmpty()) {
            System.out.println("(nothing happened)");            
          } else {
            for (InternalLogEvent log : validator.getContext().getTxClientManager().getInternalLog()) {
              System.out.println(log.getMessage()+" -> "+log.getServer()+" (for VS "+log.getVs()+" with systems '"+log.getSystems()+"', choices = '"+log.getChoices()+"')");
            }
          }
          validator.getContext().getTxClientManager().getInternalLog().clear();
        }
      }
      if (watch != ValidatorWatchMode.NONE) {
        if (statusNeeded) {
          System.out.println("Watching for changes ("+Integer.toString(watchScanDelay)+"ms cycle)");
        }
        Thread.sleep(watchScanDelay);
      }
    } while (watch != ValidatorWatchMode.NONE);
    if (ec > 0) {
      SystemExitManager.setError(1);
    }
  }

  private int countErrors(OperationOutcome oo) {
    int error = 0;
    for (OperationOutcome.OperationOutcomeIssueComponent issue : oo.getIssue()) {
      if (issue.getSeverity() == OperationOutcome.IssueSeverity.FATAL || issue.getSeverity() == OperationOutcome.IssueSeverity.ERROR)
        error++;
    }
    return error;
  }

  private ValidationOutputRenderer makeValidationOutputRenderer(CliContext cliContext) {
    String style = cliContext.getOutputStyle();
    // adding to this list?
    // Must document the option at https://confluence.hl7.org/display/FHIR/Using+the+FHIR+Validator#UsingtheFHIRValidator-ManagingOutput
    // if you're going to make a PR, document the link where the outputstyle is documented, along with a sentence that describes it, in the PR notes
    if (Utilities.noString(style)) {
      if (cliContext.getOutput() == null) {
        return new DefaultRenderer();
      } else if (cliContext.getOutput().endsWith(".json")) {
        return new NativeRenderer(FhirFormat.JSON);
      } else {
        return new NativeRenderer(FhirFormat.XML);
      }
    } else if (Utilities.existsInList(style, "eslint-compact")) {
      return new ESLintCompactRenderer();
    } else if (Utilities.existsInList(style, "compact-split")) {
      return new CompactRenderer(true);
    } else if (Utilities.existsInList(style, "compact")) {
      return new CompactRenderer(false);
    } else if (Utilities.existsInList(style, "csv")) {
      return new CSVRenderer();
    } else if (Utilities.existsInList(style, "xml")) {
      return new NativeRenderer(FhirFormat.XML);
    } else if (Utilities.existsInList(style, "json")) {
      return new NativeRenderer(FhirFormat.JSON);
    } else {
      System.out.println("Unknown output style '"+style+"'");
      return new DefaultRenderer();
    }
  }

  public void convertSources(CliContext cliContext, ValidationEngine validator) throws Exception {

      if (!((cliContext.getOutput() == null) ^ (cliContext.getOutputSuffix() == null))) {
        throw new Exception("Convert requires one of {-output, -outputSuffix} parameter to be set");
      }

      List sources = cliContext.getSources();
      if ((sources.size() == 1) && (cliContext.getOutput() != null)) {
        System.out.println(" ...convert");
        validator.convert(sources.get(0), cliContext.getOutput());
      } else {
        if (cliContext.getOutputSuffix() == null) {
          throw new Exception("Converting multiple/wildcard sources requires a -outputSuffix parameter to be set");
        }
        for (int i = 0; i < sources.size(); i++) {
            String output = sources.get(i) + "." + cliContext.getOutputSuffix();
            validator.convert(sources.get(i), output);
            System.out.println(" ...convert [" + i +  "] (" + sources.get(i) + " to " + output + ")");
        }
      }
  }

  public void evaluateFhirpath(CliContext cliContext, ValidationEngine validator) throws Exception {
    System.out.println(" ...evaluating " + cliContext.getFhirpath());
    System.out.println(validator.evaluateFhirPath(cliContext.getSources().get(0), cliContext.getFhirpath()));
  }

  public void generateSnapshot(CliContext cliContext, ValidationEngine validator) throws Exception {

      if (!((cliContext.getOutput() == null) ^ (cliContext.getOutputSuffix() == null))) {
        throw new Exception("Snapshot generation requires one of {-output, -outputSuffix} parameter to be set");
      }

      List sources = cliContext.getSources();
      if ((sources.size() == 1) && (cliContext.getOutput() != null)) {
        StructureDefinition r = validator.snapshot(sources.get(0), cliContext.getSv());
        System.out.println(" ...generated snapshot successfully");
        validator.handleOutput(r, cliContext.getOutput(), cliContext.getSv());
      } else {
        if (cliContext.getOutputSuffix() == null) {
          throw new Exception("Snapshot generation for multiple/wildcard sources requires a -outputSuffix parameter to be set");
        }
        for (int i = 0; i < sources.size(); i++) {
          StructureDefinition r = validator.snapshot(sources.get(i), cliContext.getSv());
          String output = sources.get(i) + "." + cliContext.getOutputSuffix();
          validator.handleOutput(r, output, cliContext.getSv());
          System.out.println(" ...generated snapshot [" + i +  "] successfully (" + sources.get(i) + " to " + output + ")");
        }
      }

  }

  public void generateNarrative(CliContext cliContext, ValidationEngine validator) throws Exception {
    Resource r = validator.generate(cliContext.getSources().get(0), cliContext.getSv());
    System.out.println(" ...generated narrative successfully");
    if (cliContext.getOutput() != null) {
      validator.handleOutput(r, cliContext.getOutput(), cliContext.getSv());
    }
  }

  public void transform(CliContext cliContext, ValidationEngine validator) throws Exception {
    if (cliContext.getSources().size() > 1)
      throw new Exception("Can only have one source when doing a transform (found " + cliContext.getSources() + ")");
    if (cliContext.getTxServer() == null)
      throw new Exception("Must provide a terminology server when doing a transform");
    if (cliContext.getMap() == null)
      throw new Exception("Must provide a map when doing a transform");
    try {
      ContextUtilities cu = new ContextUtilities(validator.getContext());
      List structures =  cu.allStructures();
      for (StructureDefinition sd : structures) {
        if (!sd.hasSnapshot()) {
          cu.generateSnapshot(sd);
        }
      }
      validator.setMapLog(cliContext.getMapLog());
      org.hl7.fhir.r5.elementmodel.Element r = validator.transform(cliContext.getSources().get(0), cliContext.getMap());
      System.out.println(" ...success");
      if (cliContext.getOutput() != null) {
        FileOutputStream s = ManagedFileAccess.outStream(cliContext.getOutput());
        if (cliContext.getOutput() != null && cliContext.getOutput().endsWith(".json"))
          new org.hl7.fhir.r5.elementmodel.JsonParser(validator.getContext()).compose(r, s, IParser.OutputStyle.PRETTY, null);
        else
          new org.hl7.fhir.r5.elementmodel.XmlParser(validator.getContext()).compose(r, s, IParser.OutputStyle.PRETTY, null);
        s.close();
      }
    } catch (Exception e) {
      System.out.println(" ...Failure: " + e.getMessage());
      e.printStackTrace();
    }
  }

  public void compile(CliContext cliContext, ValidationEngine validator) throws Exception {
    if (cliContext.getSources().size() > 0)
      throw new Exception("Cannot specify sources when compling transform (found " + cliContext.getSources() + ")");
    if (cliContext.getMap() == null)
      throw new Exception("Must provide a map when compiling a transform");
    if (cliContext.getOutput() == null)
      throw new Exception("Must provide an output name when compiling a transform");
    try {
      ContextUtilities cu = new ContextUtilities(validator.getContext());
      List structures = cu.allStructures();
      for (StructureDefinition sd : structures) {
        if (!sd.hasSnapshot()) {
          cu.generateSnapshot(sd);
        }
      }
      validator.setMapLog(cliContext.getMapLog());
      StructureMap map = validator.compile(cliContext.getMap());
      if (map == null)
        throw new Exception("Unable to locate map " + cliContext.getMap());
      validator.handleOutput(map, cliContext.getOutput(), validator.getVersion());
      System.out.println(" ...success");
    } catch (Exception e) {
      System.out.println(" ...Failure: " + e.getMessage());
      e.printStackTrace();
    }
  }

  public void transformVersion(CliContext cliContext, ValidationEngine validator) throws Exception {
    if (cliContext.getSources().size() > 1) {
      throw new Exception("Can only have one source when converting versions (found " + cliContext.getSources() + ")");
    }
    if (cliContext.getTargetVer() == null) {
      throw new Exception("Must provide a map when converting versions");
    }
    if (cliContext.getOutput() == null) {
      throw new Exception("Must nominate an output when converting versions");
    }
    try {
      if (cliContext.getMapLog() != null) {
        validator.setMapLog(cliContext.getMapLog());
      }
      byte[] r = validator.transformVersion(cliContext.getSources().get(0), cliContext.getTargetVer(), cliContext.getOutput().endsWith(".json") ? Manager.FhirFormat.JSON : Manager.FhirFormat.XML, cliContext.getCanDoNative());
      System.out.println(" ...success");
      TextFile.bytesToFile(r, cliContext.getOutput());
    } catch (Exception e) {
      System.out.println(" ...Failure: " + e.getMessage());
      e.printStackTrace();
    }
  }

  public ValidationEngine initializeValidator(CliContext cliContext, String definitions, TimeTracker tt) throws Exception {
    return sessionCache.fetchSessionValidatorEngine(initializeValidator(cliContext, definitions, tt, null));
  }

  public String initializeValidator(CliContext cliContext, String definitions, TimeTracker tt, String sessionId) throws Exception {
    tt.milestone();

    if (!sessionCache.sessionExists(sessionId)) {
      if (sessionId != null) {
        System.out.println("No such cached session exists for session id " + sessionId + ", re-instantiating validator.");
      }
      sessionCache.cleanUp();
      if (cliContext.getSv() == null) {
        String sv = determineVersion(cliContext);
        cliContext.setSv(sv);
      }
      final String engineDefinitions = definitions != null ? definitions : VersionUtilities.packageForVersion(cliContext.getSv()) + "#" + VersionUtilities.getCurrentVersion(cliContext.getSv());

      ValidationEngine validationEngine = getValidationEngineFromCliContext(cliContext, engineDefinitions, tt);
      sessionId = sessionCache.cacheSession(validationEngine);
      System.out.println("Cached new session. Cache size = " + sessionCache.getSessionIds().size());

    } else {
      System.out.println("Cached session exists for session id " + sessionId + ", returning stored validator session id. Cache size = " + sessionCache.getSessionIds().size());
    }
    return sessionId;
  }

  private ValidationEngine getValidationEngineFromCliContext(CliContext cliContext, String definitions, TimeTracker tt) throws Exception {
    ValidationEngine validationEngine;
    if (cliContext.getBaseEngine() != null && hasBaseEngineForKey(cliContext.getBaseEngine())) {
      validationEngine = new ValidationEngine(getBaseEngine(cliContext.getBaseEngine()));
    } else {
      if (definitions == null) {
        throw new IllegalArgumentException("Cannot create a validator engine (definitions == null)");
      }
      validationEngine = buildValidationEngine(cliContext, definitions, tt);
    }
    return validationEngine;
  }

  protected ValidationEngine.ValidationEngineBuilder getValidationEngineBuilder() {
    return new ValidationEngine.ValidationEngineBuilder();
  }

  @Nonnull
  protected ValidationEngine buildValidationEngine(CliContext cliContext, String definitions, TimeTracker timeTracker) throws IOException, URISyntaxException {
    System.out.print("  Load FHIR v" + cliContext.getSv() + " from " + definitions);
    ValidationEngine validationEngine = getValidationEngineBuilder().withTHO(false).withVersion(cliContext.getSv()).withTimeTracker(timeTracker).withUserAgent(Common.getValidatorUserAgent()).fromSource(definitions);

    System.out.println(" - " + validationEngine.getContext().countAllCaches() + " resources (" + timeTracker.milestone() + ")");

    loadIgsAndExtensions(validationEngine, cliContext, timeTracker);
    if (cliContext.getTxCache() != null) {
      TerminologyCache cache = new TerminologyCache(new Object(), cliContext.getTxCache());
      validationEngine.getContext().initTxCache(cache);
    }
    if (validationEngine.getContext().getTxCache() == null || validationEngine.getContext().getTxCache().getFolder() == null) {
      System.out.println("  No Terminology Cache");      
    } else {
      System.out.println("  Terminology Cache at "+validationEngine.getContext().getTxCache().getFolder());
      if (cliContext.isClearTxCache()) {
        System.out.println("  Terminology Cache Entries Cleaned out");
        validationEngine.getContext().getTxCache().clear();
      }
    }
    System.out.print("  Get set... ");
    validationEngine.setQuestionnaireMode(cliContext.getQuestionnaireMode());
    validationEngine.setLevel(cliContext.getLevel());
    validationEngine.setDoNative(cliContext.isDoNative());
    validationEngine.setHintAboutNonMustSupport(cliContext.isHintAboutNonMustSupport());
    for (String s : cliContext.getExtensions()) {
      if ("any".equals(s)) {
        validationEngine.setAnyExtensionsAllowed(true);
      } else {
        validationEngine.getExtensionDomains().add(s);
      }
    }
    validationEngine.setLanguage(cliContext.getLang());
    validationEngine.setLocale(cliContext.getLocale());
    validationEngine.setSnomedExtension(cliContext.getSnomedCTCode());
    validationEngine.setAssumeValidRestReferences(cliContext.isAssumeValidRestReferences());
    validationEngine.setShowMessagesFromReferences(cliContext.isShowMessagesFromReferences());
    validationEngine.setDoImplicitFHIRPathStringConversion(cliContext.isDoImplicitFHIRPathStringConversion());
    validationEngine.setHtmlInMarkdownCheck(cliContext.getHtmlInMarkdownCheck());
    validationEngine.setAllowDoubleQuotesInFHIRPath(cliContext.isAllowDoubleQuotesInFHIRPath());
    validationEngine.setNoExtensibleBindingMessages(cliContext.isNoExtensibleBindingMessages());
    validationEngine.setNoUnicodeBiDiControlChars(cliContext.isNoUnicodeBiDiControlChars());
    validationEngine.setNoInvariantChecks(cliContext.isNoInvariants());
    validationEngine.setDisplayWarnings(cliContext.isDisplayWarnings());
    validationEngine.setBestPracticeLevel(cliContext.getBestPracticeLevel());
    validationEngine.setCheckIPSCodes(cliContext.isCheckIPSCodes());
    validationEngine.setWantInvariantInMessage(cliContext.isWantInvariantsInMessages());
    validationEngine.setSecurityChecks(cliContext.isSecurityChecks());
    validationEngine.setCrumbTrails(cliContext.isCrumbTrails());
    validationEngine.setShowMessageIds(cliContext.isShowMessageIds());
    validationEngine.setForPublication(cliContext.isForPublication());
    validationEngine.setShowTimes(cliContext.isShowTimes());
    validationEngine.setAllowExampleUrls(cliContext.isAllowExampleUrls());
    validationEngine.setAiService(cliContext.getAIService());
    ReferenceValidationPolicy refpol = ReferenceValidationPolicy.CHECK_VALID;
    if (!cliContext.isDisableDefaultResourceFetcher()) {
      StandAloneValidatorFetcher fetcher = new StandAloneValidatorFetcher(validationEngine.getPcm(), validationEngine.getContext(), validationEngine);
      validationEngine.setFetcher(fetcher);
      validationEngine.getContext().setLocator(fetcher);
      validationEngine.setPolicyAdvisor(fetcher);
      if (cliContext.isCheckReferences()) {
        fetcher.setReferencePolicy(ReferenceValidationPolicy.CHECK_VALID);
      } else {
        fetcher.setReferencePolicy(ReferenceValidationPolicy.IGNORE);        
      }
      fetcher.setResolutionContext(cliContext.getResolutionContext());
    } else {
      DisabledValidationPolicyAdvisor fetcher = new DisabledValidationPolicyAdvisor();
      validationEngine.setPolicyAdvisor(fetcher);
      refpol = ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS;
    }
    if (cliContext.getAdvisorFile() != null) {
      if (cliContext.getAdvisorFile().endsWith(".json")) {
        validationEngine.getPolicyAdvisor().setPolicyAdvisor(new JsonDrivenPolicyAdvisor(validationEngine.getPolicyAdvisor().getPolicyAdvisor(), ManagedFileAccess.file(cliContext.getAdvisorFile())));
      } else {
        validationEngine.getPolicyAdvisor().setPolicyAdvisor(new TextDrivenPolicyAdvisor(validationEngine.getPolicyAdvisor().getPolicyAdvisor(), ManagedFileAccess.file(cliContext.getAdvisorFile())));          
      }
    } else {
      validationEngine.getPolicyAdvisor().setPolicyAdvisor(new BasePolicyAdvisorForFullValidation(validationEngine.getPolicyAdvisor() == null ? refpol : validationEngine.getPolicyAdvisor().getReferencePolicy()));
    }
    validationEngine.getBundleValidationRules().addAll(cliContext.getBundleValidationRules());
    validationEngine.setJurisdiction(CodeSystemUtilities.readCoding(cliContext.getJurisdiction()));
    validationEngine.setUnknownCodeSystemsCauseErrors(cliContext.isUnknownCodeSystemsCauseErrors());
    validationEngine.setNoExperimentalContent(cliContext.isNoExperimentalContent());
    TerminologyCache.setNoCaching(cliContext.isNoInternalCaching());
    validationEngine.prepare(); // generate any missing snapshots
    System.out.println(" go (" + timeTracker.milestone() + ")");
    return validationEngine;
  }

  protected void loadIgsAndExtensions(ValidationEngine validationEngine, CliContext cliContext, TimeTracker timeTracker) throws IOException, URISyntaxException {
    FhirPublication ver = FhirPublication.fromCode(cliContext.getSv());
    IgLoader igLoader = new IgLoader(validationEngine.getPcm(), validationEngine.getContext(), validationEngine.getVersion(), validationEngine.isDebug());
    igLoader.loadIg(validationEngine.getIgs(), validationEngine.getBinaries(), "hl7.terminology", false);
    if (!VersionUtilities.isR5Ver(validationEngine.getContext().getVersion())) {
      igLoader.loadIg(validationEngine.getIgs(), validationEngine.getBinaries(), "hl7.fhir.uv.extensions", false);
    }
    System.out.print("  Terminology server " + cliContext.getTxServer());
    String txver = validationEngine.setTerminologyServer(cliContext.getTxServer(), cliContext.getTxLog(), ver, !cliContext.getNoEcosystem());
    System.out.println(" - Version " + txver + " (" + timeTracker.milestone() + ")");
    validationEngine.setDebug(cliContext.isDoDebug());
    validationEngine.getContext().setLogger(new SystemOutLoggingService(cliContext.isDoDebug()));
    for (String src : cliContext.getIgs()) {
      igLoader.loadIg(validationEngine.getIgs(), validationEngine.getBinaries(), src, cliContext.isRecursive());
    }
    System.out.println("  Package Summary: "+ validationEngine.getContext().loadedPackageSummary());
  }

  public String determineVersion(CliContext cliContext) throws IOException {
    if (cliContext.getMode() != EngineMode.VALIDATION && cliContext.getMode() != EngineMode.INSTALL) {
      return "5.0";
    }
    System.out.println("Scanning for versions (no -version parameter):");
    VersionSourceInformation versions = scanForVersions(cliContext);
    for (String s : versions.getReport()) {
      if (!s.equals("(nothing found)")) {
        System.out.println("  " + s);
      }
    }
    if (versions.isEmpty()) {
      System.out.println("  No Version Info found: Using Default version R5");
      return "5.0.0";
    }
    if (versions.size() == 1) {
      System.out.println("-> use version " + versions.version());
      return versions.version();
    }
    throw new IllegalArgumentException("-> Multiple versions found. Specify a particular version using the -version parameter");
  }

  public void generateSpreadsheet(CliContext cliContext, ValidationEngine validator) throws Exception {
    CanonicalResource cr = validator.loadCanonicalResource(cliContext.getSources().get(0), cliContext.getSv());
    boolean ok = true;
    if (cr instanceof StructureDefinition) {
      new StructureDefinitionSpreadsheetGenerator(validator.getContext(), false, false).renderStructureDefinition((StructureDefinition) cr, false).finish(ManagedFileAccess.outStream(cliContext.getOutput()));
    } else if (cr instanceof CodeSystem) {
      new CodeSystemSpreadsheetGenerator(validator.getContext()).renderCodeSystem((CodeSystem) cr).finish(ManagedFileAccess.outStream(cliContext.getOutput()));
    } else if (cr instanceof ValueSet) {
      new ValueSetSpreadsheetGenerator(validator.getContext()).renderValueSet((ValueSet) cr).finish(ManagedFileAccess.outStream(cliContext.getOutput()));
    } else if (cr instanceof ConceptMap) {
      new ConceptMapSpreadsheetGenerator(validator.getContext()).renderConceptMap((ConceptMap) cr).finish(ManagedFileAccess.outStream(cliContext.getOutput()));
    } else {
      ok = false;
      System.out.println(" ...Unable to generate spreadsheet for "+cliContext.getSources().get(0)+": no way to generate a spreadsheet for a "+cr.fhirType());
    }

    if (ok) {
      System.out.println(" ...generated spreadsheet successfully");
    }
  }

  public void transformLang(CliContext cliContext, ValidationEngine validator) throws IOException {
    switch (cliContext.getLangTransform()) {
    case "extract":
      transformLangExtract(cliContext, validator);
      break;
    case "inject":
      transformLangInject(cliContext, validator);
      break;
    default:
      System.out.println(" ...Unknown lang transform mode "+cliContext.getLangTransform());        
    }
  }

  private void transformLangExtract(CliContext cliContext, ValidationEngine validator) throws IOException { 
    String dst = cliContext.getOutput();
    Utilities.createDirectory(dst);
    PoGetTextProducer po = new PoGetTextProducer(dst, ".", false);
    XLIFFProducer xliff = new XLIFFProducer(dst, ".", false);
    JsonLangFileProducer jl = new JsonLangFileProducer(dst, ".", false);
    
    List refs = new ArrayList<>();
    ValidatorUtils.parseSources(cliContext.getSources(), refs, validator.getContext());    
    for (SourceFile ref : refs) {
      System.out.println("  Extract Translations from " + ref);
      org.hl7.fhir.validation.Content cnt = validator.getIgLoader().loadContent(ref.getRef(), "translate", false, true);
      Element e = Manager.parseSingle(validator.getContext(), new ByteArrayInputStream(cnt.getFocus().getBytes()), cnt.getCntType());
      LanguageProducerSession ps = po.startSession(e.fhirType()+"-"+e.getIdBase(), cliContext.getSrcLang());
      LanguageProducerLanguageSession psl = ps.forLang(cliContext.getTgtLang());
      new LanguageUtils(validator.getContext()).generateTranslations(e, psl);
      psl.finish();
      ps.finish();
      ps = xliff.startSession(e.fhirType()+"-"+e.getIdBase(), cliContext.getSrcLang());
      psl = ps.forLang(cliContext.getTgtLang());
      new LanguageUtils(validator.getContext()).generateTranslations(e, psl);
      psl.finish();
      ps.finish(); 
      ps = jl.startSession(e.fhirType()+"-"+e.getIdBase(), cliContext.getSrcLang());
      psl = ps.forLang(cliContext.getTgtLang());
      new LanguageUtils(validator.getContext()).generateTranslations(e, psl);
      psl.finish();
      ps.finish(); 
    }
    System.out.println("Done - produced "+(po.fileCount()+xliff.fileCount()) + " files in "+dst);
  }
  
  private void transformLangInject(CliContext cliContext, ValidationEngine validator) throws IOException { 
    String dst = cliContext.getOutput();
    Utilities.createDirectory(dst);
    
    List translations = new ArrayList<>();
    for (String input : cliContext.getInputs()) {
      loadTranslationSource(translations, input);
    }
    
    List refs = new ArrayList<>();
    ValidatorUtils.parseSources(cliContext.getSources(), refs, validator.getContext()); 
    int t = 0;
    for (SourceFile ref : refs) {
      System.out.println("  Inject Translations into " + ref);
      org.hl7.fhir.validation.Content cnt = validator.getIgLoader().loadContent(ref.getRef(), "translate", false, true);
      Element e = Manager.parseSingle(validator.getContext(), new ByteArrayInputStream(cnt.getFocus().getBytes()), cnt.getCntType());      
      t = t + new LanguageUtils(validator.getContext()).importFromTranslations(e, translations);
      Manager.compose(validator.getContext(), e, ManagedFileAccess.outStream(Utilities.path(dst, ManagedFileAccess.file(ref.getRef()).getName())), cnt.getCntType(),
          OutputStyle.PRETTY, null);
    }
    System.out.println("Done - imported "+t+" translations into "+refs.size()+ " in "+dst);
  }
  
  private void loadTranslationSource(List translations, String input) throws IOException {
    File f = ManagedFileAccess.file(input);
    if (f.exists()) {
      if (f.isDirectory()) {
        for (File fd : f.listFiles()) {
          loadTranslationSource(translations, fd.getAbsolutePath());
        }
      } else {
        if (f.getName().endsWith(".po")) {
          try {
            translations.addAll(new PoGetTextProducer().loadSource(ManagedFileAccess.inStream(f)));
          } catch (Exception e) {
            System.out.println("Error reading PO File "+f.getAbsolutePath()+": "+e.getMessage());
          }
        } else if (f.getName().endsWith(".xliff")) {
          try {
            translations.addAll(new XLIFFProducer().loadSource(ManagedFileAccess.inStream(f)));
          } catch (Exception e) {
            System.out.println("Error reading XLIFF File "+f.getAbsolutePath()+": "+e.getMessage());
          }          
        } else {
          try {
            translations.addAll(new PoGetTextProducer().loadSource(ManagedFileAccess.inStream(f)));
          } catch (Exception e) {
            try {
              translations.addAll(new XLIFFProducer().loadSource(ManagedFileAccess.inStream(f)));
            } catch (Exception e2) {
              System.out.println("Error reading File "+f.getAbsolutePath()+" as XLIFF: "+e2.getMessage());
              System.out.println("Error reading File "+f.getAbsolutePath()+" as PO: "+e.getMessage());
            }   
          }
        }
      }
    } else {
      System.out.println("Input not found: "+input);
    }    
  }

  private int cp;
  private int cs;
  public void install(CliContext cliContext, ValidationEngine validator) throws FHIRException, IOException {
    cp = 0;
    cs = 0;
    System.out.println("Generating Snapshots");
    for (String ig : cliContext.getIgs()) {
      processIG(validator, ig);
    }
    System.out.println("Installed/Processed "+cp+" packages, generated "+cs+" snapshots");
  }

  private void processIG(ValidationEngine validator, String ig) throws FHIRException, IOException {
    validator.loadPackage(ig, null);
    NpmPackage npm = validator.getPcm().loadPackage(ig);
    if (!npm.isCore()) {
      for (String d : npm.dependencies()) {
        processIG(validator, d);
      }
      System.out.println("Processing "+ig);
      cp++;
      for (String d : npm.listResources("StructureDefinition")) {
        String filename = npm.getFilePath(d);
        Resource res = validator.loadResource(TextFile.fileToBytes(filename), filename);
        if (!(res instanceof StructureDefinition))
          throw new FHIRException("Require a StructureDefinition for generating a snapshot");
        StructureDefinition sd = (StructureDefinition) res;
        if (!sd.hasSnapshot()) {
          StructureDefinition base = validator.getContext().fetchResource(StructureDefinition.class, sd.getBaseDefinition());
          cs++;
          new ProfileUtilities(validator.getContext(), new ArrayList(), null).setAutoFixSliceNames(true).generateSnapshot(base, sd, sd.getUrl(), null, sd.getName());
          validator.handleOutput(sd, filename, validator.getVersion());
        }
      }
    }
  }

  public void codeGen(CliContext cliContext, ValidationEngine validationEngine) throws IOException {
    boolean ok = true;
    if (cliContext.getProfiles().isEmpty()) {
      System.out.println("Must specify at least one profile to generate code for with -profile or -profiles ");
      ok = false;
    }
    if (cliContext.getPackageName() == null) {
      System.out.println("Must provide a Java package name (-package-name)");
      ok = false;
    }
    if (cliContext.getSv() == null) {
      System.out.println("Must specify a version (-version)");
      ok = false;
    } else if (!VersionUtilities.isR4Ver(cliContext.getSv()) && !VersionUtilities.isR5Ver(cliContext.getSv())) {      
      System.out.println("Only versions 4 and 5 are supported (-version)");
      ok = false;
    }
    if (cliContext.getOutput() == null) {
      System.out.println("Must provide an output directory (-output)");
      ok = false;
    }
    Utilities.createDirectory(cliContext.getOutput());
    if (ok) {
      PECodeGenerator gen = new PECodeGenerator(validationEngine.getContext());
      gen.setFolder(cliContext.getOutput());
      gen.setExtensionPolicy(ExtensionPolicy.Complexes);
      gen.setNarrative(cliContext.getOptions().contains("narrative"));
      gen.setMeta(cliContext.getOptions().contains("meta"));
      gen.setLanguage(Locale.getDefault().toLanguageTag());
      gen.setContained(cliContext.getOptions().contains("contained"));
      gen.setKeyElementsOnly(!cliContext.getOptions().contains("all-elements"));
      gen.setGenDate(new SimpleDateFormat().format(new Date()));
      gen.setPkgName(cliContext.getPackageName());
      if (VersionUtilities.isR4Ver(cliContext.getSv())) {
        gen.setVersion("r4");
      } else {
        gen.setVersion("r5");
      }

      for (String profile : cliContext.getProfiles()) {
        if (profile.endsWith("*")) {
          for (StructureDefinition sd : validationEngine.getContext().fetchResourcesByType(StructureDefinition.class)) {
            if (sd.getUrl().startsWith(profile.replace("*", ""))) {
              gen.setCanonical(sd.getUrl());
              System.out.print("Generate for "+sd.getUrl());
              String s = gen.execute();
              System.out.println(": "+s);
            }
          }
        } else {
          gen.setCanonical(profile);
          System.out.print("Generate for "+profile);
          String s = gen.execute();
          System.out.println(": "+s);
        }
      }
      System.out.println("Done");
    }
  }

  public void instanceFactory(CliContext cliContext, ValidationEngine validationEngine) throws IOException {
    boolean ok = true;
    if (cliContext.getSource() == null) {
      System.out.println("Must specify a source (-version)");
      ok = false;
    } else if (!ManagedFileAccess.file(cliContext.getSource()).exists()) {      
      System.out.println("Factory source '"+cliContext.getSource()+"' not found");
      ok = false;
    }

    if (ok) {
      System.out.println("Preparing to execute");
              
      FHIRPathEngine fpe = new FHIRPathEngine(validationEngine.getContext());
      TestDataHostServices hs = new TestDataHostServices(validationEngine.getContext(), new DateTimeType(new Date()), new StringType(VersionUtilities.getSpecUrl(validationEngine.getContext().getVersion())));
      hs.registerFunction(new GlobalObjectRandomFunction());
      hs.registerFunction(new BaseTableWrapper.TableColumnFunction());
      hs.registerFunction(new BaseTableWrapper.TableDateColumnFunction());
      hs.registerFunction(new TestDataFactory.CellLookupFunction());
      hs.registerFunction(new TestDataFactory.TableLookupFunction());
      fpe.setHostServices(hs);
      LiquidEngine liquid = new LiquidEngine(validationEngine.getContext(), hs);
      
      String path = Utilities.getDirectoryForFile(cliContext.getSource());
      String log = Utilities.path(path, "log");
      Utilities.createDirectory(log);
                  
      JsonObject json = JsonParser.parseObjectFromFile(cliContext.getSource());
      for (JsonObject fact : json.forceArray("factories").asJsonObjects()) {
        TestDataFactory tdf = new TestDataFactory(validationEngine.getContext(), fact, liquid, fpe, "http://hl7.org/fhir/test", path, log);
        tdf.setTesting(true); // no randomness
        System.out.println("Execute Test Data Factory '"+tdf.getName()+"'. Log in "+Utilities.path(log, tdf.statedLog()));
        tdf.execute();
      }

      System.out.println("Done");
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy