/* * JBoss, Home of Professional Open Source * Copyright 2013, Red Hat, Inc. and individual contributors * by the @authors tag. See the copyright.txt 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.richfaces.application; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.text.MessageFormat; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.el.ExpressionFactory; import javax.el.ValueExpression; import javax.faces.FacesException; import javax.faces.context.FacesContext; import org.richfaces.log.Logger; import org.richfaces.log.RichfacesLogger; import org.richfaces.resource.PostConstructResource; import org.richfaces.resource.ResourceParameter; import org.richfaces.resource.ResourceParameterELResolver; /** * @author Nick Belaevski * */ public class DependencyInjectorImpl implements DependencyInjector { private static final Logger LOGGER = RichfacesLogger.APPLICATION.getLogger(); private ConcurrentMap<Class<?>, IntrospectionData> classesCache = new ConcurrentHashMap<Class<?>, IntrospectionData>(); private void invokeMethod(Object bean, Method method) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { if (method != null) { method.setAccessible(true); method.invoke(bean); } } private boolean isUncheckedException(Class<?> type) { // JLS 2nd edition - 11.2 Compile-Time Checking of Exceptions return RuntimeException.class.isAssignableFrom(type) || Error.class.isAssignableFrom(type); } private void verifyPostConstructMethod(Method method) { // TODO - allow FacesContext to be passed if (method.getParameterTypes().length != 0) { throw new IllegalStateException(MessageFormat.format("Post-construction method {0} has one or more parameters", method.toString())); } if (!Void.TYPE.equals(method.getReturnType())) { throw new IllegalStateException(MessageFormat.format("Post-construction method {0} has incorrect return type", method.toString())); } if ((method.getModifiers() & Modifier.STATIC) != 0) { throw new IllegalStateException(MessageFormat.format("Post-construction method {0} is static", method.toString())); } Class<?>[] exceptionTypes = method.getExceptionTypes(); for (Class<?> exceptionType : exceptionTypes) { if (isUncheckedException(exceptionType)) { continue; } throw new IllegalStateException(MessageFormat.format("Post-construction method {0} throws checked exception", method.toString())); } } private void inspectMethod(Method method, Class<? extends Annotation> annotationClass, IntrospectionData introspectionData) { Annotation annotation = method.getAnnotation(annotationClass); if (annotation != null) { verifyPostConstructMethod(method); if (introspectionData.getPostConstructMethod() != null) { throw new IllegalStateException(MessageFormat.format( "There are two conflicting post-construction methods: {0} and {1}", method.toString(), introspectionData .getPostConstructMethod().toString())); } introspectionData.setPostConstructMethod(method); } } private void locatePostConstructMethods(Class<?> clazz, IntrospectionData introspectionData) { Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { inspectMethod(method, PostConstructResource.class, introspectionData); } Class<?> superclass = clazz.getSuperclass(); if (!Object.class.equals(superclass)) { locatePostConstructMethods(superclass, introspectionData); } } private void locateManagedPropertyFields(Class<?> clazz, Map<String, ResourceParameter> fieldsMap) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { ResourceParameter dependency = field.getAnnotation(ResourceParameter.class); if (dependency != null) { String propertyName = field.getName(); if (!fieldsMap.containsKey(propertyName)) { fieldsMap.put(propertyName, dependency); } } } Class<?> superclass = clazz.getSuperclass(); if (!Object.class.equals(superclass)) { locateManagedPropertyFields(superclass, fieldsMap); } } private <T extends Annotation> T getAnnotation(PropertyDescriptor descriptor, Class<T> annotationClass) { T annotation = null; Method writeMethod = descriptor.getWriteMethod(); if (writeMethod != null) { annotation = writeMethod.getAnnotation(annotationClass); } if (annotation == null) { Method readMethod = descriptor.getReadMethod(); if (readMethod != null) { annotation = readMethod.getAnnotation(annotationClass); } } return annotation; } private void locateManagedPropertyDescriptors(Class<?> clazz, IntrospectionData introspectionData, Map<String, ResourceParameter> injectableFields) { try { BeanInfo beanInfo = Introspector.getBeanInfo(clazz); if (beanInfo != null) { PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); if (descriptors != null) { for (PropertyDescriptor descriptor : descriptors) { String propertyName = descriptor.getName(); ResourceParameter dependency = injectableFields.get(propertyName); if (dependency == null) { dependency = getAnnotation(descriptor, ResourceParameter.class); } if (dependency != null) { Injector<?> injector = new PropertyDependencyInjector(descriptor, dependency); introspectionData.addInjector(propertyName, injector); } } } } } catch (IntrospectionException e) { if (LOGGER.isDebugEnabled()) { LOGGER.debug(e.getMessage(), e); } } finally { Introspector.flushFromCaches(clazz); } } protected IntrospectionData createIntrospectionData(Class<?> beanClass) { IntrospectionData introspectionData = new IntrospectionData(); Map<String, ResourceParameter> injectableFields = new HashMap<String, ResourceParameter>(); locateManagedPropertyFields(beanClass, injectableFields); locateManagedPropertyDescriptors(beanClass, introspectionData, injectableFields); locatePostConstructMethods(beanClass, introspectionData); return introspectionData; } public void inject(FacesContext context, Object bean) { Class<?> beanClass = bean.getClass(); IntrospectionData introspectionData = classesCache.get(beanClass); if (introspectionData == null) { introspectionData = createIntrospectionData(beanClass); classesCache.put(beanClass, introspectionData); } try { Map<String, Injector<?>> injectorsMap = introspectionData.getInjectorsMap(); if (!injectorsMap.isEmpty()) { for (Injector<?> injector : injectorsMap.values()) { injector.inject(context, bean); } } Method postConstructMethod = introspectionData.getPostConstructMethod(); if (postConstructMethod != null) { invokeMethod(bean, postConstructMethod); } } catch (IllegalArgumentException e) { throw new FacesException(e.getMessage(), e); } catch (IllegalAccessException e) { throw new FacesException(e.getMessage(), e); } catch (InvocationTargetException e) { throw new FacesException(e.getMessage(), e); } } private abstract static class Injector<T extends Annotation> { private PropertyDescriptor propertyDescriptor; private T dependency; public Injector(PropertyDescriptor propertyDescriptor, T dependency) { super(); this.propertyDescriptor = propertyDescriptor; this.dependency = dependency; } protected T getDependency() { return dependency; } protected PropertyDescriptor getPropertyDescriptor() { return propertyDescriptor; } protected abstract Object evaluateProperty(FacesContext context, Class<?> propertyType); public void inject(FacesContext context, Object bean) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { Method writeMethod = propertyDescriptor.getWriteMethod(); if (writeMethod != null) { writeMethod.invoke(bean, evaluateProperty(context, propertyDescriptor.getPropertyType())); } else { throw new IllegalStateException(MessageFormat.format("Write method for property {0} doesn't exist", propertyDescriptor.getName())); } } } private static final class PropertyDependencyInjector extends Injector<ResourceParameter> { public PropertyDependencyInjector(PropertyDescriptor propertyDescriptor, ResourceParameter dependency) { super(propertyDescriptor, dependency); } private Object getExpressionValue(FacesContext context, String expressionString, Class<?> expectedType) { ExpressionFactory expressionFactory = context.getApplication().getExpressionFactory(); ValueExpression expression = expressionFactory.createValueExpression(context.getELContext(), expressionString, expectedType); return expression.getValue(context.getELContext()); } protected Object evaluateProperty(FacesContext context, Class<?> propertyType) { Class<?> expectedType; if (!propertyType.isPrimitive()) { expectedType = Object.class; } else { expectedType = propertyType; } ResourceParameter resourceParameter = getDependency(); String expression = resourceParameter.expression(); String name = resourceParameter.name(); if (expression.length() != 0 && name.length() != 0) { throw new IllegalStateException(MessageFormat.format( "'name' and 'expression' should not be specified simultaneously: {0}", resourceParameter)); } Object propertyValue = null; if (expression.length() != 0) { propertyValue = getExpressionValue(context, expression, expectedType); } else { if (name.length() == 0) { name = getPropertyDescriptor().getName(); } Map<String, Object> parameters = (Map<String, Object>) context.getAttributes().get( ResourceParameterELResolver.CONTEXT_ATTRIBUTE_NAME); propertyValue = parameters.get(name); } if (propertyValue == null || "".equals(propertyValue)) { String defaultValue = resourceParameter.defaultValue(); if (defaultValue != null && defaultValue.length() != 0) { propertyValue = getExpressionValue(context, defaultValue, expectedType); } } if (propertyValue != null) { propertyValue = context.getApplication().getExpressionFactory().coerceToType(propertyValue, propertyType); } return propertyValue; } } private static final class IntrospectionData { private Method postConstructMethod = null; private Map<String, Injector<?>> injectorsMap = null; public Map<String, Injector<?>> getInjectorsMap() { if (injectorsMap != null) { return injectorsMap; } return Collections.emptyMap(); } public void addInjector(String propertyName, Injector<?> injector) { if (injectorsMap == null) { injectorsMap = new HashMap<String, Injector<?>>(); } injectorsMap.put(propertyName, injector); } public Method getPostConstructMethod() { return postConstructMethod; } public void setPostConstructMethod(Method postConstructMethod) { this.postConstructMethod = postConstructMethod; } } }