1 /*
2 * Copyright (c) 2002-2006 by OpenSymphony
3 * All rights reserved.
4 */
5 package com.opensymphony.xwork2.spring;
6
7 import java.util.HashMap;
8 import java.util.Map;
9
10 import org.springframework.beans.BeansException;
11 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
12 import org.springframework.beans.factory.UnsatisfiedDependencyException;
13 import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
14 import org.springframework.context.ApplicationContext;
15 import org.springframework.context.ApplicationContextAware;
16 import org.springframework.context.ConfigurableApplicationContext;
17 import org.springframework.context.support.ClassPathXmlApplicationContext;
18
19 import com.opensymphony.xwork2.ObjectFactory;
20 import com.opensymphony.xwork2.inject.Inject;
21 import com.opensymphony.xwork2.util.logging.Logger;
22 import com.opensymphony.xwork2.util.logging.LoggerFactory;
23
24 /**
25 * Simple implementation of the ObjectFactory that makes use of Spring's application context if one has been configured,
26 * before falling back on the default mechanism of instantiating a new class using the class name. <p/> In order to use
27 * this class in your application, you will need to instantiate a copy of this class and set it as XWork's ObjectFactory
28 * before the xwork.xml file is parsed. In a servlet environment, this could be done using a ServletContextListener.
29 *
30 * @author Simon Stewart (sms@lateral.net)
31 */
32 public class SpringObjectFactory extends ObjectFactory implements ApplicationContextAware {
33 private static final Logger LOG = LoggerFactory.getLogger(SpringObjectFactory.class);
34
35 protected ApplicationContext appContext;
36 protected AutowireCapableBeanFactory autoWiringFactory;
37 protected int autowireStrategy = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
38 private Map classes = new HashMap();
39 private boolean useClassCache = true;
40
41 @Inject(value="applicationContextPath",required=false)
42 public void setApplicationContextPath(String ctx) {
43 if (ctx != null) {
44 setApplicationContext(new ClassPathXmlApplicationContext(ctx));
45 }
46 }
47
48 /**
49 * Set the Spring ApplicationContext that should be used to look beans up with.
50 *
51 * @param appContext The Spring ApplicationContext that should be used to look beans up with.
52 */
53 public void setApplicationContext(ApplicationContext appContext)
54 throws BeansException {
55 this.appContext = appContext;
56 autoWiringFactory = findAutoWiringBeanFactory(this.appContext);
57 }
58
59 /**
60 * Sets the autowiring strategy
61 *
62 * @param autowireStrategy
63 */
64 public void setAutowireStrategy(int autowireStrategy) {
65 switch (autowireStrategy) {
66 case AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT:
67 LOG.info("Setting autowire strategy to autodetect");
68 this.autowireStrategy = autowireStrategy;
69 break;
70 case AutowireCapableBeanFactory.AUTOWIRE_BY_NAME:
71 LOG.info("Setting autowire strategy to name");
72 this.autowireStrategy = autowireStrategy;
73 break;
74 case AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE:
75 LOG.info("Setting autowire strategy to type");
76 this.autowireStrategy = autowireStrategy;
77 break;
78 case AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR:
79 LOG.info("Setting autowire strategy to constructor");
80 this.autowireStrategy = autowireStrategy;
81 break;
82 default:
83 throw new IllegalStateException("Invalid autowire type set");
84 }
85 }
86
87 public int getAutowireStrategy() {
88 return autowireStrategy;
89 }
90
91
92 /**
93 * If the given context is assignable to AutowireCapbleBeanFactory or contains a parent or a factory that is, then
94 * set the autoWiringFactory appropriately.
95 *
96 * @param context
97 */
98 protected AutowireCapableBeanFactory findAutoWiringBeanFactory(ApplicationContext context) {
99 if (context instanceof AutowireCapableBeanFactory) {
100 // Check the context
101 return (AutowireCapableBeanFactory) context;
102 } else if (context instanceof ConfigurableApplicationContext) {
103 // Try and grab the beanFactory
104 return ((ConfigurableApplicationContext) context).getBeanFactory();
105 } else if (context.getParent() != null) {
106 // And if all else fails, try again with the parent context
107 return findAutoWiringBeanFactory(context.getParent());
108 }
109 return null;
110 }
111
112 /**
113 * Looks up beans using Spring's application context before falling back to the method defined in the {@link
114 * ObjectFactory}.
115 *
116 * @param beanName The name of the bean to look up in the application context
117 * @param extraContext
118 * @return A bean from Spring or the result of calling the overridden
119 * method.
120 * @throws Exception
121 */
122 public Object buildBean(String beanName, Map extraContext, boolean injectInternal) throws Exception {
123 Object o = null;
124 try {
125 o = appContext.getBean(beanName);
126 } catch (NoSuchBeanDefinitionException e) {
127 Class beanClazz = getClassInstance(beanName);
128 o = buildBean(beanClazz, extraContext);
129 }
130 if (injectInternal) {
131 injectInternalBeans(o);
132 }
133 return o;
134 }
135
136 /**
137 * @param clazz
138 * @param extraContext
139 * @throws Exception
140 */
141 public Object buildBean(Class clazz, Map extraContext) throws Exception {
142 Object bean;
143
144 try {
145 bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
146 } catch (UnsatisfiedDependencyException e) {
147 // Fall back
148 bean = super.buildBean(clazz, extraContext);
149 }
150
151 bean = autoWiringFactory.applyBeanPostProcessorsBeforeInitialization(bean, bean.getClass().getName());
152 // We don't need to call the init-method since one won't be registered.
153 bean = autoWiringFactory.applyBeanPostProcessorsAfterInitialization(bean, bean.getClass().getName());
154 return autoWireBean(bean, autoWiringFactory);
155 }
156
157 public Object autoWireBean(Object bean) {
158 return autoWireBean(bean, autoWiringFactory);
159 }
160
161 /**
162 * @param bean
163 * @param autoWiringFactory
164 */
165 public Object autoWireBean(Object bean, AutowireCapableBeanFactory autoWiringFactory) {
166 if (autoWiringFactory != null) {
167 autoWiringFactory.autowireBeanProperties(bean,
168 autowireStrategy, false);
169 }
170 if (bean instanceof ApplicationContextAware) {
171 ((ApplicationContextAware) bean).setApplicationContext(appContext);
172 }
173
174 injectInternalBeans(bean);
175
176 return bean;
177 }
178
179 public Class getClassInstance(String className) throws ClassNotFoundException {
180 Class clazz = null;
181 if (useClassCache) {
182 synchronized(classes) {
183 // this cache of classes is needed because Spring sucks at dealing with situations where the
184 // class instance changes
185 clazz = (Class) classes.get(className);
186 }
187 }
188
189 if (clazz == null) {
190 if (appContext.containsBean(className)) {
191 clazz = appContext.getBean(className).getClass();
192 } else {
193 clazz = super.getClassInstance(className);
194 }
195
196 if (useClassCache) {
197 synchronized(classes) {
198 classes.put(className, clazz);
199 }
200 }
201 }
202
203 return clazz;
204 }
205
206 /**
207 * This method sets the ObjectFactory used by XWork to this object. It's best used as the "init-method" of a Spring
208 * bean definition in order to hook Spring and XWork together properly (as an alternative to the
209 * org.apache.struts2.spring.lifecycle.SpringObjectFactoryListener)
210 * @deprecated Since 2.1 as it isn't necessary
211 */
212 public void initObjectFactory() {
213 // not necessary anymore
214 }
215
216 /**
217 * Allows for ObjectFactory implementations that support
218 * Actions without no-arg constructors.
219 *
220 * @return false
221 */
222 public boolean isNoArgConstructorRequired() {
223 return false;
224 }
225
226 /**
227 * Enable / disable caching of classes loaded by Spring.
228 *
229 * @param useClassCache
230 */
231 public void setUseClassCache(boolean useClassCache) {
232 this.useClassCache = useClassCache;
233 }
234 }