001: /**
002: *
003: * Licensed to the Apache Software Foundation (ASF) under one or more
004: * contributor license agreements. See the NOTICE file distributed with
005: * this work for additional information regarding copyright ownership.
006: * The ASF licenses this file to You under the Apache License, Version 2.0
007: * (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */package org.apache.openejb.client;
018:
019: import javax.naming.Context;
020: import javax.naming.NamingException;
021: import java.util.List;
022: import java.util.ArrayList;
023: import java.util.Map;
024: import java.util.HashMap;
025: import java.util.Arrays;
026: import java.util.logging.Logger;
027: import java.util.logging.Level;
028: import java.lang.reflect.Method;
029: import java.lang.reflect.InvocationTargetException;
030: import java.lang.reflect.Field;
031: import java.lang.reflect.Modifier;
032: import java.lang.reflect.AccessibleObject;
033: import java.security.AccessController;
034: import java.security.PrivilegedAction;
035: import java.beans.PropertyEditor;
036: import java.beans.PropertyEditorManager;
037:
038: public class ClientInjectionProcessor<T> {
039: private static final Logger logger = Logger
040: .getLogger("OpenEJB.client");
041:
042: private final Class<? extends T> beanClass;
043: private final ClassLoader classLoader;
044: private final List<Injection> injections;
045: private final List<CallbackMetaData> postConstructCallbacks;
046: private final List<CallbackMetaData> preDestroyCallbacks;
047: private final Context context;
048: private T instance;
049: private boolean allowStatic;
050:
051: public ClientInjectionProcessor(Class<? extends T> beanClass,
052: List<Injection> injections,
053: List<CallbackMetaData> postConstructMethods,
054: List<CallbackMetaData> preDestroyMethods, Context context) {
055: this .beanClass = beanClass;
056: classLoader = beanClass.getClassLoader();
057: this .injections = injections;
058: this .postConstructCallbacks = postConstructMethods;
059: this .preDestroyCallbacks = preDestroyMethods;
060: this .context = context;
061: }
062:
063: public void allowStatic() {
064: allowStatic = true;
065: }
066:
067: public T createInstance() throws Exception {
068: if (instance == null) {
069: construct();
070: }
071: return instance;
072: }
073:
074: public T getInstance() {
075: return instance;
076: }
077:
078: private void construct() {
079: Map<Injection, Object> values = new HashMap<Injection, Object>();
080: for (Injection injection : injections) {
081: // only process injections for this class
082: Class<?> targetClass = loadClass(injection.getTargetClass());
083: if (targetClass == null)
084: continue;
085: if (!targetClass.isAssignableFrom(beanClass))
086: continue;
087:
088: try {
089: String jndiName = injection.getJndiName();
090: Object object = context.lookup("java:comp/env/"
091: + jndiName);
092: values.put(injection, object);
093: } catch (NamingException e) {
094: logger
095: .warning("Injection data not found in JNDI context: jndiName='"
096: + injection.getJndiName()
097: + "', target="
098: + injection.getTargetClass()
099: + "/"
100: + injection.getName());
101: }
102: }
103:
104: try {
105: instance = beanClass.newInstance();
106: } catch (Exception e) {
107: throw new IllegalStateException(
108: "Error while creating bean " + beanClass.getName(),
109: e);
110: }
111:
112: List<String> unsetProperties = new ArrayList<String>();
113: for (Map.Entry<Injection, Object> entry : values.entrySet()) {
114: Injection injection = entry.getKey();
115: Object value = entry.getValue();
116:
117: Class<?> targetClass = loadClass(injection.getTargetClass());
118: if (targetClass == null
119: || !targetClass.isAssignableFrom(beanClass))
120: continue;
121:
122: if (!setProperty(targetClass, injection.getName(), value)) {
123: unsetProperties.add(injection.getName());
124: }
125: }
126:
127: if (unsetProperties.size() > 0) {
128: for (Object property : unsetProperties) {
129: logger.warning("Injection: Unable to set property '"
130: + property + "' in class "
131: + beanClass.getName());
132: }
133: }
134: }
135:
136: public void postConstruct() throws Exception {
137: if (instance == null)
138: throw new IllegalStateException(
139: "Instance has not been constructed");
140: if (postConstructCallbacks == null)
141: return;
142:
143: for (Method postConstruct : toMethod(postConstructCallbacks)) {
144: try {
145: postConstruct.invoke(instance);
146: } catch (Exception e) {
147: e = unwrap(e);
148: throw new Exception(
149: "Error while calling post construct method", e);
150: }
151: }
152: }
153:
154: public void preDestroy() {
155: if (instance == null)
156: return;
157: if (preDestroyCallbacks == null)
158: return;
159: for (Method preDestroy : toMethod(preDestroyCallbacks)) {
160: try {
161: preDestroy.invoke(instance);
162: } catch (Exception e) {
163: e = unwrap(e);
164: logger.log(Level.SEVERE,
165: "Error while calling pre destroy method", e);
166: }
167: }
168: }
169:
170: private List<Method> toMethod(List<CallbackMetaData> callbacks) {
171: List<String> methodsNotFound = new ArrayList<String>(1);
172: List<Method> methods = new ArrayList<Method>(callbacks.size());
173: for (CallbackMetaData callback : callbacks) {
174: Method method = toMethod(callback);
175: if (method != null) {
176: methods.add(method);
177: } else {
178: methodsNotFound.add(callback.toString());
179: }
180: }
181: if (!methodsNotFound.isEmpty()) {
182: throw new IllegalStateException(
183: "Callback methods not found " + methodsNotFound);
184: }
185: return methods;
186: }
187:
188: private Method toMethod(CallbackMetaData callback) {
189: try {
190: String className = callback.getClassName();
191: Class<?> clazz = classLoader.loadClass(className);
192: Method method = clazz.getDeclaredMethod(callback
193: .getMethod());
194: return method;
195: } catch (Exception e) {
196: return null;
197: }
198: }
199:
200: private boolean setProperty(Class clazz, String name,
201: Object propertyValue) {
202: Method method = findSetter(clazz, name, propertyValue);
203: if (method != null) {
204: try {
205: propertyValue = convert(method.getParameterTypes()[0],
206: propertyValue);
207: method.invoke(instance, propertyValue);
208: return true;
209: } catch (Exception e) {
210: return false;
211: }
212: }
213:
214: Field field = findField(clazz, name, propertyValue);
215: if (field != null) {
216: try {
217: propertyValue = convert(field.getType(), propertyValue);
218: field.set(instance, propertyValue);
219: return true;
220: } catch (Exception e) {
221: return false;
222: }
223: }
224:
225: return false;
226: }
227:
228: public Method findSetter(Class typeClass, String propertyName,
229: Object propertyValue) {
230: if (propertyName == null)
231: throw new NullPointerException("name is null");
232: if (propertyName.length() == 0)
233: throw new IllegalArgumentException(
234: "name is an empty string");
235:
236: String setterName = "set"
237: + Character.toUpperCase(propertyName.charAt(0));
238: if (propertyName.length() > 0) {
239: setterName += propertyName.substring(1);
240: }
241:
242: List<Method> methods = new ArrayList<Method>(Arrays
243: .asList(typeClass.getMethods()));
244: methods.addAll(Arrays.asList(typeClass.getDeclaredMethods()));
245: for (Method method : methods) {
246: if (method.getName().equals(setterName)) {
247: if (method.getParameterTypes().length == 0) {
248: continue;
249: }
250:
251: if (method.getParameterTypes().length > 1) {
252: continue;
253: }
254:
255: if (method.getReturnType() != Void.TYPE) {
256: continue;
257: }
258:
259: if (Modifier.isAbstract(method.getModifiers())) {
260: continue;
261: }
262:
263: if (!allowStatic
264: && Modifier.isStatic(method.getModifiers())) {
265: continue;
266: }
267:
268: Class methodParameterType = method.getParameterTypes()[0];
269: if (methodParameterType.isPrimitive()
270: && propertyValue == null) {
271: continue;
272: }
273:
274: if (!isInstance(methodParameterType, propertyValue)
275: && !isConvertable(methodParameterType,
276: propertyValue)) {
277: continue;
278: }
279:
280: if (!Modifier.isPublic(method.getModifiers())) {
281: setAccessible(method);
282: }
283:
284: return method;
285: }
286:
287: }
288: return null;
289: }
290:
291: public Field findField(Class typeClass, String propertyName,
292: Object propertyValue) {
293: if (propertyName == null)
294: throw new NullPointerException("name is null");
295: if (propertyName.length() == 0)
296: throw new IllegalArgumentException(
297: "name is an empty string");
298:
299: List<Field> fields = new ArrayList<Field>(Arrays
300: .asList(typeClass.getDeclaredFields()));
301: Class parent = typeClass.getSuperclass();
302: while (parent != null) {
303: fields.addAll(Arrays.asList(parent.getDeclaredFields()));
304: parent = parent.getSuperclass();
305: }
306:
307: for (Field field : fields) {
308: if (field.getName().equals(propertyName)) {
309:
310: if (!allowStatic
311: && Modifier.isStatic(field.getModifiers())) {
312: continue;
313: }
314:
315: Class fieldType = field.getType();
316: if (fieldType.isPrimitive() && propertyValue == null) {
317: continue;
318: }
319:
320: if (!isInstance(fieldType, propertyValue)
321: && !isConvertable(fieldType, propertyValue)) {
322: continue;
323: }
324:
325: if (!Modifier.isPublic(field.getModifiers())) {
326: setAccessible(field);
327: }
328:
329: return field;
330: }
331:
332: }
333: return null;
334: }
335:
336: private static void setAccessible(
337: final AccessibleObject accessibleObject) {
338: AccessController.doPrivileged(new PrivilegedAction<Object>() {
339: public Object run() {
340: accessibleObject.setAccessible(true);
341: return null;
342: }
343: });
344: }
345:
346: private static boolean isInstance(Class type, Object instance) {
347: if (type.isPrimitive()) {
348: // for primitives the insance can't be null
349: if (instance == null) {
350: return false;
351: }
352:
353: // verify instance is the correct wrapper type
354: if (type.equals(boolean.class)) {
355: return instance instanceof Boolean;
356: } else if (type.equals(char.class)) {
357: return instance instanceof Character;
358: } else if (type.equals(byte.class)) {
359: return instance instanceof Byte;
360: } else if (type.equals(short.class)) {
361: return instance instanceof Short;
362: } else if (type.equals(int.class)) {
363: return instance instanceof Integer;
364: } else if (type.equals(long.class)) {
365: return instance instanceof Long;
366: } else if (type.equals(float.class)) {
367: return instance instanceof Float;
368: } else if (type.equals(double.class)) {
369: return instance instanceof Double;
370: } else {
371: throw new AssertionError("Invalid primitve type: "
372: + type);
373: }
374: }
375:
376: return instance == null || type.isInstance(instance);
377: }
378:
379: private static boolean isConvertable(Class type,
380: Object propertyValue) {
381: return (propertyValue instanceof String && findEditor(type) != null);
382: }
383:
384: private Object convert(Class type, Object value) {
385: if (type == Object.class || !(value instanceof String)) {
386: return value;
387: }
388:
389: String stringValue = (String) value;
390: PropertyEditor editor = findEditor(type);
391: if (editor != null) {
392: editor.setAsText(stringValue);
393: value = editor.getValue();
394: }
395: return value;
396: }
397:
398: /**
399: * Locate a property editor for qiven class of object.
400: *
401: * @param type The target object class of the property.
402: * @return The resolved editor, if any. Returns null if a suitable editor
403: * could not be located.
404: */
405: private static PropertyEditor findEditor(Class type) {
406: if (type == null)
407: throw new NullPointerException("type is null");
408:
409: // try to locate this directly from the editor manager first.
410: PropertyEditor editor = PropertyEditorManager.findEditor(type);
411:
412: // we're outta here if we got one.
413: if (editor != null) {
414: return editor;
415: }
416:
417: // nothing found
418: return null;
419: }
420:
421: private Class<?> loadClass(String targetClass) {
422: try {
423: return classLoader.loadClass(targetClass);
424: } catch (ClassNotFoundException e) {
425: return null;
426: }
427: }
428:
429: private static Exception unwrap(Exception e) {
430: if (e instanceof InvocationTargetException
431: && e.getCause() instanceof Exception) {
432: e = (Exception) e.getCause();
433: }
434: return e;
435: }
436: }
|