Coverage Report - org.kuali.rice.core.util.GRLServiceInjectionPostProcessor
 
Classes in this File Line Coverage Branch Coverage Complexity
GRLServiceInjectionPostProcessor
0%
0/40
0%
0/18
5.333
GRLServiceInjectionPostProcessor$1
0%
0/3
N/A
5.333
GRLServiceInjectionPostProcessor$2
0%
0/3
N/A
5.333
GRLServiceInjectionPostProcessor$AnnotatedMember
0%
0/44
0%
0/22
5.333
 
 1  
 /*
 2  
  * Copyright 2005-2009 The Kuali Foundation
 3  
  * 
 4  
  * 
 5  
  * Licensed under the Educational Community License, Version 2.0 (the "License");
 6  
  * you may not use this file except in compliance with the License.
 7  
  * You may obtain a copy of the License at
 8  
  * 
 9  
  * http://www.opensource.org/licenses/ecl2.php
 10  
  * 
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.kuali.rice.core.util;
 18  
 
 19  
 import java.beans.PropertyDescriptor;
 20  
 import java.lang.reflect.AccessibleObject;
 21  
 import java.lang.reflect.Field;
 22  
 import java.lang.reflect.InvocationTargetException;
 23  
 import java.lang.reflect.Method;
 24  
 import java.util.HashMap;
 25  
 import java.util.LinkedList;
 26  
 import java.util.List;
 27  
 import java.util.Map;
 28  
 
 29  
 import javax.xml.namespace.QName;
 30  
 
 31  
 import org.apache.commons.lang.StringUtils;
 32  
 import org.apache.log4j.Logger;
 33  
 import org.kuali.rice.core.config.ConfigContext;
 34  
 import org.kuali.rice.core.resourceloader.GlobalResourceLoader;
 35  
 import org.kuali.rice.core.resourceloader.ResourceLoader;
 36  
 import org.springframework.beans.BeanUtils;
 37  
 import org.springframework.beans.BeansException;
 38  
 import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
 39  
 import org.springframework.util.ReflectionUtils;
 40  
 
 41  
 /**
 42  
  * This bean postprocessor initializes fields which are marked with the {@link RiceService} annotation
 43  
  * and are null after property injection and prior to init call, with a named Rice service obtained
 44  
  * from a specified (or global) resource loader. 
 45  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 46  
  *
 47  
  */
 48  
 // some of the implementation derived from PersistenceAnnotationBeanPostProcessor which is an
 49  
 // example of an InstantiationAwareBeanPostProcessorAdapter
 50  
 // http://fisheye1.atlassian.com/browse/springframework/spring/tiger/src/org/springframework/orm/jpa/spi/PersistenceAnnotationBeanPostProcessor.java?r=1.1
 51  0
 public class GRLServiceInjectionPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
 52  0
     private static final Logger LOG = Logger.getLogger(GRLServiceInjectionPostProcessor.class);
 53  
 
 54  
     // holds cached Class metadata
 55  0
     private Map<Class<?>, List<AnnotatedMember>> classMetadata = new HashMap<Class<?>, List<AnnotatedMember>>();
 56  
 
 57  
     @Override
 58  
     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
 59  0
         List<AnnotatedMember> metadata = findClassMetadata(bean.getClass());
 60  0
         for (AnnotatedMember member: metadata) {
 61  0
             Object value = member.read(bean);
 62  0
             if (value == null) { // only look up the service if the member value is null
 63  0
                 Object newValue = lookupRiceService(member.service);
 64  0
                 if (newValue != null) {
 65  
                     // if it is non-null, then inject it
 66  0
                     member.inject(bean, newValue);
 67  
                 }
 68  
             }
 69  0
         }
 70  0
         return super.postProcessBeforeInitialization(bean, beanName);
 71  
     }
 72  
     
 73  
     // Resolve the object against the application context
 74  
     protected Object lookupRiceService(RiceService annotation) {
 75  0
         String resourceLoader = annotation.resourceLoader();
 76  0
         String name = annotation.name();
 77  
         
 78  
         ResourceLoader rl;
 79  
         // null is not a constant expression as far as Java and annotations go
 80  
         // so we have to rely on the default of an empty string for resource loader name
 81  0
         if (StringUtils.isEmpty(resourceLoader)) {
 82  0
             LOG.error("Using global resource loader");
 83  0
             rl = GlobalResourceLoader.getResourceLoader();
 84  0
             if (rl == null) {
 85  
                 // ...not so good
 86  0
                 throw new RuntimeException("Global resource loader could not be obtained");
 87  
             }
 88  
         } else {
 89  0
             QName rlName = QName.valueOf(resourceLoader);
 90  
 
 91  0
             if (StringUtils.isBlank(rlName.getNamespaceURI())) {
 92  
                 // if they don't specify a namespace in the string just use the "current context" namespace
 93  0
                 rlName = new QName(ConfigContext.getCurrentContextConfig().getServiceNamespace(), rlName.getLocalPart());
 94  
             }
 95  0
             rl = GlobalResourceLoader.getResourceLoader(rlName);
 96  0
             if (rl == null) {
 97  0
                 throw new RuntimeException("Named resource loader not found: " + resourceLoader);
 98  
             }
 99  
         }
 100  
 
 101  0
         LOG.error("Looking up service for injection: " + name);
 102  0
         return rl.getService(QName.valueOf(name));
 103  
     }
 104  
 
 105  
     /**
 106  
      * Helper method to scan the properties of the bean and find members that need service injection. 
 107  
      * 
 108  
      * @param clazz the bean class
 109  
      * @return list of {@link RiceService}-annotated members
 110  
      */
 111  
     private synchronized List<AnnotatedMember> findClassMetadata(Class<? extends Object> clazz) {
 112  0
         List<AnnotatedMember> metadata = classMetadata.get(clazz);
 113  0
         if (metadata == null) {
 114  0
             final List<AnnotatedMember> newMetadata = new LinkedList<AnnotatedMember>();
 115  
 
 116  0
             ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
 117  
                 public void doWith(Field f) throws IllegalArgumentException, IllegalAccessException {
 118  0
                     addIfPresent(newMetadata, f);
 119  0
                 }
 120  
             });
 121  
         
 122  
             // TODO is it correct to walk up the hierarchy for methods? Otherwise inheritance
 123  
             // is implied? CL to resolve
 124  0
             ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
 125  
                 public void doWith(Method m) throws IllegalArgumentException, IllegalAccessException {
 126  0
                     addIfPresent(newMetadata, m);
 127  0
                 }
 128  
             });
 129  
             
 130  0
             metadata = newMetadata;
 131  0
             classMetadata.put(clazz, metadata);
 132  
         }
 133  0
         return metadata;
 134  
     }
 135  
     
 136  
     /**
 137  
      * Adds an AnnotatedMember for the member if it is actually annotated.
 138  
      * 
 139  
      * @param metadata the class metadata
 140  
      * @param ao the particular member (field or method)
 141  
      */
 142  
     private void addIfPresent(List<AnnotatedMember> metadata, AccessibleObject ao) {
 143  0
         RiceService annotation = ao.getAnnotation(RiceService.class);
 144  0
         if (annotation != null) {
 145  0
             metadata.add(new AnnotatedMember(ao, annotation));
 146  
         }
 147  0
     }
 148  
 
 149  
     /**
 150  
      * Class representing Rice service injection information about an annotated field.
 151  
      */
 152  0
     private final class AnnotatedMember {
 153  
         private final AccessibleObject member;
 154  
         private final RiceService service;
 155  
         
 156  0
         public AnnotatedMember(AccessibleObject member, RiceService service) {
 157  0
             this.member = member;
 158  0
             this.service = service;
 159  
 
 160  0
             if (service.name() == null) {
 161  0
                 throw new IllegalArgumentException("Service name must be specified in RiceService annotation");
 162  
             }
 163  0
         }
 164  
 
 165  
         public Object read(Object instance) {
 166  
             // I'm sure some handy utility class or code exists to easily set properties
 167  
             // but I couldn't find it quickly, so it's straight up reflection for now
 168  
             // working from model in inject
 169  
             try {
 170  0
                 if (!member.isAccessible()) {
 171  0
                     member.setAccessible(true);
 172  
                 }
 173  0
                 if (member instanceof Field) {
 174  0
                     return ((Field) member).get(instance);
 175  
                 }
 176  0
                 else if (member instanceof Method) {
 177  0
                     PropertyDescriptor pd = BeanUtils.findPropertyForMethod((Method) member);
 178  0
                     if (pd == null) {
 179  0
                         throw new IllegalArgumentException("Annotated was found on a method that did not resolve to a bean property: " + member);
 180  
                     }
 181  0
                     Method getter = pd.getReadMethod();
 182  0
                     if (getter == null) {
 183  
                         // we still want to support setter-only properties, so if there is no getter...then
 184  
                         // just assume the value is null and that we should set it
 185  0
                         return null;
 186  
                         //throw new IllegalArgumentException("No getter found for property " + pd.getName());
 187  
                     }
 188  0
                     return getter.invoke(instance, (Object[]) null); // it had better be a no-arg getter
 189  
                 }
 190  
                 else {
 191  0
                     throw new IllegalArgumentException("Cannot read unknown AccessibleObject type " + member);
 192  
                 }
 193  
             }
 194  0
             catch (IllegalAccessException ex) {
 195  0
                 throw new IllegalArgumentException("Cannot inject member " + member, ex);
 196  
             }
 197  0
             catch (InvocationTargetException ex) {
 198  
                 // Method threw an exception
 199  0
                 throw new IllegalArgumentException("Attempt to inject setter method " + member +
 200  
                                                    " resulted in an exception", ex);
 201  
             }
 202  
         }
 203  
 
 204  
         public void inject(Object instance, Object value) {
 205  
             try {
 206  0
                 if (!member.isAccessible()) {
 207  0
                     member.setAccessible(true);
 208  
                 }
 209  0
                 if (member instanceof Field) {
 210  0
                     ((Field) member).set(instance, value);
 211  
                 }
 212  0
                 else if (member instanceof Method) {
 213  0
                     PropertyDescriptor pd = BeanUtils.findPropertyForMethod((Method) member);
 214  0
                     if (pd == null) {
 215  0
                         throw new IllegalArgumentException("Annotated was found on a method that did not resolve to a bean property: " + member);
 216  
                     }
 217  0
                     Method setter = pd.getWriteMethod();
 218  0
                     if (setter == null) {
 219  0
                         throw new IllegalArgumentException("No setter found for property " + pd.getName());
 220  
                     }
 221  0
                     setter.invoke(instance, value); // it had better be a single-arg setter
 222  0
                 }
 223  
                 else {
 224  0
                     throw new IllegalArgumentException("Cannot inject unknown AccessibleObject type " + member);
 225  
                 }
 226  
             }
 227  0
             catch (IllegalAccessException ex) {
 228  0
                 throw new IllegalArgumentException("Cannot inject member " + member, ex);
 229  
             }
 230  0
             catch (InvocationTargetException ex) {
 231  
                 // Method threw an exception
 232  0
                 throw new IllegalArgumentException("Attempt to inject setter method " + member +
 233  
                                                    " resulted in an exception", ex);
 234  0
             }
 235  0
         }
 236  
     }
 237  
 }