001package ca.uhn.fhir.context; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2017 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<String, List<RuntimeSearchParam>>(); 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<RuntimeSearchParam>()); 194 } 195 compartmentNameToSearchParams.get(nextCompartment).add(next); 196 } 197 } 198 } 199 myCompartmentNameToSearchParams = Collections.unmodifiableMap(compartmentNameToSearchParams); 200 201 Class<?> target = getImplementingClass(); 202 myBaseType = (Class<? extends IBaseResource>) target; 203 do { 204 target = target.getSuperclass(); 205 if (IBaseResource.class.isAssignableFrom(target) && target.getAnnotation(ResourceDef.class) != null) { 206 myBaseType = (Class<? extends IBaseResource>) target; 207 } 208 } while (target.equals(Object.class) == false); 209 210 /* 211 * See #504: 212 * Bundle types may not have extensions 213 */ 214 if (hasExtensions()) { 215 if (IAnyResource.class.isAssignableFrom(getImplementingClass())) { 216 if (!IDomainResource.class.isAssignableFrom(getImplementingClass())) { 217 throw new ConfigurationException("Class \"" + getImplementingClass() + "\" is invalid. This resource type is not a DomainResource, it must not have extensions"); 218 } 219 } 220 } 221 222 } 223 224 @Deprecated 225 public synchronized IBaseResource toProfile() { 226 validateSealed(); 227 if (myProfileDef != null) { 228 return myProfileDef; 229 } 230 231 IBaseResource retVal = myContext.getVersion().generateProfile(this, null); 232 myProfileDef = retVal; 233 234 return retVal; 235 } 236 237 public synchronized IBaseResource toProfile(String theServerBase) { 238 validateSealed(); 239 if (myProfileDef != null) { 240 return myProfileDef; 241 } 242 243 IBaseResource retVal = myContext.getVersion().generateProfile(this, theServerBase); 244 myProfileDef = retVal; 245 246 return retVal; 247 } 248 249}