Java tutorial
/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.spring.support; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.naming.InitialContext; import javax.naming.NamingException; import org.jboss.annotation.spring.Spring; import org.jboss.logging.Logger; import org.jboss.spring.util.Version; import org.jboss.spring.util.VersionProvider; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ListableBeanFactory; /** * Injects objects from bean factory located in JNDI at jndiName gained * from @Spring annotation's field jndiName. * It is applied to setter methods and fields annotated with @Spring annotation. * * @author <a href="mailto:ales.justin@genera-lynx.com">Ales Justin</a> * @author Marius Bogoevici * @see MethodComparator Excludes overridden @Spring annotated methods * Class type check is performed before actual setting. */ public abstract class SpringInjectionSupport { protected Logger log = Logger.getLogger(getClass()); private final Comparator<Method> METHOD_COMPARATOR = new MethodComparator(); protected Object inject(Object target) throws Exception { log.debug("Invoking Spring injection: " + target.getClass().getName()); Method[] methods = getAllMethods(target); for (Method m : methods) { Spring spring = m.getAnnotation(Spring.class); if (spring != null) { if (isSetterMethod(m)) { injectToMethod(target, m, spring); } else { log.warn("Spring annotation only allowed on setter methods."); } } } Field[] fields = getAllFields(target); for (Field f : fields) { Spring spring = f.getAnnotation(Spring.class); if (spring != null) { injectToField(target, f, spring); } } return target; } protected Method[] getAllMethods(Object bean) { Class<?> beanClass = bean.getClass(); Set<Method> methods = new TreeSet<Method>(METHOD_COMPARATOR); while (beanClass != Object.class) { methods.addAll(Arrays.asList(beanClass.getDeclaredMethods())); beanClass = beanClass.getSuperclass(); } return methods.toArray(new Method[methods.size()]); } protected Field[] getAllFields(Object bean) { Class<?> beanClass = bean.getClass(); List<Field> fields = new ArrayList<Field>(); while (beanClass != Object.class) { fields.addAll(Arrays.asList(beanClass.getDeclaredFields())); beanClass = beanClass.getSuperclass(); } return fields.toArray(new Field[fields.size()]); } private boolean isSetterMethod(Method m) { return m.getName().startsWith("set") && m.getParameterTypes().length == 1; } /** * Get jndi name for bean factory. * Simple check for null or empty string is applied. * You can override this in subclasses for any extra * jndi name handling. * * @param jndiName the current jndi name * @return jndiName parameter */ protected String getJndiName(String jndiName) { if (jndiName == null || jndiName.length() == 0) { throw new IllegalArgumentException("Empty BeanFactory jndi name."); } // On JBoss AS 7, custom bindings can be created only under java:/jboss // so this is what the deployer does // Append the prefix if the path is relative - should allow deployments that // worked in JBoss AS 5/6 to be portable in JBoss AS 7 if (VersionProvider.VERSION.compareTo(Version.AS_7) >= 0 && !jndiName.startsWith("java:")) { jndiName = "java:jboss/" + jndiName; } return jndiName; } private Object getObjectFromBeanFactory(Spring spring, String defaultBeanName, Class<?> beanType) throws Exception { String jndiName = getJndiName(spring.jndiName()); BeanFactory beanFactory = lookup(jndiName, BeanFactory.class); String beanName = spring.bean(); if (beanName != null && beanName.length() > 0) { return beanFactory.getBean(beanName, beanType); } else { // by type injection if (beanFactory instanceof ListableBeanFactory) { ListableBeanFactory lbf = (ListableBeanFactory) beanFactory; Map<?, ?> beans = lbf.getBeansOfType(beanType); if (beans.size() > 1) { Object bean = beans.get(defaultBeanName); if (bean == null) { throw new IllegalArgumentException("More than one bean of type: " + beanType); } return bean; } else if (beans.size() == 1) { return beans.values().iterator().next(); } else { throw new IllegalArgumentException("No such bean by type: " + beanType); } } else { // bean factory is not listable - use default bean name return beanFactory.getBean(defaultBeanName, beanType); } } } /** * @param jndiName the JNDI location of the Spring context relative to * 'java:' on JBoss AS5 and JBoss AS6 and 'java:jboss' on JBoss AS7 * @param expectedClass the expected type of the retrieved object * @return */ private <T> T lookup(String jndiName, Class<T> expectedClass) { Object instance; try { InitialContext initialContext = new InitialContext(); instance = initialContext.lookup(jndiName); if (!(expectedClass.isAssignableFrom(instance.getClass()))) { throw new IllegalArgumentException("Cannot retrieve an " + expectedClass.getName() + " from " + jndiName + " - a " + instance.getClass().getName() + " found instead"); } } catch (NamingException e) { throw new IllegalStateException(e); } return expectedClass.cast(instance); } private void injectToMethod(Object target, Method method, Spring spring) throws Exception { String defaultBeanName = getDefaultBeanName(method); Object bean = getObjectFromBeanFactory(spring, defaultBeanName, method.getParameterTypes()[0]); logInjection(spring, bean, target, method); method.setAccessible(true); method.invoke(target, bean); } protected String getDefaultBeanName(Method method) { StringBuffer buffer = new StringBuffer(); buffer.append(method.getName().substring(3, 3).toLowerCase()); buffer.append(method.getName().substring(4)); return buffer.toString(); } private void injectToField(Object target, Field field, Spring spring) throws Exception { String defaultBeanName = getDefaultBeanName(field); Object bean = getObjectFromBeanFactory(spring, defaultBeanName, field.getType()); logInjection(spring, bean, target, field); field.setAccessible(true); field.set(target, bean); } protected String getDefaultBeanName(Field field) { return field.getName(); } private void logInjection(Spring spring, Object bean, Object target, Member m) { log.debug("Injecting bean '" + spring.bean() + "' of class type " + bean.getClass().getName() + " into " + target + " via " + m); } /** * Equals on overridden methods. * Any other solution? */ private class MethodComparator implements Comparator<Method> { public int compare(Method m1, Method m2) { String name1 = m1.getName(); String name2 = m2.getName(); if (name1.equals(name2)) { Class<?> returnType1 = m1.getReturnType(); Class<?> returnType2 = m2.getReturnType(); Class<?>[] params1 = m1.getParameterTypes(); Class<?>[] params2 = m1.getParameterTypes(); if (params1.length == params2.length) { if (returnType1.equals(returnType2)) { int i; int length = params1.length; for (i = 0; i < length; i++) { if (!params1[i].equals(params2[i])) { break; } } //not equal if (i < length) { return params1[i].getName().compareTo(params2[i].getName()); } else { //overridden method if (m1.getAnnotation(Spring.class) != null) { log.warn("Found overridden @Spring annotated method: " + m1); } return 0; } } else { return returnType1.getName().compareTo(returnType2.getName()); } } else { return params1.length - params2.length; } } else { return name1.compareTo(name2); } } } }