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}