001/*- 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.context.support; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.context.FhirVersionEnum; 024import ca.uhn.fhir.util.ILockable; 025import ca.uhn.fhir.util.ReflectionUtil; 026import jakarta.annotation.Nonnull; 027import jakarta.annotation.Nullable; 028import org.apache.commons.lang3.Validate; 029import org.hl7.fhir.instance.model.api.IBase; 030import org.hl7.fhir.instance.model.api.IBaseResource; 031import org.hl7.fhir.instance.model.api.IPrimitiveType; 032 033import java.util.Collections; 034import java.util.HashMap; 035import java.util.List; 036import java.util.Map; 037import java.util.Optional; 038 039/** 040 * This class returns the vocabulary that is shipped with the base FHIR 041 * specification. 042 * 043 * Note that this class is version aware. For example, a request for 044 * <code>http://foo-codesystem|123</code> will only return a value if 045 * the built in resource if the version matches. Unversioned URLs 046 * should generally be used, and will return whatever version is 047 * present. 048 */ 049public class DefaultProfileValidationSupport implements IValidationSupport { 050 051 private static final Map<FhirVersionEnum, IValidationSupport> ourImplementations = 052 Collections.synchronizedMap(new HashMap<>()); 053 054 /** 055 * Userdata key indicating the source package ID for this package 056 */ 057 public static final String SOURCE_PACKAGE_ID = 058 DefaultProfileValidationSupport.class.getName() + "_SOURCE_PACKAGE_ID"; 059 060 private final FhirContext myCtx; 061 /** 062 * This module just delegates all calls to a concrete implementation which will 063 * be in this field. Which implementation gets used depends on the FHIR version. 064 */ 065 private final IValidationSupport myDelegate; 066 067 private final Runnable myFlush; 068 069 /** 070 * Constructor 071 * 072 * @param theFhirContext The context to use 073 */ 074 public DefaultProfileValidationSupport(@Nonnull FhirContext theFhirContext) { 075 Validate.notNull(theFhirContext, "FhirContext must not be null"); 076 myCtx = theFhirContext; 077 078 IValidationSupport strategy; 079 synchronized (ourImplementations) { 080 strategy = ourImplementations.get(theFhirContext.getVersion().getVersion()); 081 082 if (strategy == null) { 083 if (theFhirContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R5)) { 084 /* 085 * I don't love that we use reflection here, but this class is in 086 * hapi-fhir-base, and the class we're creating is in 087 * hapi-fhir-validation. There are complicated dependency chains that 088 * make this hard to clean up. At some point it'd be nice to figure out 089 * a cleaner solution though. 090 */ 091 strategy = ReflectionUtil.newInstance( 092 "org.hl7.fhir.common.hapi.validation.support.DefaultProfileValidationSupportNpmStrategy", 093 IValidationSupport.class, 094 new Class[] {FhirContext.class}, 095 new Object[] {theFhirContext}); 096 ((ILockable) strategy).lock(); 097 } else { 098 strategy = new DefaultProfileValidationSupportBundleStrategy(theFhirContext); 099 } 100 ourImplementations.put(theFhirContext.getVersion().getVersion(), strategy); 101 } 102 } 103 104 myDelegate = strategy; 105 if (myDelegate instanceof DefaultProfileValidationSupportBundleStrategy) { 106 myFlush = () -> ((DefaultProfileValidationSupportBundleStrategy) myDelegate).flush(); 107 } else { 108 myFlush = () -> {}; 109 } 110 } 111 112 @Override 113 public String getName() { 114 return myCtx.getVersion().getVersion() + " FHIR Standard Profile Validation Support"; 115 } 116 117 @Override 118 public List<IBaseResource> fetchAllConformanceResources() { 119 List<IBaseResource> retVal = myDelegate.fetchAllConformanceResources(); 120 addPackageInformation(retVal); 121 return retVal; 122 } 123 124 @Override 125 public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() { 126 List<T> retVal = myDelegate.fetchAllStructureDefinitions(); 127 addPackageInformation(retVal); 128 return retVal; 129 } 130 131 @Nullable 132 @Override 133 public <T extends IBaseResource> List<T> fetchAllNonBaseStructureDefinitions() { 134 List<T> retVal = myDelegate.fetchAllNonBaseStructureDefinitions(); 135 addPackageInformation(retVal); 136 return retVal; 137 } 138 139 @Override 140 public IBaseResource fetchCodeSystem(String theSystem) { 141 IBaseResource retVal = myDelegate.fetchCodeSystem(theSystem); 142 addPackageInformation(retVal); 143 return retVal; 144 } 145 146 @Override 147 public IBaseResource fetchStructureDefinition(String theUrl) { 148 IBaseResource retVal = myDelegate.fetchStructureDefinition(theUrl); 149 addPackageInformation(retVal); 150 return retVal; 151 } 152 153 @Override 154 public IBaseResource fetchValueSet(String theUrl) { 155 IBaseResource retVal = myDelegate.fetchValueSet(theUrl); 156 addPackageInformation(retVal); 157 return retVal; 158 } 159 160 public void flush() { 161 myFlush.run(); 162 } 163 164 @Override 165 public FhirContext getFhirContext() { 166 return myCtx; 167 } 168 169 @Nullable 170 public static String getConformanceResourceUrl(FhirContext theFhirContext, IBaseResource theResource) { 171 String urlValueString = null; 172 Optional<IBase> urlValue = theFhirContext 173 .getResourceDefinition(theResource) 174 .getChildByName("url") 175 .getAccessor() 176 .getFirstValueOrNull(theResource); 177 if (urlValue.isPresent()) { 178 IPrimitiveType<?> urlValueType = (IPrimitiveType<?>) urlValue.get(); 179 urlValueString = urlValueType.getValueAsString(); 180 } 181 return urlValueString; 182 } 183 184 private <T extends IBaseResource> void addPackageInformation(List<T> theResources) { 185 if (theResources != null) { 186 theResources.forEach(this::addPackageInformation); 187 } 188 } 189 190 private void addPackageInformation(IBaseResource theResource) { 191 if (theResource != null) { 192 String sourcePackageId = null; 193 switch (myCtx.getVersion().getVersion()) { 194 case DSTU2: 195 case DSTU2_HL7ORG: 196 sourcePackageId = "hl7.fhir.r2.core"; 197 break; 198 case DSTU2_1: 199 return; 200 case DSTU3: 201 sourcePackageId = "hl7.fhir.r3.core"; 202 break; 203 case R4: 204 sourcePackageId = "hl7.fhir.r4.core"; 205 break; 206 case R4B: 207 sourcePackageId = "hl7.fhir.r4b.core"; 208 break; 209 case R5: 210 sourcePackageId = "hl7.fhir.r5.core"; 211 break; 212 } 213 214 Validate.notNull( 215 sourcePackageId, 216 "Don't know how to handle package ID: %s", 217 myCtx.getVersion().getVersion()); 218 219 theResource.setUserData(SOURCE_PACKAGE_ID, sourcePackageId); 220 } 221 } 222}