/** * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.openejb.client; import javax.naming.Context; import javax.naming.NamingException; import java.beans.PropertyEditor; import java.beans.PropertyEditorManager; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; public class ClientInjectionProcessor<T> { private static final Logger logger = Logger.getLogger("OpenEJB.client"); private final Class<? extends T> beanClass; private final ClassLoader classLoader; private final List<Injection> injections; private final List<CallbackMetaData> postConstructCallbacks; private final List<CallbackMetaData> preDestroyCallbacks; private final Context context; private T instance; private boolean allowStatic; public ClientInjectionProcessor(final Class<? extends T> beanClass, final List<Injection> injections, final List<CallbackMetaData> postConstructMethods, final List<CallbackMetaData> preDestroyMethods, final Context context) { this.beanClass = beanClass; classLoader = beanClass.getClassLoader(); this.injections = injections; this.postConstructCallbacks = postConstructMethods; this.preDestroyCallbacks = preDestroyMethods; this.context = context; } public void allowStatic() { allowStatic = true; } public T createInstance() throws Exception { if (instance == null) { construct(); } return instance; } public T getInstance() { return instance; } private void construct() { final Map<Injection, Object> values = new HashMap<Injection, Object>(); for (final Injection injection : injections) { // only process injections for this class final Class<?> targetClass = loadClass(injection.getTargetClass()); if (targetClass == null) { continue; } if (!targetClass.isAssignableFrom(beanClass)) { continue; } try { final String jndiName = injection.getJndiName(); final Object object = context.lookup("java:comp/env/" + jndiName); values.put(injection, object); } catch (NamingException e) { logger.warning("Injection data not found in JNDI context: jndiName='" + injection.getJndiName() + "', target=" + injection.getTargetClass() + "/" + injection.getName()); } } try { instance = beanClass.newInstance(); } catch (Exception e) { throw new IllegalStateException("Error while creating bean " + beanClass.getName(), e); } final List<String> unsetProperties = new ArrayList<String>(); for (final Map.Entry<Injection, Object> entry : values.entrySet()) { final Injection injection = entry.getKey(); final Object value = entry.getValue(); final Class<?> targetClass = loadClass(injection.getTargetClass()); if (targetClass == null || !targetClass.isAssignableFrom(beanClass)) { continue; } if (!setProperty(targetClass, injection.getName(), value)) { unsetProperties.add(injection.getName()); } } if (unsetProperties.size() > 0) { for (final Object property : unsetProperties) { logger.warning("Injection: Unable to set property '" + property + "' in class " + beanClass.getName()); } } } public void postConstruct() throws Exception { if (instance == null) { throw new IllegalStateException("Instance has not been constructed"); } if (postConstructCallbacks == null) { return; } for (final Method postConstruct : toMethod(postConstructCallbacks)) { try { postConstruct.invoke(instance); } catch (Exception e) { e = unwrap(e); throw new Exception("Error while calling post construct method", e); } } } public void preDestroy() { if (instance == null) { return; } if (preDestroyCallbacks == null) { return; } for (final Method preDestroy : toMethod(preDestroyCallbacks)) { try { preDestroy.invoke(instance); } catch (Exception e) { e = unwrap(e); logger.log(Level.SEVERE, "Error while calling pre destroy method", e); } } } private List<Method> toMethod(final List<CallbackMetaData> callbacks) { final List<String> methodsNotFound = new ArrayList<String>(1); final List<Method> methods = new ArrayList<Method>(callbacks.size()); for (final CallbackMetaData callback : callbacks) { final Method method = toMethod(callback); if (method != null) { methods.add(method); } else { methodsNotFound.add(callback.toString()); } } if (!methodsNotFound.isEmpty()) { throw new IllegalStateException("Callback methods not found " + methodsNotFound); } return methods; } private Method toMethod(final CallbackMetaData callback) { try { final String className = callback.getClassName(); final Class<?> clazz = classLoader.loadClass(className); return clazz.getDeclaredMethod(callback.getMethod()); } catch (Exception e) { return null; } } private boolean setProperty(final Class clazz, final String name, Object propertyValue) { final Method method = findSetter(clazz, name, propertyValue); if (method != null) { try { propertyValue = convert(method.getParameterTypes()[0], propertyValue); method.invoke(instance, propertyValue); return true; } catch (Exception e) { return false; } } final Field field = findField(clazz, name, propertyValue); if (field != null) { try { propertyValue = convert(field.getType(), propertyValue); field.set(instance, propertyValue); return true; } catch (Exception e) { return false; } } return false; } public Method findSetter(final Class typeClass, final String propertyName, final Object propertyValue) { if (propertyName == null) { throw new NullPointerException("name is null"); } if (propertyName.length() == 0) { throw new IllegalArgumentException("name is an empty string"); } String setterName = "set" + Character.toUpperCase(propertyName.charAt(0)); if (propertyName.length() > 0) { setterName += propertyName.substring(1); } final List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods())); methods.addAll(Arrays.asList(typeClass.getDeclaredMethods())); for (final Method method : methods) { if (method.getName().equals(setterName)) { if (method.getParameterTypes().length == 0) { continue; } if (method.getParameterTypes().length > 1) { continue; } if (method.getReturnType() != Void.TYPE) { continue; } if (Modifier.isAbstract(method.getModifiers())) { continue; } if (!allowStatic && Modifier.isStatic(method.getModifiers())) { continue; } final Class methodParameterType = method.getParameterTypes()[0]; if (methodParameterType.isPrimitive() && propertyValue == null) { continue; } if (!isInstance(methodParameterType, propertyValue) && !isConvertable(methodParameterType, propertyValue)) { continue; } if (!Modifier.isPublic(method.getModifiers())) { setAccessible(method); } return method; } } return null; } public Field findField(final Class typeClass, final String propertyName, final Object propertyValue) { if (propertyName == null) { throw new NullPointerException("name is null"); } if (propertyName.length() == 0) { throw new IllegalArgumentException("name is an empty string"); } final List<Field> fields = new ArrayList<Field>(Arrays.asList(typeClass.getDeclaredFields())); Class parent = typeClass.getSuperclass(); while (parent != null) { fields.addAll(Arrays.asList(parent.getDeclaredFields())); parent = parent.getSuperclass(); } for (final Field field : fields) { if (field.getName().equals(propertyName)) { if (!allowStatic && Modifier.isStatic(field.getModifiers())) { continue; } final Class fieldType = field.getType(); if (fieldType.isPrimitive() && propertyValue == null) { continue; } if (!isInstance(fieldType, propertyValue) && !isConvertable(fieldType, propertyValue)) { continue; } if (!Modifier.isPublic(field.getModifiers())) { setAccessible(field); } return field; } } return null; } private static void setAccessible(final AccessibleObject accessibleObject) { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { accessibleObject.setAccessible(true); return null; } }); } private static boolean isInstance(final Class type, final Object instance) { if (type.isPrimitive()) { // for primitives the insance can't be null if (instance == null) { return false; } // verify instance is the correct wrapper type if (type.equals(boolean.class)) { return instance instanceof Boolean; } else if (type.equals(char.class)) { return instance instanceof Character; } else if (type.equals(byte.class)) { return instance instanceof Byte; } else if (type.equals(short.class)) { return instance instanceof Short; } else if (type.equals(int.class)) { return instance instanceof Integer; } else if (type.equals(long.class)) { return instance instanceof Long; } else if (type.equals(float.class)) { return instance instanceof Float; } else if (type.equals(double.class)) { return instance instanceof Double; } else { throw new AssertionError("Invalid primitve type: " + type); } } return instance == null || type.isInstance(instance); } private static boolean isConvertable(final Class type, final Object propertyValue) { return (propertyValue instanceof String && findEditor(type) != null); } private Object convert(final Class type, Object value) { if (type == Object.class || !(value instanceof String)) { return value; } final String stringValue = (String) value; final PropertyEditor editor = findEditor(type); if (editor != null) { editor.setAsText(stringValue); value = editor.getValue(); } return value; } /** * Locate a property editor for qiven class of object. * * @param type The target object class of the property. * @return The resolved editor, if any. Returns null if a suitable editor * could not be located. */ private static PropertyEditor findEditor(final Class type) { if (type == null) { throw new NullPointerException("type is null"); } // try to locate this directly from the editor manager first. final PropertyEditor editor = PropertyEditorManager.findEditor(type); // we're outta here if we got one. if (editor != null) { return editor; } // nothing found return null; } private Class<?> loadClass(final String targetClass) { try { return classLoader.loadClass(targetClass); } catch (ClassNotFoundException e) { return null; } } private static Exception unwrap(Exception e) { if (e instanceof InvocationTargetException && e.getCause() instanceof Exception) { e = (Exception) e.getCause(); } return e; } }