View Javadoc
1   package ca.uhn.fhir.context;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 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 static org.apache.commons.lang3.StringUtils.*;
24  
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.Comparator;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.LinkedHashMap;
31  import java.util.LinkedList;
32  import java.util.List;
33  import java.util.Map;
34  
35  import org.apache.commons.lang3.StringUtils;
36  import org.apache.commons.lang3.text.WordUtils;
37  
38  import ca.uhn.fhir.model.api.IElement;
39  import ca.uhn.fhir.model.api.IPrimitiveDatatype;
40  import ca.uhn.fhir.model.api.IResource;
41  import ca.uhn.fhir.model.api.annotation.Child;
42  import ca.uhn.fhir.model.api.annotation.ResourceDef;
43  import ca.uhn.fhir.model.dstu.resource.Profile;
44  import ca.uhn.fhir.model.dstu.resource.Profile.ExtensionDefn;
45  import ca.uhn.fhir.model.dstu.resource.Profile.Structure;
46  import ca.uhn.fhir.model.dstu.resource.Profile.StructureElement;
47  import ca.uhn.fhir.model.dstu.resource.Profile.StructureElementDefinitionType;
48  import ca.uhn.fhir.model.dstu.valueset.DataTypeEnum;
49  import ca.uhn.fhir.model.dstu.valueset.SlicingRulesEnum;
50  import ca.uhn.fhir.model.primitive.IdDt;
51  
52  public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefinition<IResource> {
53  
54  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RuntimeResourceDefinition.class);
55  	private RuntimeResourceDefinition myBaseDefinition;
56  	private Map<RuntimeChildDeclaredExtensionDefinition, String> myExtensionDefToCode = new HashMap<RuntimeChildDeclaredExtensionDefinition, String>();
57  	private String myId;
58  	private Map<String, RuntimeSearchParam> myNameToSearchParam = new LinkedHashMap<String, RuntimeSearchParam>();
59  	private Profile myProfileDef;
60  	private String myResourceProfile;
61  	private List<RuntimeSearchParam> mySearchParams;
62  
63  	public RuntimeResourceDefinition(String theResourceName, Class<? extends IResource> theClass, ResourceDef theResourceAnnotation) {
64  		super(theResourceName, theClass);
65  		myResourceProfile = theResourceAnnotation.profile();
66  		myId = theResourceAnnotation.id();
67  	}
68  
69  	public void addSearchParam(RuntimeSearchParam theParam) {
70  		myNameToSearchParam.put(theParam.getName(), theParam);
71  	}
72  
73  	/**
74  	 * If this definition refers to a class which extends another resource definition type, this
75  	 * method will return the definition of the topmost resource. For example, if this definition
76  	 * refers to MyPatient2, which extends MyPatient, which in turn extends Patient, this method
77  	 * will return the resource definition for Patient.
78  	 * <p>
79  	 * If the definition has no parent, returns <code>this</code>
80  	 * </p>
81  	 */
82  	public RuntimeResourceDefinition getBaseDefinition() {
83  		return myBaseDefinition;
84  	}
85  
86  	@Override
87  	public ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum getChildType() {
88  		return ChildTypeEnum.RESOURCE;
89  	}
90  
91  	public String getResourceProfile() {
92  		return myResourceProfile;
93  	}
94  
95  	public RuntimeSearchParam getSearchParam(String theName) {
96  		return myNameToSearchParam.get(theName);
97  	}
98  
99  	public List<RuntimeSearchParam> getSearchParams() {
100 		return mySearchParams;
101 	}
102 
103 	public boolean isStandardProfile() {
104 		return myResourceProfile.startsWith("http://hl7.org/fhir/profiles");
105 	}
106 
107 	@Override
108 	public void sealAndInitialize(Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
109 		super.sealAndInitialize(theClassToElementDefinitions);
110 
111 		myNameToSearchParam = Collections.unmodifiableMap(myNameToSearchParam);
112 
113 		ArrayList<RuntimeSearchParam> searchParams = new ArrayList<RuntimeSearchParam>(myNameToSearchParam.values());
114 		Collections.sort(searchParams, new Comparator<RuntimeSearchParam>() {
115 			@Override
116 			public int compare(RuntimeSearchParam theArg0, RuntimeSearchParam theArg1) {
117 				return theArg0.getName().compareTo(theArg1.getName());
118 			}
119 		});
120 		mySearchParams = Collections.unmodifiableList(searchParams);
121 		
122 		Class<?> target = getImplementingClass();
123 		myBaseDefinition = this;
124 		do {
125 			target = target.getSuperclass();
126 			if (IResource.class.isAssignableFrom(target) && target.getAnnotation(ResourceDef.class)!=null) {
127 				myBaseDefinition = (RuntimeResourceDefinition) theClassToElementDefinitions.get(target);
128 			}
129 		} while (target.equals(Object.class)==false);
130 	}
131 
132 	public synchronized Profile toProfile() {
133 		if (myProfileDef != null) {
134 			return myProfileDef;
135 		}
136 
137 		Profile retVal = new Profile();
138 		
139 		RuntimeResourceDefinition def = this;
140 
141 		if (StringUtils.isBlank(myId)) {
142 			myId = getName().toLowerCase();
143 		}
144 		
145 		retVal.setId(new IdDt(myId));
146 		
147 		// Scan for extensions
148 		scanForExtensions(retVal, def);
149 		Collections.sort(retVal.getExtensionDefn(), new Comparator<ExtensionDefn>() {
150 			@Override
151 			public int compare(ExtensionDefn theO1, ExtensionDefn theO2) {
152 				return theO1.getCode().compareTo(theO2.getCode());
153 			}
154 		});
155 
156 		// Scan for children
157 		retVal.setName(getName());
158 		Structure struct = retVal.addStructure();
159 		LinkedList<String> path = new LinkedList<String>();
160 
161 		StructureElement element = struct.addElement();
162 		element.getDefinition().setMin(1);
163 		element.getDefinition().setMax("1");
164 
165 		fillProfile(struct, element, def, path, null);
166 
167 		retVal.getStructure().get(0).getElement().get(0).getDefinition().addType().getCode().setValue("Resource");
168 
169 		myProfileDef = retVal;
170 
171 		return retVal;
172 	}
173 
174 	private void fillBasics(StructureElement theElement, BaseRuntimeElementDefinition<?> def, LinkedList<String> path, BaseRuntimeDeclaredChildDefinition theChild) {
175 		if (path.isEmpty()) {
176 			path.add(def.getName());
177 			theElement.setName(def.getName());
178 		} else {
179 			path.add(WordUtils.uncapitalize(theChild.getElementName()));
180 			theElement.setName(theChild.getElementName());
181 		}
182 		theElement.setPath(StringUtils.join(path, '.'));
183 	}
184 
185 	private void fillExtensions(Structure theStruct, LinkedList<String> path, List<RuntimeChildDeclaredExtensionDefinition> extList, String elementName, boolean theIsModifier) {
186 		if (extList.size() > 0) {
187 			StructureElement extSlice = theStruct.addElement();
188 			extSlice.setName(elementName);
189 			extSlice.setPath(join(path, '.') + '.' + elementName);
190 			extSlice.getSlicing().getDiscriminator().setValue("url");
191 			extSlice.getSlicing().setOrdered(false);
192 			extSlice.getSlicing().setRules(SlicingRulesEnum.OPEN);
193 			extSlice.getDefinition().addType().setCode(DataTypeEnum.EXTENSION);
194 
195 			for (RuntimeChildDeclaredExtensionDefinition nextExt : extList) {
196 				StructureElement nextProfileExt = theStruct.addElement();
197 				nextProfileExt.getDefinition().setIsModifier(theIsModifier);
198 				nextProfileExt.setName(extSlice.getName());
199 				nextProfileExt.setPath(extSlice.getPath());
200 				fillMinAndMaxAndDefinitions(nextExt, nextProfileExt);
201 				StructureElementDefinitionType type = nextProfileExt.getDefinition().addType();
202 				type.setCode(DataTypeEnum.EXTENSION);
203 				if (nextExt.isDefinedLocally()) {
204 					type.setProfile(nextExt.getExtensionUrl().substring(nextExt.getExtensionUrl().indexOf('#')));
205 				} else {
206 					type.setProfile(nextExt.getExtensionUrl());
207 				}
208 			}
209 		} else {
210 			StructureElement extSlice = theStruct.addElement();
211 			extSlice.setName(elementName);
212 			extSlice.setPath(join(path, '.') + '.' + elementName);
213 			extSlice.getDefinition().setIsModifier(theIsModifier);
214 			extSlice.getDefinition().addType().setCode(DataTypeEnum.EXTENSION);
215 			extSlice.getDefinition().setMin(0);
216 			extSlice.getDefinition().setMax("*");
217 		}
218 	}
219 
220 	private void fillMinAndMaxAndDefinitions(BaseRuntimeDeclaredChildDefinition child, StructureElement elem) {
221 		elem.getDefinition().setMin(child.getMin());
222 		if (child.getMax() == Child.MAX_UNLIMITED) {
223 			elem.getDefinition().setMax("*");
224 		} else {
225 			elem.getDefinition().setMax(Integer.toString(child.getMax()));
226 		}
227 
228 		if (isNotBlank(child.getShortDefinition())) {
229 			elem.getDefinition().getShort().setValue(child.getShortDefinition());
230 		}
231 		if (isNotBlank(child.getFormalDefinition())) {
232 			elem.getDefinition().getFormal().setValue(child.getFormalDefinition());
233 		}
234 	}
235 
236 	private void fillName(StructureElement elem, BaseRuntimeElementDefinition<?> nextDef) {
237 		if (nextDef instanceof RuntimeResourceReferenceDefinition) {
238 			RuntimeResourceReferenceDefinition rr = (RuntimeResourceReferenceDefinition) nextDef;
239 			for (Class<? extends IResource> next : rr.getResourceTypes()) {
240 				StructureElementDefinitionType type = elem.getDefinition().addType();
241 				type.getCode().setValue("ResourceReference");
242 
243 				if (next != IResource.class) {
244 					RuntimeResourceDefinition resDef = rr.getDefinitionForResourceType(next);
245 					type.getProfile().setValueAsString(resDef.getResourceProfile());
246 				}
247 			}
248 
249 			return;
250 		}
251 
252 		StructureElementDefinitionType type = elem.getDefinition().addType();
253 		String name = nextDef.getName();
254 		DataTypeEnum fromCodeString = DataTypeEnum.VALUESET_BINDER.fromCodeString(name);
255 		if (fromCodeString == null) {
256 			throw new ConfigurationException("Unknown type: " + name);
257 		}
258 		type.setCode(fromCodeString);
259 	}
260 
261 	private void fillProfile(Structure theStruct, StructureElement theElement, BaseRuntimeElementDefinition<?> def, LinkedList<String> path, BaseRuntimeDeclaredChildDefinition theChild) {
262 
263 		fillBasics(theElement, def, path, theChild);
264 
265 		String expectedPath = StringUtils.join(path, '.');
266 
267 		ourLog.info("Filling profile for: {} - Path: {}", expectedPath);
268 		String name = def.getName();
269 		if (!expectedPath.equals(name)) {
270 			path.pollLast();
271 			theElement.getDefinition().getNameReference().setValue(def.getName());
272 			return;
273 		}
274 
275 		fillExtensions(theStruct, path, def.getExtensionsNonModifier(), "extension", false);
276 		fillExtensions(theStruct, path, def.getExtensionsModifier(), "modifierExtension", true);
277 
278 		if (def.getChildType() == ChildTypeEnum.RESOURCE) {
279 			StructureElement narrative = theStruct.addElement();
280 			narrative.setName("text");
281 			narrative.setPath(join(path, '.') + ".text");
282 			narrative.getDefinition().addType().setCode(DataTypeEnum.NARRATIVE);
283 			narrative.getDefinition().setIsModifier(false);
284 			narrative.getDefinition().setMin(0);
285 			narrative.getDefinition().setMax("1");
286 
287 			StructureElement contained = theStruct.addElement();
288 			contained.setName("contained");
289 			contained.setPath(join(path, '.') + ".contained");
290 			contained.getDefinition().addType().getCode().setValue("Resource");
291 			contained.getDefinition().setIsModifier(false);
292 			contained.getDefinition().setMin(0);
293 			contained.getDefinition().setMax("1");
294 		}
295 
296 		if (def instanceof BaseRuntimeElementCompositeDefinition) {
297 			BaseRuntimeElementCompositeDefinition<?> cdef = ((BaseRuntimeElementCompositeDefinition<?>) def);
298 			for (BaseRuntimeChildDefinition nextChild : cdef.getChildren()) {
299 				if (nextChild instanceof RuntimeChildUndeclaredExtensionDefinition) {
300 					continue;
301 				}
302 
303 				BaseRuntimeDeclaredChildDefinition child = (BaseRuntimeDeclaredChildDefinition) nextChild;
304 				StructureElement elem = theStruct.addElement();
305 				fillMinAndMaxAndDefinitions(child, elem);
306 
307 				if (child instanceof RuntimeChildResourceBlockDefinition) {
308 					RuntimeResourceBlockDefinition nextDef = (RuntimeResourceBlockDefinition) child.getSingleChildOrThrow();
309 					fillProfile(theStruct, elem, nextDef, path, child);
310 				} else if (child instanceof RuntimeChildContainedResources) {
311 					// ignore
312 				} else if (child instanceof RuntimeChildDeclaredExtensionDefinition) {
313 					throw new IllegalStateException("Unexpected child type: " + child.getClass().getCanonicalName());
314 				} else if (child instanceof RuntimeChildCompositeDatatypeDefinition || child instanceof RuntimeChildPrimitiveDatatypeDefinition || child instanceof RuntimeChildChoiceDefinition
315 						|| child instanceof RuntimeChildResourceDefinition) {
316 					Iterator<String> childNamesIter = child.getValidChildNames().iterator();
317 					String nextName = childNamesIter.next();
318 					BaseRuntimeElementDefinition<?> nextDef = child.getChildByName(nextName);
319 					fillBasics(elem, nextDef, path, child);
320 					fillName(elem, nextDef);
321 					while (childNamesIter.hasNext()) {
322 						nextDef = child.getChildByName(childNamesIter.next());
323 						fillName(elem, nextDef);
324 					}
325 					path.pollLast();
326 				} else {
327 					throw new IllegalStateException("Unexpected child type: " + child.getClass().getCanonicalName());
328 				}
329 
330 			}
331 		} else {
332 			throw new IllegalStateException("Unexpected child type: " + def.getClass().getCanonicalName());
333 		}
334 
335 		path.pollLast();
336 	}
337 
338 	private void scanForExtensions(Profile theProfile, BaseRuntimeElementDefinition<?> def) {
339 		BaseRuntimeElementCompositeDefinition<?> cdef = ((BaseRuntimeElementCompositeDefinition<?>) def);
340 
341 		for (RuntimeChildDeclaredExtensionDefinition nextChild : cdef.getExtensions()) {
342 			if (myExtensionDefToCode.containsKey(nextChild)) {
343 				continue;
344 			}
345 
346 			if (nextChild.isDefinedLocally() == false) {
347 				continue;
348 			}
349 
350 			ExtensionDefn defn = theProfile.addExtensionDefn();
351 			String code = null;
352 			if (nextChild.getExtensionUrl().contains("#") && !nextChild.getExtensionUrl().endsWith("#")) {
353 				code = nextChild.getExtensionUrl().substring(nextChild.getExtensionUrl().indexOf('#') + 1);
354 			} else {
355 				throw new ConfigurationException("Locally defined extension has no '#[code]' part in extension URL: " + nextChild.getExtensionUrl());
356 			}
357 
358 			defn.setCode(code);
359 			if (myExtensionDefToCode.values().contains(code)) {
360 				throw new IllegalStateException("Duplicate extension code: " + code);
361 			}
362 			myExtensionDefToCode.put(nextChild, code);
363 
364 			if (nextChild.getChildType() != null && IPrimitiveDatatype.class.isAssignableFrom(nextChild.getChildType())) {
365 				RuntimePrimitiveDatatypeDefinition pdef = (RuntimePrimitiveDatatypeDefinition) nextChild.getSingleChildOrThrow();
366 				defn.getDefinition().addType().setCode(DataTypeEnum.VALUESET_BINDER.fromCodeString(pdef.getName()));
367 			} else {
368 				RuntimeResourceBlockDefinition pdef = (RuntimeResourceBlockDefinition) nextChild.getSingleChildOrThrow();
369 				scanForExtensions(theProfile, pdef);
370 
371 				for (RuntimeChildDeclaredExtensionDefinition nextChildExt : pdef.getExtensions()) {
372 					StructureElementDefinitionType type = defn.getDefinition().addType();
373 					type.setCode(DataTypeEnum.EXTENSION);
374 					type.setProfile("#" + myExtensionDefToCode.get(nextChildExt));
375 				}
376 
377 			}
378 		}
379 	}
380 
381 }