001: /*
002: * Copyright 2006-2007, Unitils.org
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.unitils.inject.util;
017:
018: import ognl.DefaultMemberAccess;
019: import ognl.Ognl;
020: import ognl.OgnlContext;
021: import ognl.OgnlException;
022: import org.apache.commons.lang.StringUtils;
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025: import org.unitils.core.UnitilsException;
026: import org.unitils.util.ReflectionUtils;
027: import static org.unitils.util.ReflectionUtils.*;
028:
029: import java.lang.reflect.Field;
030: import java.lang.reflect.InvocationTargetException;
031: import java.lang.reflect.Method;
032: import java.util.List;
033:
034: /**
035: * Class containing static methods that implement explicit injection using OGNL expressions, and auto-injection by type.
036: *
037: * @author Filip Neven
038: * @author Tim Ducheyne
039: */
040: public class InjectionUtils {
041:
042: /* The logger instance for this class */
043: private static Log logger = LogFactory.getLog(InjectionUtils.class);
044:
045: /**
046: * Explicit injection of the objectToInject into the specified property of the target. The property should be a
047: * correct OGNL expression.
048: *
049: * @param objectToInject The object that is injected
050: * @param target The target object
051: * @param property The OGNL expression that defines where the object will be injected, not null
052: * @return The object that was replaced by the injection
053: */
054: public static Object injectInto(Object objectToInject,
055: Object target, String property) {
056: if (target == null) {
057: throw new UnitilsException(
058: "Target for injection should not be null");
059: }
060: try {
061: OgnlContext ognlContext = new OgnlContext();
062: ognlContext.setMemberAccess(new DefaultMemberAccess(true));
063: Object ognlExpression = Ognl.parseExpression(property);
064:
065: Object oldValue = null;
066: try {
067: Ognl.getValue(ognlExpression, ognlContext, target);
068:
069: } catch (Exception e) {
070: logger
071: .warn(
072: "Unable to retrieve current value of field to inject into. Will not be able to restore value after injection.",
073: e);
074: }
075: Ognl.setValue(ognlExpression, ognlContext, target,
076: objectToInject);
077: return oldValue;
078:
079: } catch (OgnlException e) {
080: throw new UnitilsException(
081: "Failed to set value using OGNL expression "
082: + property, e);
083: }
084: }
085:
086: /**
087: * Explicit injection of the objectToInject into the specified static property of the target class. The property
088: * should be a correct OGNL expression.
089: *
090: * @param objectToInject The object that is injected
091: * @param targetClass The target class, not null
092: * @param property The OGNL expression that defines where the object will be injected, not null
093: * @return The object that was replaced by the injection
094: */
095: public static Object injectIntoStatic(Object objectToInject,
096: Class<?> targetClass, String property) {
097: String staticProperty = StringUtils.substringBefore(property,
098: ".");
099: if (property.equals(staticProperty)) {
100: // Simple property: directly set value on this property
101: Object oldValue = null;
102: try {
103: oldValue = getValueStatic(targetClass, staticProperty);
104:
105: } catch (Exception e) {
106: logger
107: .warn(
108: "Unable to retrieve current value of field to inject into. Will not be able to restore value after injection.",
109: e);
110: }
111: setValueStatic(targetClass, staticProperty, objectToInject);
112: return oldValue;
113:
114: } else {
115: // Multipart property: use ognl for remaining property part
116: Object objectToInjectInto = getValueStatic(targetClass,
117: staticProperty);
118: String remainingPropertyPart = StringUtils.substringAfter(
119: property, ".");
120: try {
121: return injectInto(objectToInject, objectToInjectInto,
122: remainingPropertyPart);
123:
124: } catch (UnitilsException e) {
125: throw new UnitilsException(
126: "Property named "
127: + remainingPropertyPart
128: + " not found on "
129: + objectToInjectInto.getClass()
130: .getSimpleName(), e);
131: }
132: }
133: }
134:
135: /**
136: * Performs auto-injection by type of the objectToInject on the target object.
137: *
138: * @param objectToInject The object that is injected
139: * @param objectToInjectType The type of the object. This should be the type of the object or one of his super-types
140: * or implemented interfaces. This type is used for property type matching on the target object
141: * @param target The object into which the objectToInject is injected
142: * @param propertyAccess Defines if field or setter injection is used
143: * @return The object that was replaced by the injection
144: */
145: public static Object injectIntoByType(Object objectToInject,
146: Class<?> objectToInjectType, Object target,
147: PropertyAccess propertyAccess) {
148: if (target == null) {
149: throw new UnitilsException(
150: "Target for injection should not be null");
151: }
152: if (propertyAccess == PropertyAccess.FIELD) {
153: return injectIntoFieldByType(objectToInject,
154: objectToInjectType, target, target.getClass(),
155: false);
156: }
157: return injectIntoSetterByType(objectToInject,
158: objectToInjectType, target, target.getClass(), false);
159: }
160:
161: /**
162: * Performs auto-injection by type of the objectToInject into the target class.
163: *
164: * @param objectToInject The object that is injected
165: * @param objectToInjectType The type of the object. This should be the type of the object or one of his super-types
166: * or implemented interfaces. This type is used for property type matching on the target class
167: * @param targetClass The class into which the objectToInject is injected
168: * @param propertyAccess Defines if field or setter injection is used
169: * @return The object that was replaced by the injection
170: */
171: public static Object injectIntoStaticByType(Object objectToInject,
172: Class<?> objectToInjectType, Class<?> targetClass,
173: PropertyAccess propertyAccess) {
174: if (propertyAccess == PropertyAccess.FIELD) {
175: return injectIntoFieldByType(objectToInject,
176: objectToInjectType, null, targetClass, true);
177: }
178: return injectIntoSetterByType(objectToInject,
179: objectToInjectType, null, targetClass, true);
180: }
181:
182: /**
183: * Performs auto-injection on a field by type of the objectToInject into the given target object or targetClass,
184: * depending on the value of isStatic. The object is injected on one single field, if there is more than one
185: * candidate field, a {@link UnitilsException} is thrown. We try to inject the object on the most specific field,
186: * this means that when there are muliple fields of one of the super-types or implemented interfaces of the field,
187: * the one that is lowest in the hierarchy is chosen (if possible, otherwise, a {@link UnitilsException} is thrown.
188: *
189: * @param objectToInject The object that is injected
190: * @param objectToInjectType The type of the object that is injected
191: * @param target The target object (only used when isStatic is false)
192: * @param targetClass The target class (only used when isStatis is true)
193: * @param isStatic Indicates wether injection should be performed on the target object or on the target class
194: * @return The object that was replaced by the injection
195: */
196: private static Object injectIntoFieldByType(Object objectToInject,
197: Class<?> objectToInjectType, Object target,
198: Class<?> targetClass, boolean isStatic) {
199: // Try to find a field with an exact matching type
200: Field fieldToInjectTo = null;
201: List<Field> fieldsWithExactType = ReflectionUtils
202: .getFieldsOfType(targetClass, objectToInjectType,
203: isStatic);
204: if (fieldsWithExactType.size() > 1) {
205: throw new UnitilsException("More than one "
206: + (isStatic ? "static " : "")
207: + "field with exact type "
208: + objectToInjectType.getSimpleName() + " found in "
209: + targetClass.getSimpleName());
210:
211: } else if (fieldsWithExactType.size() == 1) {
212: fieldToInjectTo = fieldsWithExactType.get(0);
213:
214: } else {
215: // Try to find a supertype field:
216: // If one field exist that has a type which is more specific than all other fields of the given type,
217: // this one is taken. Otherwise, an exception is thrown
218: List<Field> fieldsOfType = getFieldsAssignableFrom(
219: targetClass, objectToInjectType, isStatic);
220: if (fieldsOfType.size() == 0) {
221: throw new UnitilsException("No "
222: + (isStatic ? "static " : "")
223: + "field with (super)type "
224: + objectToInjectType.getSimpleName()
225: + " found in " + targetClass.getSimpleName());
226: }
227: for (Field field : fieldsOfType) {
228: boolean moreSpecific = true;
229: for (Field compareToField : fieldsOfType) {
230: if (field != compareToField) {
231: if (field.getClass().isAssignableFrom(
232: compareToField.getClass())) {
233: moreSpecific = false;
234: break;
235: }
236: }
237: }
238: if (moreSpecific) {
239: fieldToInjectTo = field;
240: break;
241: }
242: }
243: if (fieldToInjectTo == null) {
244: throw new UnitilsException(
245: "Multiple candidate target "
246: + (isStatic ? "static " : "")
247: + "fields found in "
248: + targetClass.getSimpleName()
249: + ", with none of them more specific than all others.");
250: }
251: }
252:
253: // Field to inject into found, inject the object and return old value
254: Object oldValue = null;
255: try {
256: oldValue = getFieldValue(target, fieldToInjectTo);
257:
258: } catch (Exception e) {
259: logger
260: .warn(
261: "Unable to retrieve current value of field to inject into. Will not be able to restore value after injection.",
262: e);
263: }
264: setFieldValue(target, fieldToInjectTo, objectToInject);
265: return oldValue;
266: }
267:
268: /**
269: * Performs auto-injection on a setter by type of the objectToInject into the given target object or targetClass,
270: * depending on the value of isStatic. The object is injected to one single setter, if there is more than one
271: * candidate setter, a {@link UnitilsException} is thrown. We try to inject the object on the most specific type,
272: * this means that when there are muliple setters for one of the super-types or implemented interfaces of the setter
273: * type, the one that is lowest in the hierarchy is chosen (if possible, otherwise, a {@link UnitilsException} is
274: * thrown.
275: *
276: * @param objectToInject The object that is injected
277: * @param objectToInjectType The type of the object that is injected
278: * @param target The target object (only used when isStatic is false)
279: * @param targetClass The target class (only used when isStatis is true)
280: * @param isStatic Indicates wether injection should be performed on the target object or on the target class
281: * @return The object that was replaced by the injection
282: */
283: private static Object injectIntoSetterByType(Object objectToInject,
284: Class<?> objectToInjectType, Object target,
285: Class<?> targetClass, boolean isStatic) {
286: // Try to find a method with an exact matching type
287: Method setterToInjectTo = null;
288: List<Method> settersWithExactType = getSettersOfType(
289: targetClass, objectToInjectType, isStatic);
290: if (settersWithExactType.size() > 1) {
291: throw new UnitilsException("More than one "
292: + (isStatic ? "static " : "")
293: + "setter with exact type "
294: + objectToInjectType.getSimpleName() + " found in "
295: + targetClass.getSimpleName());
296:
297: } else if (settersWithExactType.size() == 1) {
298: setterToInjectTo = settersWithExactType.get(0);
299:
300: } else {
301: // Try to find a supertype setter:
302: // If one setter exist that has a type which is more specific than all other setters of the given type,
303: // this one is taken. Otherwise, an exception is thrown
304: List<Method> settersOfType = getSettersAssignableFrom(
305: targetClass, objectToInjectType, isStatic);
306: if (settersOfType.size() == 0) {
307: throw new UnitilsException("No "
308: + (isStatic ? "static " : "")
309: + "setter with (super)type "
310: + objectToInjectType.getSimpleName()
311: + " found in " + targetClass.getSimpleName());
312: }
313: for (Method setter : settersOfType) {
314: boolean moreSpecific = true;
315: for (Method compareToSetter : settersOfType) {
316: if (setter != compareToSetter) {
317: if (setter.getClass().isAssignableFrom(
318: compareToSetter.getClass())) {
319: moreSpecific = false;
320: break;
321: }
322: }
323: }
324: if (moreSpecific) {
325: setterToInjectTo = setter;
326: break;
327: }
328: }
329: if (setterToInjectTo == null) {
330: throw new UnitilsException(
331: "Multiple candidate target "
332: + (isStatic ? "static " : "")
333: + "setters found in "
334: + targetClass.getSimpleName()
335: + ", with none of them more specific than all others.");
336: }
337: }
338:
339: // Setter to inject into found, inject the object and return old value
340: Object oldValue = null;
341: try {
342: Method getter = getGetter(setterToInjectTo);
343: if (getter == null) {
344: logger
345: .warn("Unable to retrieve current value of field to inject into, no getter found for setter: "
346: + setterToInjectTo
347: + ". Will not be able to restore value after injection.");
348: } else {
349: oldValue = invokeMethod(target, getter);
350: }
351: } catch (Exception e) {
352: logger
353: .warn(
354: "Unable to retrieve current value of field to inject into. Will not be able to restore value after injection.",
355: e);
356: }
357:
358: try {
359: invokeMethod(target, setterToInjectTo, objectToInject);
360:
361: } catch (InvocationTargetException e) {
362: throw new UnitilsException(
363: "Unable to inject to setter, exception thrown by target.",
364: e);
365: }
366: return oldValue;
367: }
368:
369: /**
370: * Retrieves the value of the static property from the given class
371: *
372: * @param targetClass The class from which the static property value is retrieved
373: * @param staticProperty The name of the property (simple name, not a composite expression)
374: * @return The value of the static property from the given class
375: */
376: private static Object getValueStatic(Class<?> targetClass,
377: String staticProperty) {
378: Method staticGetter = getGetter(targetClass, staticProperty,
379: true);
380: if (staticGetter != null) {
381: try {
382: return invokeMethod(targetClass, staticGetter);
383:
384: } catch (InvocationTargetException e) {
385: throw new UnitilsException(
386: "Exception thrown by target", e);
387: }
388: } else {
389: Field staticField = getFieldWithName(targetClass,
390: staticProperty, true);
391: if (staticField != null) {
392: return getFieldValue(targetClass, staticField);
393: } else {
394: throw new UnitilsException("Static property named "
395: + staticProperty + " not found on class "
396: + targetClass.getSimpleName());
397: }
398: }
399: }
400:
401: /**
402: * Sets the given value on the static property of the given targetClass
403: *
404: * @param targetClass The class on which the static property value should be set
405: * @param staticProperty The name of the property (simple name, not a composite expression)
406: * @param value The value to set
407: */
408: private static void setValueStatic(Class<?> targetClass,
409: String staticProperty, Object value) {
410: Method staticSetter = ReflectionUtils.getSetter(targetClass,
411: staticProperty, true);
412: if (staticSetter != null) {
413: try {
414: invokeMethod(targetClass, staticSetter, value);
415:
416: } catch (InvocationTargetException e) {
417: throw new UnitilsException(
418: "Exception thrown by target", e);
419: }
420: } else {
421: Field staticField = getFieldWithName(targetClass,
422: staticProperty, true);
423: if (staticField == null) {
424: throw new UnitilsException("Static property named "
425: + staticProperty + " not found on class "
426: + targetClass.getSimpleName());
427: }
428: setFieldValue(targetClass, staticField, value);
429: }
430: }
431: }
|