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