001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2023 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; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.model.api.annotation.ResourceDef; 024import ca.uhn.fhir.util.UrlUtil; 025import org.hl7.fhir.instance.model.api.IAnyResource; 026import org.hl7.fhir.instance.model.api.IBase; 027import org.hl7.fhir.instance.model.api.IBaseResource; 028import org.hl7.fhir.instance.model.api.IDomainResource; 029 030import java.util.ArrayList; 031import java.util.Collections; 032import java.util.Comparator; 033import java.util.HashMap; 034import java.util.LinkedHashMap; 035import java.util.List; 036import java.util.Map; 037 038public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefinition<IBaseResource> { 039 040 private Class<? extends IBaseResource> myBaseType; 041 private Map<String, List<RuntimeSearchParam>> myCompartmentNameToSearchParams; 042 private FhirContext myContext; 043 private String myId; 044 private Map<String, RuntimeSearchParam> myNameToSearchParam = new LinkedHashMap<String, RuntimeSearchParam>(); 045 private IBaseResource myProfileDef; 046 private String myResourceProfile; 047 private List<RuntimeSearchParam> mySearchParams; 048 private final FhirVersionEnum myStructureVersion; 049 private volatile RuntimeResourceDefinition myBaseDefinition; 050 051 052 053 public RuntimeResourceDefinition(FhirContext theContext, String theResourceName, Class<? extends IBaseResource> theClass, ResourceDef theResourceAnnotation, boolean theStandardType, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) { 054 super(theResourceName, theClass, theStandardType, theContext, theClassToElementDefinitions); 055 myContext = theContext; 056 myResourceProfile = theResourceAnnotation.profile(); 057 myId = theResourceAnnotation.id(); 058 059 IBaseResource instance; 060 try { 061 instance = theClass.getConstructor().newInstance(); 062 } catch (Exception e) { 063 throw new ConfigurationException(Msg.code(1730) + myContext.getLocalizer().getMessage(getClass(), "nonInstantiableType", theClass.getName(), e.toString()), e); 064 } 065 myStructureVersion = instance.getStructureFhirVersionEnum(); 066 if (myStructureVersion != theContext.getVersion().getVersion()) { 067 if (myStructureVersion == FhirVersionEnum.R5 && theContext.getVersion().getVersion() == FhirVersionEnum.R4B) { 068 // TODO: remove this exception once we've bumped FHIR core to a new version 069 // TODO: also fix the TODO in ModelScanner 070 // TODO: also fix the TODO in RestfulServerUtils 071 // TODO: also fix the TODO in BaseParser 072 } else { 073 throw new ConfigurationException(Msg.code(1731) + myContext.getLocalizer().getMessage(getClass(), "typeWrongVersion", theContext.getVersion().getVersion(), theClass.getName(), myStructureVersion)); 074 } 075 } 076 077 } 078 079 080 public void addSearchParam(RuntimeSearchParam theParam) { 081 myNameToSearchParam.put(theParam.getName(), theParam); 082 } 083 084 /** 085 * If this definition refers to a class which extends another resource definition type, this 086 * method will return the definition of the topmost resource. For example, if this definition 087 * refers to MyPatient2, which extends MyPatient, which in turn extends Patient, this method 088 * will return the resource definition for Patient. 089 * <p> 090 * If the definition has no parent, returns <code>this</code> 091 * </p> 092 */ 093 public RuntimeResourceDefinition getBaseDefinition() { 094 validateSealed(); 095 if (myBaseDefinition == null) { 096 myBaseDefinition = myContext.getResourceDefinition(myBaseType); 097 } 098 return myBaseDefinition; 099 } 100 101 @Override 102 public ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum getChildType() { 103 return ChildTypeEnum.RESOURCE; 104 } 105 106 public String getId() { 107 return myId; 108 } 109 110 /** 111 * Express {@link #getImplementingClass()} as theClass (to prevent casting warnings) 112 */ 113 @SuppressWarnings("unchecked") 114 public <T> Class<T> getImplementingClass(Class<T> theClass) { 115 if (!theClass.isAssignableFrom(getImplementingClass())) { 116 throw new ConfigurationException(Msg.code(1732) + "Unable to convert " + getImplementingClass() + " to " + theClass); 117 } 118 return (Class<T>) getImplementingClass(); 119 } 120 121 @Deprecated 122 public String getResourceProfile() { 123 return myResourceProfile; 124 } 125 126 public String getResourceProfile(String theServerBase) { 127 validateSealed(); 128 String profile; 129 if (!myResourceProfile.isEmpty()) { 130 profile = myResourceProfile; 131 } else if (!myId.isEmpty()) { 132 profile = myId; 133 } else { 134 return ""; 135 } 136 137 if (!UrlUtil.isValid(profile)) { 138 String resourceName = "/StructureDefinition/"; 139 String profileWithUrl = theServerBase + resourceName + profile; 140 if (UrlUtil.isValid(profileWithUrl)) { 141 return profileWithUrl; 142 } 143 } 144 return profile; 145 } 146 147 public RuntimeSearchParam getSearchParam(String theName) { 148 validateSealed(); 149 return myNameToSearchParam.get(theName); 150 } 151 152 public List<RuntimeSearchParam> getSearchParams() { 153 validateSealed(); 154 return mySearchParams; 155 } 156 157 /** 158 * Will not return null 159 */ 160 public List<RuntimeSearchParam> getSearchParamsForCompartmentName(String theCompartmentName) { 161 validateSealed(); 162 List<RuntimeSearchParam> retVal = myCompartmentNameToSearchParams.get(theCompartmentName); 163 if (retVal == null) { 164 return Collections.emptyList(); 165 } 166 return retVal; 167 } 168 169 public FhirVersionEnum getStructureVersion() { 170 return myStructureVersion; 171 } 172 173 public boolean isBundle() { 174 return "Bundle".equals(getName()); 175 } 176 177 @SuppressWarnings("unchecked") 178 @Override 179 public void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) { 180 super.sealAndInitialize(theContext, theClassToElementDefinitions); 181 182 myNameToSearchParam = Collections.unmodifiableMap(myNameToSearchParam); 183 184 ArrayList<RuntimeSearchParam> searchParams = new ArrayList<RuntimeSearchParam>(myNameToSearchParam.values()); 185 Collections.sort(searchParams, new Comparator<RuntimeSearchParam>() { 186 @Override 187 public int compare(RuntimeSearchParam theArg0, RuntimeSearchParam theArg1) { 188 return theArg0.getName().compareTo(theArg1.getName()); 189 } 190 }); 191 mySearchParams = Collections.unmodifiableList(searchParams); 192 193 Map<String, List<RuntimeSearchParam>> compartmentNameToSearchParams = new HashMap<>(); 194 for (RuntimeSearchParam next : searchParams) { 195 if (next.getProvidesMembershipInCompartments() != null) { 196 for (String nextCompartment : next.getProvidesMembershipInCompartments()) { 197 198 if (nextCompartment.startsWith("Base FHIR compartment definition for ")) { 199 nextCompartment = nextCompartment.substring("Base FHIR compartment definition for ".length()); 200 } 201 202 if (!compartmentNameToSearchParams.containsKey(nextCompartment)) { 203 compartmentNameToSearchParams.put(nextCompartment, new ArrayList<>()); 204 } 205 List<RuntimeSearchParam> searchParamsForCompartment = compartmentNameToSearchParams.get(nextCompartment); 206 searchParamsForCompartment.add(next); 207 208 /* 209 * If one search parameter marks an SP as making a resource 210 * a part of a compartment, let's also denote all other 211 * SPs with the same path the same way. This behaviour is 212 * used by AuthorizationInterceptor 213 */ 214 String nextPath = massagePathForCompartmentSimilarity(next.getPath()); 215 for (RuntimeSearchParam nextAlternate : searchParams) { 216 String nextAlternatePath = massagePathForCompartmentSimilarity(nextAlternate.getPath()); 217 if (nextAlternatePath.equals(nextPath)) { 218 if (!nextAlternate.getName().equals(next.getName())) { 219 searchParamsForCompartment.add(nextAlternate); 220 } 221 } 222 } 223 } 224 } 225 } 226 227 // Make the map of lists completely unmodifiable 228 for (String nextKey : new ArrayList<>(compartmentNameToSearchParams.keySet())) { 229 List<RuntimeSearchParam> nextList = compartmentNameToSearchParams.get(nextKey); 230 compartmentNameToSearchParams.put(nextKey, Collections.unmodifiableList(nextList)); 231 } 232 myCompartmentNameToSearchParams = Collections.unmodifiableMap(compartmentNameToSearchParams); 233 234 Class<?> target = getImplementingClass(); 235 myBaseType = (Class<? extends IBaseResource>) target; 236 do { 237 target = target.getSuperclass(); 238 if (IBaseResource.class.isAssignableFrom(target) && target.getAnnotation(ResourceDef.class) != null) { 239 myBaseType = (Class<? extends IBaseResource>) target; 240 } 241 } while (target.equals(Object.class) == false); 242 243 /* 244 * See #504: 245 * Bundle types may not have extensions 246 */ 247 if (hasExtensions()) { 248 if (IAnyResource.class.isAssignableFrom(getImplementingClass())) { 249 if (!IDomainResource.class.isAssignableFrom(getImplementingClass())) { 250 throw new ConfigurationException(Msg.code(1733) + "Class \"" + getImplementingClass() + "\" is invalid. This resource type is not a DomainResource, it must not have extensions"); 251 } 252 } 253 } 254 255 } 256 257 private String massagePathForCompartmentSimilarity(String thePath) { 258 String path = thePath; 259 if (path.matches(".*\\.where\\(resolve\\(\\) is [a-zA-Z]+\\)")) { 260 path = path.substring(0, path.indexOf(".where")); 261 } 262 return path; 263 } 264 265 @Deprecated 266 public synchronized IBaseResource toProfile() { 267 validateSealed(); 268 if (myProfileDef != null) { 269 return myProfileDef; 270 } 271 272 IBaseResource retVal = myContext.getVersion().generateProfile(this, null); 273 myProfileDef = retVal; 274 275 return retVal; 276 } 277 278 public synchronized IBaseResource toProfile(String theServerBase) { 279 validateSealed(); 280 if (myProfileDef != null) { 281 return myProfileDef; 282 } 283 284 IBaseResource retVal = myContext.getVersion().generateProfile(this, theServerBase); 285 myProfileDef = retVal; 286 287 return retVal; 288 } 289 290}