View Javadoc
1   package ca.uhn.fhir.util;
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 java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.TreeSet;
28  
29  import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
30  import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
31  import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
32  import ca.uhn.fhir.context.ConfigurationException;
33  import ca.uhn.fhir.context.FhirContext;
34  import ca.uhn.fhir.context.RuntimeResourceDefinition;
35  import ca.uhn.fhir.model.api.ExtensionDt;
36  import ca.uhn.fhir.model.api.IElement;
37  import ca.uhn.fhir.model.api.IResource;
38  import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
39  import ca.uhn.fhir.model.dstu.composite.ContainedDt;
40  import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
41  import ca.uhn.fhir.model.primitive.StringDt;
42  import ca.uhn.fhir.parser.DataFormatException;
43  
44  public class FhirTerser {
45  
46  	public FhirTerser(FhirContext theContext) {
47  		super();
48  		myContext = theContext;
49  	}
50  
51  	private FhirContext myContext;
52  
53  	public BaseRuntimeChildDefinition getDefinition(Class<? extends IResource> theResourceType, String thePath) {
54  		RuntimeResourceDefinition def = myContext.getResourceDefinition(theResourceType);
55  
56  		BaseRuntimeElementCompositeDefinition<?> currentDef = def;
57  
58  		List<String> parts = Arrays.asList(thePath.split("\\."));
59  		List<String> subList = parts.subList(1, parts.size());
60  		if (subList.size() < 1) {
61  			throw new ConfigurationException("Invalid path: " + thePath);
62  		}
63  		return getDefinition(currentDef, subList);
64  
65  	}
66  
67  	private BaseRuntimeChildDefinition getDefinition(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, List<String> theSubList) {
68  		BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(theSubList.get(0));
69  
70  		if (theSubList.size() == 1) {
71  			return nextDef;
72  		} else {
73  			BaseRuntimeElementCompositeDefinition<?> cmp = (BaseRuntimeElementCompositeDefinition<?>) nextDef.getChildByName(theSubList.get(0));
74  			return getDefinition(cmp, theSubList.subList(1, theSubList.size()));
75  		}
76  	}
77  
78  	public List<Object> getValues(IResource theResource, String thePath) {
79  		RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
80  
81  		BaseRuntimeElementCompositeDefinition<?> currentDef = def;
82  		Object currentObj = theResource;
83  
84  		List<String> parts = Arrays.asList(thePath.split("\\."));
85  		List<String> subList = parts.subList(1, parts.size());
86  		if (subList.size() < 1) {
87  			throw new ConfigurationException("Invalid path: " + thePath);
88  		}
89  		return getValues(currentDef, currentObj, subList);
90  
91  	}
92  
93  	private List<Object> getValues(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, Object theCurrentObj, List<String> theSubList) {
94  		String name = theSubList.get(0);
95  		BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(name);
96  		List<? extends IElement> values = nextDef.getAccessor().getValues(theCurrentObj);
97  		List<Object> retVal = new ArrayList<Object>();
98  
99  		if (theSubList.size() == 1) {
100 			retVal.addAll(values);
101 		} else {
102 			for (IElement nextElement : values) {
103 				BaseRuntimeElementCompositeDefinition<?> nextChildDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(nextElement.getClass());
104 				List<?> foundValues = getValues(nextChildDef, nextElement, theSubList.subList(1, theSubList.size()));
105 				retVal.addAll(foundValues);
106 			}
107 		}
108 		return retVal;
109 	}
110 
111 	/**
112 	 * Visit all elements in a given resource
113 	 * 
114 	 * @param theResource The resource to visit
115 	 * @param theVisitor The visitor
116 	 */
117 	public void visit(IResource theResource, IModelVisitor theVisitor) {
118 		BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
119 		visit(theResource, null, def, theVisitor);
120 	}
121 
122 	/**
123 	 * Returns a list containing all child elements (including the resource itself) which are <b>non-empty</b> and are
124 	 * either of the exact type specified, or are a subclass of that type.
125 	 * <p>
126 	 * For example, specifying a type of {@link StringDt} would return all non-empty string instances within the
127 	 * message. Specifying a type of {@link IResource} would return the resource itself, as well as any contained
128 	 * resources.
129 	 * </p>
130 	 * <p>
131 	 * Note on scope: This method will descend into any contained resources ({@link IResource#getContained()}) as well, but will not
132 	 * decend into linked resources (e.g. {@link ResourceReferenceDt#getResource()})
133 	 * </p>
134 	 * 
135 	 * @param theResource
136 	 *            The resource instance to search. Must not be null.
137 	 * @param theType
138 	 *            The type to search for. Must not be null.
139 	 * @return Returns a list of all matching elements
140 	 */
141 	public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(IResource theResource, final Class<T> theType) {
142 		final ArrayList<T> retVal = new ArrayList<T>();
143 		BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
144 		visit(theResource, null, def, new IModelVisitor() {
145 			@SuppressWarnings("unchecked")
146 			@Override
147 			public void acceptElement(IElement theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) {
148 				if (theElement == null || theElement.isEmpty()) {
149 					return;
150 				}
151 
152 				if (theType.isAssignableFrom(theElement.getClass())) {
153 					retVal.add((T) theElement);
154 				}
155 			}
156 
157 			@SuppressWarnings("unchecked")
158 			@Override
159 			public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition, ExtensionDt theNextExt) {
160 				if (theType.isAssignableFrom(theNextExt.getClass())) {
161 					retVal.add((T) theNextExt);
162 				}
163 				if (theNextExt.getValue() != null && theType.isAssignableFrom(theNextExt.getValue().getClass())) {
164 					retVal.add((T) theNextExt.getValue());
165 				}
166 			}
167 		});
168 		return retVal;
169 	}
170 
171 	private <T extends IElement> void visit(IElement theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition, IModelVisitor theCallback) {
172 		theCallback.acceptElement(theElement, theChildDefinition, theDefinition);
173 		addUndeclaredExtensions(theElement, theDefinition, theChildDefinition, theCallback);
174 
175 //		if (theElement.isEmpty()) {
176 //			return;
177 //		}
178 		
179 		switch (theDefinition.getChildType()) {
180 		case PRIMITIVE_XHTML:
181 		case PRIMITIVE_DATATYPE:
182 		case RESOURCE_REF:
183 			// These are primitive types
184 			break;
185 		case RESOURCE_BLOCK:
186 		case COMPOSITE_DATATYPE:
187 		case RESOURCE: {
188 			BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) theDefinition;
189 			for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
190 				List<? extends IElement> values = nextChild.getAccessor().getValues(theElement);
191 				if (values != null) {
192 					for (IElement nextValue : values) {
193 						if (nextValue == null) {
194 							continue;
195 						}
196 						if (nextValue.isEmpty()) {
197 							continue;
198 						}
199 						BaseRuntimeElementDefinition<?> childElementDef;
200 						childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass());
201 						
202 						if (childElementDef == null) {
203 							StringBuilder b = new StringBuilder();
204 							b.append("Found value of type[");
205 							b.append(nextValue.getClass().getSimpleName());
206 							b.append("] which is not valid for field[");
207 							b.append(nextChild.getElementName());
208 							b.append("] in ");
209 							b.append(childDef.getName());
210 							b.append(" - Valid types: ");
211 							for (Iterator<String> iter = new TreeSet<String>(nextChild.getValidChildNames()).iterator(); iter.hasNext();) {
212 								b.append(nextChild.getChildByName(iter.next()).getImplementingClass().getSimpleName());
213 								if (iter.hasNext()) {
214 									b.append(", ");
215 								}
216 							}
217 							throw new DataFormatException(b.toString());
218 						}
219 						visit(nextValue, nextChild, childElementDef, theCallback);
220 					}
221 				}
222 			}
223 			break;
224 		}
225 		case CONTAINED_RESOURCES: {
226 			ContainedDt value = (ContainedDt) theElement;
227 			for (IResource next : value.getContainedResources()) {
228 				BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(next);
229 				visit(next, null, def, theCallback);
230 			}
231 			break;
232 		}
233 		case EXTENSION_DECLARED:
234 		case UNDECL_EXT: {
235 			throw new IllegalStateException("state should not happen: " + theDefinition.getChildType());
236 		}
237 		}
238 	}
239 
240 	private <T extends IElement> void addUndeclaredExtensions(IElement theElement, BaseRuntimeElementDefinition<?> theDefinition, BaseRuntimeChildDefinition theChildDefinition, IModelVisitor theCallback) {
241 		if (theElement instanceof ISupportsUndeclaredExtensions) {
242 			ISupportsUndeclaredExtensions containingElement = (ISupportsUndeclaredExtensions) theElement;
243 			for (ExtensionDt nextExt : containingElement.getUndeclaredExtensions()) {
244 				theCallback.acceptUndeclaredExtension(containingElement, theChildDefinition, theDefinition, nextExt);
245 				addUndeclaredExtensions(nextExt, theDefinition, theChildDefinition, theCallback);
246 			}
247 		}
248 	}
249 
250 }