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