View Javadoc

1   /*
2    * Copyright 2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.student.common.spring;
17  
18  import java.beans.PropertyDescriptor;
19  import java.lang.reflect.Field;
20  import java.lang.reflect.Method;
21  import java.lang.reflect.Proxy;
22  import java.util.Set;
23  
24  import javax.annotation.Resource;
25  import javax.inject.Inject;
26  import javax.jws.WebService;
27  import javax.xml.namespace.QName;
28  
29  import org.apache.commons.lang.StringUtils;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  import org.springframework.beans.BeansException;
33  import org.springframework.beans.PropertyValues;
34  import org.springframework.beans.factory.BeanFactory;
35  import org.springframework.beans.factory.BeanFactoryAware;
36  import org.springframework.beans.factory.NoSuchBeanDefinitionException;
37  import org.springframework.beans.factory.annotation.Autowired;
38  import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
39  import org.springframework.beans.factory.annotation.Value;
40  import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
41  import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
42  import org.springframework.core.Ordered;
43  import org.springframework.core.PriorityOrdered;
44  import org.springframework.util.ReflectionUtils;
45  
46  /**
47   * 
48   * A Spring Bean PostProcessor designed for automatic lookup of auto wired
49   * dependencies through the Rice GlobalResourceLoader.
50   * 
51   * In Kuali Student when we create services they are registered onto the Kuali
52   * Service Bus where clients like the KRAD UI needs to consume them from.
53   * 
54   * This is a helper for resolving the services through the KSB using the
55   * namespace and local name defined on the ks-api service @WebService
56   * annotation.
57   * 
58   * @author Kuali Student Team
59   * 
60   */
61  public class WebServiceAwareSpringBeanPostProcessor extends AutowiredAnnotationBeanPostProcessor implements PriorityOrdered, BeanFactoryAware {
62  
63  	private static final Logger log = LoggerFactory
64  			.getLogger(WebServiceAwareSpringBeanPostProcessor.class);
65  	
66  	private BeanFactory beanFactory;
67  
68  	/**
69  	 * 
70  	 */
71  	public WebServiceAwareSpringBeanPostProcessor() {
72  	}
73  
74  	
75  	
76  	/* (non-Javadoc)
77  	 * @see org.springframework.core.Ordered#getOrder()
78  	 */
79  	@Override
80  	public int getOrder() {
81  		return Ordered.HIGHEST_PRECEDENCE;
82  	}
83  
84  
85  
86  	@Override
87  	public Object postProcessBeforeInitialization(Object bean, String beanName)
88  			throws BeansException {
89  		
90  		// intentionally does nothing
91  		return bean;
92  	}
93  
94  	
95  	
96  	/* (non-Javadoc)
97  	 * @see org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter#postProcessAfterInstantiation(java.lang.Object, java.lang.String)
98  	 */
99  	@Override
100 	public boolean postProcessAfterInstantiation(Object bean, String beanName)
101 			throws BeansException {
102 		
103 		Class<? extends Object> beanClass = bean.getClass();
104 		
105 		try {
106 			super.processInjection(bean);
107 			
108 		} catch (BeansException e2) {
109 			// any resolved beans from the local applicationContext will have been injected into the bean by now
110 			// so fall through.
111 			// we will only update the @Autowired fields that are null and declare @WebService annotations.
112 		}
113 
114 		Field[] fields = beanClass.getDeclaredFields();
115 
116 		for (Field field : fields) {
117 
118 			// check for the @Autowired, @Resource, or @Value annotation
119 			if (!fieldHasInjectionAnnotation(field))
120 				continue; // skip fields that don't have an injection annotation
121 
122 			
123 			Object currentValue = null;
124 			try {
125 
126 				ReflectionUtils.makeAccessible(field);
127 
128 				currentValue = field.get(bean);
129 
130 			} catch (IllegalArgumentException e1) {
131 				log.warn("get error", e1);
132 				continue;
133 
134 			} catch (SecurityException e1) {
135 				log.warn("get error", e1);
136 				continue;
137 			} catch (IllegalAccessException e1) {
138 				log.warn("get error", e1);
139 				continue;
140 			}
141 
142 			// Only resolve using KSB if the object value has not been set.
143 			if (currentValue != null)
144 				continue; // was handled already
145 
146 			Class<?> fieldType = field.getType();
147 			
148 			WebService webServiceAnnotation = (WebService) fieldType.getAnnotation(
149 					WebService.class);
150 			
151 			if (webServiceAnnotation != null) {
152 
153 				// we can only auto resolve if the @WebService annotation is
154 				// present.
155 
156 				
157 				String namespace = webServiceAnnotation.targetNamespace();
158 				String serviceName = webServiceAnnotation.serviceName();
159 				
160 				if (serviceName.isEmpty())
161 					serviceName = webServiceAnnotation.name();
162 
163 				if (namespace != null) {
164 					
165 					// First check to see if the application context has a reference 
166 					// using the serivceName
167 					
168 					try {
169 					
170 						Object service = null;
171                         try {
172 	                        service = beanFactory.getBean(serviceName, fieldType);
173                         } catch (NoSuchBeanDefinitionException e) {
174                         	service = null;
175                         	// fall through
176                         }
177 					
178 						if (service != null) {
179 							injectServiceReference(bean, beanName, field, service, " from ApplicationContext using BeanName: " + serviceName);
180 							continue; // skip to the next field
181 						}
182 						// else service == null
183 							// now try to resolve using the ksb
184 					
185 
186 							final QName name = new QName(namespace, serviceName);
187 
188 					
189 						
190 						try {
191 								SerializableProxyInvokationHandler invocationHandler = new SerializableProxyInvokationHandler();
192 								
193 								invocationHandler.setServiceName(name);
194 								
195 								service = Proxy
196 										.newProxyInstance(
197 												getClass().getClassLoader(),
198 												new Class[] { fieldType },
199 												invocationHandler);
200 
201 								// now we have the service, try to inject it.
202 								injectServiceReference(bean, beanName, field, service, " from KSB using QName: " + name);
203 								
204 						} catch (NullPointerException e1) {
205 							log.warn("RiceResourceLoader is not configured/initialized properly.", e1);
206 							// skip to the next field.
207 							continue;
208 						}
209 						
210 						
211 						
212 					
213 						
214 					} catch (Exception e) {
215 						log.error(
216 								"failed to lookup resource in GlobalResourceLoader ("
217 										+ namespace + ", " + serviceName + ")",
218 								e);
219 						continue;
220 					}
221 				}
222 
223 			}
224 		}
225 		
226 		// skip over any of the other post processors
227 		return false;
228 	}
229 
230 	
231 
232 	
233 
234 
235 
236 	/*
237 	 * Inject the service reference into the bean.
238 	 * 
239 	 * Logic has been split out to let the proxy service be used
240 	 * or if there is a bean in the application context that has the local name use that.
241 	 * 
242 	 */
243 	private void injectServiceReference(Object bean, String beanName,  
244             Field field,
245             Object service, String message) throws IllegalArgumentException, IllegalAccessException {
246 		
247 		String fieldName = field.getName();
248 
249 		String setterMethodName = "set" + StringUtils.capitalize(fieldName);
250 		
251 		Class<? extends Object> beanClass = bean.getClass();
252 		
253 		Class<?> fieldType = field.getType();
254 		
255 		try {
256 
257 			/*
258 			 * We use the set method because even though we can reach in and set the bean
259 			 * if it is final we still get an exception.
260 			 * This way it will always work.
261 			 * And since the bean's are presently being wired using XML the setters will exist.
262 			 * 
263 			 */
264 			Method setterMethod = beanClass.getMethod(
265 					setterMethodName, field.getType());
266 
267 			ReflectionUtils.invokeMethod(setterMethod, bean,
268 					service);
269 
270 			log.warn("RESOLVED: beanName = "  + beanName + ", fieldName = " + field.getName() + ", fieldType = " + fieldType.getName() + message);
271 			
272 			
273 		} catch (IllegalArgumentException e) {
274 			log.warn("set error", e);
275 		} catch (SecurityException e) {
276 			log.warn("set error", e);
277 		} catch (NoSuchMethodException e) {
278 			
279 			// try the field
280 			ReflectionUtils.makeAccessible(field);
281 			
282 			field.set(bean, service);
283 			log.warn("RESOLVED: beanName = "  + beanName + ", fieldName = " + field.getName() + ", fieldType = " + fieldType.getName() + message);
284 		}
285 	    
286     }
287 
288 
289 
290 	/* (non-Javadoc)
291 	 * @see org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#setBeanFactory(org.springframework.beans.factory.BeanFactory)
292 	 */
293     @Override
294     public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
295 	    this.beanFactory = beanFactory;
296 		super.setBeanFactory(beanFactory);
297     }
298 
299 
300 
301 	@Override
302 	public Object postProcessAfterInitialization(Object bean, String beanName)
303 			throws BeansException {
304 
305 		
306 		return bean;
307 	}
308 
309 	private boolean fieldHasInjectionAnnotation(Field field) {
310 
311 		// @Autowired, @Resource, or @Value annotation
312 		if (field.getAnnotation(Autowired.class) != null) {
313 			log.debug("detected @Autowired annotation on field: "
314 					+ field.getName());
315 			return true;
316 		}
317 		if (field.getAnnotation(Value.class) != null) {
318 			log.debug("detected @Value annotation on field: " + field.getName());
319 			return true;
320 		}
321 		if (field.getAnnotation(Resource.class) != null) {
322 			log.debug("detected @Resource annotation on field: "
323 					+ field.getName());
324 			return true;
325 		}
326 		if (field.getAnnotation(Inject.class) != null) {
327 			log.debug("detected @Inject annotation on field: "
328 					+ field.getName());
329 			return true;
330 		}
331 
332 		// not an injectable annotation.
333 		return false;
334 
335 	}
336 	
337 	
338 
339 }