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