View Javadoc
1   package ca.uhn.fhir.context;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 - 2018 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.Comparator;
26  import java.util.HashMap;
27  import java.util.LinkedHashMap;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.apache.commons.lang3.StringUtils;
32  import org.hl7.fhir.instance.model.api.IAnyResource;
33  import org.hl7.fhir.instance.model.api.IBase;
34  import org.hl7.fhir.instance.model.api.IBaseResource;
35  import org.hl7.fhir.instance.model.api.IDomainResource;
36  
37  import ca.uhn.fhir.model.api.annotation.ResourceDef;
38  import ca.uhn.fhir.util.UrlUtil;
39  
40  public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefinition<IBaseResource> {
41  
42  	private Class<? extends IBaseResource> myBaseType;
43  	private Map<String, List<RuntimeSearchParam>> myCompartmentNameToSearchParams;
44  	private FhirContext myContext;
45  	private String myId;
46  	private Map<String, RuntimeSearchParam> myNameToSearchParam = new LinkedHashMap<String, RuntimeSearchParam>();
47  	private IBaseResource myProfileDef;
48  	private String myResourceProfile;
49  	private List<RuntimeSearchParam> mySearchParams;
50  	private final FhirVersionEnum myStructureVersion;
51  	private volatile RuntimeResourceDefinition myBaseDefinition;
52  
53  
54  
55  	public RuntimeResourceDefinition(FhirContext theContext, String theResourceName, Class<? extends IBaseResource> theClass, ResourceDef theResourceAnnotation, boolean theStandardType, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
56  		super(theResourceName, theClass, theStandardType, theContext, theClassToElementDefinitions);
57  		myContext = theContext;
58  		myResourceProfile = theResourceAnnotation.profile();
59  		myId = theResourceAnnotation.id();
60  
61  		IBaseResource instance;
62  		try {
63  			instance = theClass.newInstance();
64  		} catch (Exception e) {
65  			throw new ConfigurationException(myContext.getLocalizer().getMessage(getClass(), "nonInstantiableType", theClass.getName(), e.toString()), e);
66  		}
67  		myStructureVersion = instance.getStructureFhirVersionEnum();
68  		if (myStructureVersion != theContext.getVersion().getVersion()) {
69  			throw new ConfigurationException(myContext.getLocalizer().getMessage(getClass(), "typeWrongVersion", theContext.getVersion().getVersion(), theClass.getName(), myStructureVersion));
70  		}
71  
72  	}
73  
74  
75  	public void addSearchParam(RuntimeSearchParam theParam) {
76  		myNameToSearchParam.put(theParam.getName(), theParam);
77  	}
78  
79  	/**
80  	 * If this definition refers to a class which extends another resource definition type, this
81  	 * method will return the definition of the topmost resource. For example, if this definition
82  	 * refers to MyPatient2, which extends MyPatient, which in turn extends Patient, this method
83  	 * will return the resource definition for Patient.
84  	 * <p>
85  	 * If the definition has no parent, returns <code>this</code>
86  	 * </p>
87  	 */
88  	public RuntimeResourceDefinition getBaseDefinition() {
89  		validateSealed();
90  		if (myBaseDefinition == null) {
91  			myBaseDefinition = myContext.getResourceDefinition(myBaseType);
92  		}
93  		return myBaseDefinition;
94  	}
95  
96  	@Override
97  	public ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum getChildType() {
98  		return ChildTypeEnum.RESOURCE;
99  	}
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 					for (RuntimeSearchParam nextAlternate : searchParams) {
205 						if (nextAlternate.getPath().equals(next.getPath())) {
206 							if (!nextAlternate.getName().equals(next.getName())) {
207 								searchParamsForCompartment.add(nextAlternate);
208 							}
209 						}
210 					}
211 				}
212 			}
213 		}
214 		myCompartmentNameToSearchParams = Collections.unmodifiableMap(compartmentNameToSearchParams);
215 
216 		Class<?> target = getImplementingClass();
217 		myBaseType = (Class<? extends IBaseResource>) target;
218 		do {
219 			target = target.getSuperclass();
220 			if (IBaseResource.class.isAssignableFrom(target) && target.getAnnotation(ResourceDef.class) != null) {
221 				myBaseType = (Class<? extends IBaseResource>) target;
222 			}
223 		} while (target.equals(Object.class) == false);
224 		
225 		/*
226 		 * See #504:
227 		 * Bundle types may not have extensions
228 		 */
229 		if (hasExtensions()) {
230 			if (IAnyResource.class.isAssignableFrom(getImplementingClass())) {
231 				if (!IDomainResource.class.isAssignableFrom(getImplementingClass())) {
232 					throw new ConfigurationException("Class \"" + getImplementingClass() + "\" is invalid. This resource type is not a DomainResource, it must not have extensions");
233 				}
234 			}
235 		}
236 
237 	}
238 
239 	@Deprecated
240 	public synchronized IBaseResource toProfile() {
241 		validateSealed();
242 		if (myProfileDef != null) {
243 			return myProfileDef;
244 		}
245 
246 		IBaseResource retVal = myContext.getVersion().generateProfile(this, null);
247 		myProfileDef = retVal;
248 
249 		return retVal;
250 	}
251 
252 	public synchronized IBaseResource toProfile(String theServerBase) {
253 		validateSealed();
254 		if (myProfileDef != null) {
255 			return myProfileDef;
256 		}
257 
258 		IBaseResource retVal = myContext.getVersion().generateProfile(this, theServerBase);
259 		myProfileDef = retVal;
260 
261 		return retVal;
262 	}
263 
264 }