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.lang.reflect.Field;
26  import java.lang.reflect.InvocationTargetException;
27  import java.lang.reflect.Method;
28  import java.util.ArrayList;
29  import java.util.Collections;
30  import java.util.List;
31  
32  import org.apache.commons.lang3.text.WordUtils;
33  
34  import ca.uhn.fhir.model.api.IElement;
35  import ca.uhn.fhir.model.api.annotation.Child;
36  import ca.uhn.fhir.model.api.annotation.Description;
37  import ca.uhn.fhir.util.BeanUtils;
38  
39  public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChildDefinition {
40  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseRuntimeDeclaredChildDefinition.class);
41  	private Boolean ourUseMethodAccessors;
42  	private final IAccessor myAccessor;
43  	private final String myElementName;
44  	private final Field myField;
45  	private final String myFormalDefinition;
46  	private final int myMax;
47  	private final int myMin;
48  	private final IMutator myMutator;
49  	private final String myShortDefinition;
50  
51  	BaseRuntimeDeclaredChildDefinition(Field theField, Child theChildAnnotation, Description theDescriptionAnnotation, String theElementName) throws ConfigurationException {
52  		super();
53  		if (theField == null) {
54  			throw new IllegalArgumentException("No field speficied");
55  		}
56  		if (theChildAnnotation.min() < 0) {
57  			throw new ConfigurationException("Min must be >= 0");
58  		}
59  		if (theChildAnnotation.max() != -1 && theChildAnnotation.max() < theChildAnnotation.min()) {
60  			throw new ConfigurationException("Max must be >= Min (unless it is -1 / unlimited)");
61  		}
62  		if (isBlank(theElementName)) {
63  			throw new ConfigurationException("Element name must not be blank");
64  		}
65  
66  		myField = theField;
67  		myMin = theChildAnnotation.min();
68  		myMax = theChildAnnotation.max();
69  		myElementName = theElementName;
70  		if (theDescriptionAnnotation != null) {
71  			myShortDefinition = theDescriptionAnnotation.shortDefinition();
72  			myFormalDefinition = theDescriptionAnnotation.formalDefinition();
73  		} else {
74  			myShortDefinition = null;
75  			myFormalDefinition = null;
76  		}
77  
78  		// TODO: handle lists (max>0), and maybe max=0?
79  
80  		// TODO: finish implementing field level accessors/mutators
81  		if (ourUseMethodAccessors == null) {
82  			try {
83  				myField.setAccessible(true);
84  				ourUseMethodAccessors = true;
85  			} catch (SecurityException e) {
86  				ourLog.info("Can not use field accessors/mutators, going to use methods instead");
87  				ourUseMethodAccessors = false;
88  			}
89  		}
90  
91  		if (ourUseMethodAccessors == false) {
92  			if (List.class.equals(myField.getType())) {
93  				// TODO: verify that generic type is IElement
94  				myAccessor = new FieldListAccessor();
95  				myMutator = new FieldListMutator();
96  			} else {
97  				myAccessor = new FieldPlainAccessor();
98  				myMutator = new FieldPlainMutator();
99  			}
100 		} else {
101 			Class<?> declaringClass = myField.getDeclaringClass();
102 			final Class<?> targetReturnType = myField.getType();
103 			try {
104 				String elementName = myElementName;
105 				if ("class".equals(elementName.toLowerCase())) {
106 					elementName = "classElement"; // because getClass() is reserved
107 				}
108 				final Method accessor = BeanUtils.findAccessor(declaringClass, targetReturnType, elementName);
109 				if (accessor == null) {
110 					throw new ConfigurationException("Could not find bean accessor/getter for property " + elementName + " on class " + declaringClass.getCanonicalName());
111 				}
112 
113 				final Method mutator = findMutator(declaringClass, targetReturnType, elementName);
114 				if (mutator == null) {
115 					throw new ConfigurationException("Could not find bean mutator/setter for property " + elementName + " on class " + declaringClass.getCanonicalName() + " (expected return type " + targetReturnType.getCanonicalName() + ")");
116 				}
117 
118 				if (List.class.isAssignableFrom(targetReturnType)) {
119 					myAccessor = new ListAccessor(accessor);
120 					myMutator = new ListMutator(mutator);
121 				} else {
122 					myAccessor = new PlainAccessor(accessor);
123 					myMutator = new PlainMutator(targetReturnType, mutator);
124 				}
125 			} catch (NoSuchFieldException e) {
126 				throw new ConfigurationException(e);
127 			}
128 		}
129 
130 	}
131 
132 	@Override
133 	public IAccessor getAccessor() {
134 		return myAccessor;
135 	}
136 
137 	public String getElementName() {
138 		return myElementName;
139 	}
140 
141 	public Field getField() {
142 		return myField;
143 	}
144 
145 	public String getFormalDefinition() {
146 		return myFormalDefinition;
147 	}
148 
149 	@Override
150 	public int getMax() {
151 		return myMax;
152 	}
153 
154 	@Override
155 	public int getMin() {
156 		return myMin;
157 	}
158 
159 	@Override
160 	public IMutator getMutator() {
161 		return myMutator;
162 	}
163 
164 	public String getShortDefinition() {
165 		return myShortDefinition;
166 	}
167 
168 	public BaseRuntimeElementDefinition<?> getSingleChildOrThrow() {
169 		if (getValidChildNames().size() != 1) {
170 			throw new IllegalStateException("This child has " + getValidChildNames().size() + " children, expected 1. This is a HAPI bug. Found: " + getValidChildNames());
171 		}
172 		return getChildByName(getValidChildNames().iterator().next());
173 	}
174 
175 	private static Method findMutator(Class<?> theDeclaringClass, Class<?> theTargetReturnType, String theElementName) {
176 		String methodName = "set" + WordUtils.capitalize(theElementName);
177 		try {
178 			return theDeclaringClass.getMethod(methodName, theTargetReturnType);
179 		} catch (NoSuchMethodException e) {
180 			return null;
181 		} catch (SecurityException e) {
182 			throw new ConfigurationException("Failed to scan class '" + theDeclaringClass + "' because of a security exception", e);
183 		}
184 	}
185 
186 	private final class FieldPlainMutator implements IMutator {
187 		@Override
188 		public void addValue(Object theTarget, IElement theValue) {
189 			try {
190 				myField.set(theTarget, theValue);
191 			} catch (IllegalArgumentException e) {
192 				throw new ConfigurationException("Failed to set value", e);
193 			} catch (IllegalAccessException e) {
194 				throw new ConfigurationException("Failed to set value", e);
195 			}
196 		}
197 	}
198 
199 	private final class FieldPlainAccessor implements IAccessor {
200 		@Override
201 		public List<? extends IElement> getValues(Object theTarget) {
202 			try {
203 				Object values = myField.get(theTarget);
204 				if (values == null) {
205 					return Collections.emptyList();
206 				}
207 				List<? extends IElement> retVal = (List<? extends IElement>) Collections.singletonList((IElement)values);
208 				return retVal;
209 			} catch (IllegalArgumentException e) {
210 				throw new ConfigurationException("Failed to get value", e);
211 			} catch (IllegalAccessException e) {
212 				throw new ConfigurationException("Failed to get value", e);
213 			}
214 		}
215 	}
216 
217 	private final class FieldListMutator implements IMutator {
218 		@Override
219 		public void addValue(Object theTarget, IElement theValue) {
220 			try {
221 				@SuppressWarnings("unchecked")
222 				List<IElement> existingList = (List<IElement>) myField.get(theTarget);
223 				if (existingList == null) {
224 					existingList = new ArrayList<IElement>(2);
225 					myField.set(theTarget, existingList);
226 				}
227 				existingList.add(theValue);
228 			} catch (IllegalArgumentException e) {
229 				throw new ConfigurationException("Failed to set value", e);
230 			} catch (IllegalAccessException e) {
231 				throw new ConfigurationException("Failed to set value", e);
232 			}
233 		}
234 	}
235 
236 	private final class FieldListAccessor implements IAccessor {
237 		@SuppressWarnings("unchecked")
238 		@Override
239 		public List<? extends IElement> getValues(Object theTarget) {
240 			@SuppressWarnings("unchecked")
241 			List<? extends IElement> retVal;
242 			try {
243 				retVal = (List<? extends IElement>) myField.get(theTarget);
244 			} catch (IllegalArgumentException e) {
245 				throw new ConfigurationException("Failed to get value", e);
246 			} catch (IllegalAccessException e) {
247 				throw new ConfigurationException("Failed to get value", e);
248 			}
249 			if (retVal == null) {
250 				retVal = Collections.emptyList();
251 			}
252 			return retVal;
253 		}
254 	}
255 
256 	private final static class ListAccessor implements IAccessor {
257 		private final Method myAccessorMethod;
258 
259 		private ListAccessor(Method theAccessor) {
260 			myAccessorMethod = theAccessor;
261 		}
262 
263 		@SuppressWarnings("unchecked")
264 		@Override
265 		public List<IElement> getValues(Object theTarget) {
266 			try {
267 				return (List<IElement>) myAccessorMethod.invoke(theTarget);
268 			} catch (IllegalAccessException e) {
269 				throw new ConfigurationException("Failed to get value", e);
270 			} catch (IllegalArgumentException e) {
271 				throw new ConfigurationException("Failed to get value", e);
272 			} catch (InvocationTargetException e) {
273 				throw new ConfigurationException("Failed to get value", e);
274 			}
275 		}
276 	}
277 
278 	private final class ListMutator implements IMutator {
279 		private final Method myMutatorMethod;
280 
281 		private ListMutator(Method theMutator) {
282 			myMutatorMethod = theMutator;
283 		}
284 
285 		@Override
286 		public void addValue(Object theTarget, IElement theValue) {
287 			@SuppressWarnings("unchecked")
288 			List<IElement> existingList = (List<IElement>) myAccessor.getValues(theTarget);
289 			if (existingList == null) {
290 				existingList = new ArrayList<IElement>();
291 				try {
292 					myMutatorMethod.invoke(theTarget, existingList);
293 				} catch (IllegalAccessException e) {
294 					throw new ConfigurationException("Failed to get value", e);
295 				} catch (IllegalArgumentException e) {
296 					throw new ConfigurationException("Failed to get value", e);
297 				} catch (InvocationTargetException e) {
298 					throw new ConfigurationException("Failed to get value", e);
299 				}
300 			}
301 			existingList.add(theValue);
302 		}
303 	}
304 
305 	private final class PlainAccessor implements IAccessor {
306 		private final Method myAccessorMethod;
307 
308 		private PlainAccessor(Method theAccessor) {
309 			myAccessorMethod = theAccessor;
310 		}
311 
312 		@Override
313 		public List<IElement> getValues(Object theTarget) {
314 			try {
315 				return Collections.singletonList((IElement) myAccessorMethod.invoke(theTarget));
316 			} catch (IllegalAccessException e) {
317 				throw new ConfigurationException("Failed to get value", e);
318 			} catch (IllegalArgumentException e) {
319 				throw new ConfigurationException("Failed to get value", e);
320 			} catch (InvocationTargetException e) {
321 				throw new ConfigurationException("Failed to get value", e);
322 			}
323 		}
324 	}
325 
326 	private final class PlainMutator implements IMutator {
327 		private final Method myMutatorMethod;
328 		private final Class<?> myTargetReturnType;
329 
330 		private PlainMutator(Class<?> theTargetReturnType, Method theMutator) {
331 			assert theTargetReturnType != null;
332 			assert theMutator != null;
333 
334 			myTargetReturnType = theTargetReturnType;
335 			myMutatorMethod = theMutator;
336 		}
337 
338 		@Override
339 		public void addValue(Object theTarget, IElement theValue) {
340 			try {
341 				if (theValue != null && !myTargetReturnType.isAssignableFrom(theValue.getClass())) {
342 					throw new ConfigurationException("Value for field " + myElementName + " expects type " + myTargetReturnType + " but got " + theValue.getClass());
343 				}
344 				myMutatorMethod.invoke(theTarget, theValue);
345 			} catch (IllegalAccessException e) {
346 				throw new ConfigurationException("Failed to get value", e);
347 			} catch (IllegalArgumentException e) {
348 				throw new ConfigurationException("Failed to get value", e);
349 			} catch (InvocationTargetException e) {
350 				throw new ConfigurationException("Failed to get value", e);
351 			}
352 		}
353 	}
354 
355 }