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