001package org.hl7.fhir.validation;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032/*
033Copyright (c) 2011+, HL7, Inc
034All rights reserved.
035
036Redistribution and use in source and binary forms, with or without modification,
037are permitted provided that the following conditions are met:
038
039 * Redistributions of source code must retain the above copyright notice, this
040   list of conditions and the following disclaimer.
041 * Redistributions in binary form must reproduce the above copyright notice,
042   this list of conditions and the following disclaimer in the documentation
043   and/or other materials provided with the distribution.
044 * Neither the name of HL7 nor the names of its contributors may be used to
045   endorse or promote products derived from this software without specific
046   prior written permission.
047
048THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
049ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
050WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
051IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
052INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
053NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
054PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
055WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
056ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
057POSSIBILITY OF SUCH DAMAGE.
058
059*/
060
061import org.hl7.fhir.r5.model.ImplementationGuide;
062import org.hl7.fhir.r5.model.StructureDefinition;
063import org.hl7.fhir.utilities.TimeTracker;
064import org.hl7.fhir.utilities.Utilities;
065import org.hl7.fhir.utilities.VersionUtilities;
066import org.hl7.fhir.utilities.npm.CommonPackages;
067import org.hl7.fhir.validation.cli.model.CliContext;
068import org.hl7.fhir.validation.cli.services.ComparisonService;
069import org.hl7.fhir.validation.cli.services.ValidationService;
070import org.hl7.fhir.validation.cli.utils.*;
071
072import java.io.File;
073import java.net.Authenticator;
074import java.net.PasswordAuthentication;
075import java.util.ArrayList;
076import java.util.List;
077
078/**
079 * A executable class that will validate one or more FHIR resources against
080 * the specification
081 * <p>
082 * todo: schema validation (w3c xml, json schema, shex?)
083 * <p>
084 * if you want to host validation inside a process, skip this class, and look at
085 * ValidationEngine
086 * <p>
087 * todo: find a home for this:
088 *
089 * @author Grahame
090 */
091public class ValidatorCli {
092
093  public static final String HTTP_PROXY_HOST = "http.proxyHost";
094  public static final String HTTP_PROXY_PORT = "http.proxyPort";
095  public static final String HTTP_PROXY_USER = "http.proxyUser";
096  public static final String HTTP_PROXY_PASS = "http.proxyPassword";
097  public static final String JAVA_DISABLED_TUNNELING_SCHEMES = "jdk.http.auth.tunneling.disabledSchemes";
098  public static final String JAVA_DISABLED_PROXY_SCHEMES = "jdk.http.auth.proxying.disabledSchemes";
099  public static final String JAVA_USE_SYSTEM_PROXIES = "java.net.useSystemProxies";
100
101  private static ValidationService validationService = new ValidationService();
102
103  public static void main(String[] args) throws Exception {
104    TimeTracker tt = new TimeTracker();
105    TimeTracker.Session tts = tt.start("Loading");
106
107    args = preProcessArgs(args);
108    
109    Display.displayVersion();
110    Display.displaySystemInfo();
111
112    if (Params.hasParam(args, Params.PROXY)) {
113      assert Params.getParam(args, Params.PROXY) != null : "PROXY arg passed in was NULL";
114      String[] p = Params.getParam(args, Params.PROXY).split(":");
115      System.setProperty(HTTP_PROXY_HOST, p[0]);
116      System.setProperty(HTTP_PROXY_PORT, p[1]);
117    }
118
119    if (Params.hasParam(args, Params.PROXY_AUTH)) {
120      assert Params.getParam(args, Params.PROXY) != null : "Cannot set PROXY_AUTH without setting PROXY...";
121      assert Params.getParam(args, Params.PROXY_AUTH) != null : "PROXY_AUTH arg passed in was NULL...";
122      String[] p = Params.getParam(args, Params.PROXY_AUTH).split(":");
123      String authUser = p[0];
124      String authPass = p[1];
125
126      /*
127       * For authentication, use java.net.Authenticator to set proxy's configuration and set the system properties
128       * http.proxyUser and http.proxyPassword
129       */
130      Authenticator.setDefault(
131        new Authenticator() {
132          @Override
133          public PasswordAuthentication getPasswordAuthentication() {
134            return new PasswordAuthentication(authUser, authPass.toCharArray());
135          }
136        }
137      );
138
139      System.setProperty(HTTP_PROXY_USER, authUser);
140      System.setProperty(HTTP_PROXY_PASS, authPass);
141      System.setProperty(JAVA_USE_SYSTEM_PROXIES, "true");
142
143      /*
144       * For Java 1.8 and higher you must set
145       * -Djdk.http.auth.tunneling.disabledSchemes=
146       * to make proxies with Basic Authorization working with https along with Authenticator
147       */
148      System.setProperty(JAVA_DISABLED_TUNNELING_SCHEMES, "");
149      System.setProperty(JAVA_DISABLED_PROXY_SCHEMES, "");
150    }
151
152    CliContext cliContext = Params.loadCliContext(args);
153
154    if (Params.hasParam(args, Params.TEST)) {
155      Common.runValidationEngineTests();
156    } else if (shouldDisplayHelpToUser(args)) {
157      Display.displayHelpDetails();
158    } else if (Params.hasParam(args, Params.COMPARE)) {
159      if (destinationDirectoryValid(Params.getParam(args, Params.DESTINATION))) {
160        doLeftRightComparison(args, cliContext, tt);
161      }
162    } else {
163      Display.printCliArgumentsAndInfo(args);
164      doValidation(tt, tts, cliContext);
165    }
166  }
167
168  private static String[] preProcessArgs(String[] args) {
169    // ips$branch --> -version 4.0 -ig hl7.fhir.uv.ips#current$connectathon-2 -profile http://hl7.org/fhir/uv/ips/StructureDefinition/Bundle-uv-ips
170    List<String> res = new ArrayList<>();
171    for (String a : args) {
172      if (a.equals("-ips")) {
173        res.add("-version");
174        res.add("4.0");
175        res.add("-ig");
176        res.add("hl7.fhir.uv.ips#current$connectathon-2");
177        res.add("-profile");
178        res.add("http://hl7.org/fhir/uv/ips/StructureDefinition/Bundle-uv-ips");
179      } else if (a.startsWith("-ips$")) {
180        res.add("-version");
181        res.add("4.0");
182        res.add("-ig");
183        res.add("hl7.fhir.uv.ips#current$"+a.substring(5));
184        res.add("-profile");
185        res.add("http://hl7.org/fhir/uv/ips/StructureDefinition/Bundle-uv-ips");        
186      } else {
187        res.add(a);
188      }
189    }
190    String[] r = new String[res.size()];
191    for (int i = 0; i < res.size(); i++) {
192      r[i] = res.get(i);
193    }
194    return r;
195  }
196
197  private static boolean destinationDirectoryValid(String dest) {
198    if (dest == null) {
199      System.out.println("no -dest parameter provided");
200      return false;
201    } else if (!new File(dest).isDirectory()) {
202      System.out.println("Specified destination (-dest parameter) is not valid: \"" + dest + "\")");
203      return false;
204    } else {
205      System.out.println("Valid destination directory provided: \"" + dest + "\")");
206      return true;
207    }
208  }
209
210  private static boolean shouldDisplayHelpToUser(String[] args) {
211    return (args.length == 0
212      || Params.hasParam(args, Params.HELP)
213      || Params.hasParam(args, "?")
214      || Params.hasParam(args, "-?")
215      || Params.hasParam(args, "/?"));
216  }
217
218  private static void doLeftRightComparison(String[] args, CliContext cliContext, TimeTracker tt) throws Exception {
219    Display.printCliArgumentsAndInfo(args);
220    if (cliContext.getSv() == null) {
221      cliContext.setSv(validationService.determineVersion(cliContext));
222    }
223    String v = VersionUtilities.getCurrentVersion(cliContext.getSv());
224    String definitions = VersionUtilities.packageForVersion(v) + "#" + v;
225    ValidationEngine validator = validationService.initializeValidator(cliContext, definitions, tt);
226    validator.loadPackage(CommonPackages.ID_PUBPACK, null);
227    ComparisonService.doLeftRightComparison(args, Params.getParam(args, Params.DESTINATION), validator);
228  }
229
230  private static void doValidation(TimeTracker tt, TimeTracker.Session tts, CliContext cliContext) throws Exception {
231    if (cliContext.getSv() == null) {
232      cliContext.setSv(validationService.determineVersion(cliContext));
233    }
234    System.out.println("Loading");
235    // Comment this out because definitions filename doesn't necessarily contain version (and many not even be 14 characters long).
236    // Version gets spit out a couple of lines later after we've loaded the context
237    String definitions = VersionUtilities.packageForVersion(cliContext.getSv()) + "#" + VersionUtilities.getCurrentVersion(cliContext.getSv());
238    ValidationEngine validator = validationService.initializeValidator(cliContext, definitions, tt);
239    tts.end();
240    switch (cliContext.getMode()) {
241      case TRANSFORM:
242        validationService.transform(cliContext, validator);
243        break;
244      case COMPILE:
245        validationService.compile(cliContext, validator);
246        break;
247      case NARRATIVE:
248        validationService.generateNarrative(cliContext, validator);
249        break;
250      case SNAPSHOT:
251        validationService.generateSnapshot(cliContext, validator);
252        break;
253      case SPREADSHEET:
254        validationService.generateSpreadsheet(cliContext, validator);
255        break;
256      case CONVERT:
257        validationService.convertSources(cliContext, validator);
258        break;
259      case FHIRPATH:
260        validationService.evaluateFhirpath(cliContext, validator);
261        break;
262      case VERSION:
263        validationService.transformVersion(cliContext, validator);
264        break;
265      case VALIDATION:
266      case SCAN:
267      default:
268        for (String s : cliContext.getProfiles()) {
269          if (!validator.getContext().hasResource(StructureDefinition.class, s) && !validator.getContext().hasResource(ImplementationGuide.class, s)) {
270            System.out.println("  Fetch Profile from " + s);
271            validator.loadProfile(cliContext.getLocations().getOrDefault(s, s));
272          }
273        }
274        System.out.println("Validating");
275        if (cliContext.getMode() == EngineMode.SCAN) {
276          Scanner validationScanner = new Scanner(validator.getContext(), validator.getValidator(null), validator.getIgLoader(), validator.getFhirPathEngine());
277          validationScanner.validateScan(cliContext.getOutput(), cliContext.getSources());
278        } else {
279          validationService.validateSources(cliContext, validator);
280        }
281        break;
282    }
283    System.out.println("Done. " + tt.report()+". Max Memory = "+Utilities.describeSize(Runtime.getRuntime().maxMemory()));
284  }
285}