blob: 9dead67ee687be889e5dd37c9d9dc812565575c4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.opensymphony.xwork2.spring;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* Simple implementation of the ObjectFactory that makes use of Spring's application context if one has been configured,
* before falling back on the default mechanism of instantiating a new class using the class name.
* </p>
*
* <p>
* In order to use this class in your application, you will need to instantiate a copy of this class and set it as XWork's ObjectFactory
* before the xwork.xml file is parsed. In a servlet environment, this could be done using a ServletContextListener.
* </p>
*
* @author Simon Stewart (sms@lateral.net)
*/
public class SpringObjectFactory extends ObjectFactory implements ApplicationContextAware {
private static final Logger LOG = LogManager.getLogger(SpringObjectFactory.class);
protected ApplicationContext appContext;
protected AutowireCapableBeanFactory autoWiringFactory;
protected int autowireStrategy = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
private final Map<String, Object> classes = new HashMap<>();
private boolean useClassCache = true;
private boolean alwaysRespectAutowireStrategy = false;
/**
* This is temporary solution, after validating can be removed
* @since 2.3.18
*/
@Deprecated
private boolean enableAopSupport = false;
public SpringObjectFactory() {
}
@Inject(value="applicationContextPath",required=false)
public void setApplicationContextPath(String ctx) {
if (ctx != null) {
setApplicationContext(new ClassPathXmlApplicationContext(ctx));
}
}
@Inject(value = "enableAopSupport", required = false)
public void setEnableAopSupport(String enableAopSupport) {
this.enableAopSupport = BooleanUtils.toBoolean(enableAopSupport);
}
/**
* Set the Spring ApplicationContext that should be used to look beans up with.
*
* @param appContext The Spring ApplicationContext that should be used to look beans up with.
*/
public void setApplicationContext(ApplicationContext appContext) throws BeansException {
this.appContext = appContext;
autoWiringFactory = findAutoWiringBeanFactory(this.appContext);
}
/**
* Sets the autowiring strategy
*
* @param autowireStrategy the autowire strategy
*/
public void setAutowireStrategy(int autowireStrategy) {
switch (autowireStrategy) {
case AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT:
LOG.info("Setting autowire strategy to autodetect");
this.autowireStrategy = autowireStrategy;
break;
case AutowireCapableBeanFactory.AUTOWIRE_BY_NAME:
LOG.info("Setting autowire strategy to name");
this.autowireStrategy = autowireStrategy;
break;
case AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE:
LOG.info("Setting autowire strategy to type");
this.autowireStrategy = autowireStrategy;
break;
case AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR:
LOG.info("Setting autowire strategy to constructor");
this.autowireStrategy = autowireStrategy;
break;
case AutowireCapableBeanFactory.AUTOWIRE_NO:
LOG.info("Setting autowire strategy to none");
this.autowireStrategy = autowireStrategy;
break;
default:
throw new IllegalStateException("Invalid autowire type set");
}
}
public int getAutowireStrategy() {
return autowireStrategy;
}
/**
* If the given context is assignable to AutowireCapbleBeanFactory or contains a parent or a factory that is, then
* set the autoWiringFactory appropriately.
*
* @param context the application context
*
* @return the bean factory
*/
protected AutowireCapableBeanFactory findAutoWiringBeanFactory(ApplicationContext context) {
if (context instanceof AutowireCapableBeanFactory) {
// Check the context
return (AutowireCapableBeanFactory) context;
} else if (context instanceof ConfigurableApplicationContext) {
// Try and grab the beanFactory
return ((ConfigurableApplicationContext) context).getBeanFactory();
} else if (context.getParent() != null) {
// And if all else fails, try again with the parent context
return findAutoWiringBeanFactory(context.getParent());
}
return null;
}
/**
* Looks up beans using Spring's application context before falling back to the method defined in the {@link
* ObjectFactory}.
*
* @param beanName The name of the bean to look up in the application context
* @param extraContext additional context parameters
* @return A bean from Spring or the result of calling the overridden
* method.
* @throws Exception in case of any errors
*/
@Override
public Object buildBean(String beanName, Map<String, Object> extraContext, boolean injectInternal) throws Exception {
Object o;
if (appContext.containsBean(beanName)) {
o = appContext.getBean(beanName);
} else {
Class beanClazz = getClassInstance(beanName);
o = buildBean(beanClazz, extraContext);
}
if (injectInternal) {
injectInternalBeans(o);
}
return o;
}
/**
* @param clazz class of bean
* @param extraContext additional context parameters
* @return bean
* @throws Exception in case of any errors
*/
@Override
public Object buildBean(Class clazz, Map<String, Object> extraContext) throws Exception {
Object bean;
try {
// Decide to follow autowire strategy or use the legacy approach which mixes injection strategies
if (alwaysRespectAutowireStrategy) {
// Leave the creation up to Spring
bean = autoWiringFactory.createBean(clazz, autowireStrategy, false);
injectApplicationContext(bean);
return injectInternalBeans(bean);
} else if (enableAopSupport) {
bean = autoWiringFactory.createBean(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
bean = autoWireBean(bean, autoWiringFactory);
bean = autoWiringFactory.initializeBean(bean, bean.getClass().getName());
return bean;
} else {
bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
bean = autoWiringFactory.initializeBean(bean, bean.getClass().getName());
return autoWireBean(bean, autoWiringFactory);
}
} catch (UnsatisfiedDependencyException e) {
LOG.error("Error building bean", e);
// Fall back
return autoWireBean(super.buildBean(clazz, extraContext), autoWiringFactory);
}
}
public Object autoWireBean(Object bean) {
return autoWireBean(bean, autoWiringFactory);
}
/**
* @param bean the bean to be autowired
* @param autoWiringFactory the autowiring factory
*
* @return bean
*/
public Object autoWireBean(Object bean, AutowireCapableBeanFactory autoWiringFactory) {
if (autoWiringFactory != null) {
autoWiringFactory.autowireBeanProperties(bean, autowireStrategy, false);
}
injectApplicationContext(bean);
injectInternalBeans(bean);
return bean;
}
private void injectApplicationContext(Object bean) {
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(appContext);
}
}
public Class getClassInstance(String className) throws ClassNotFoundException {
Class clazz = null;
if (useClassCache) {
synchronized(classes) {
// this cache of classes is needed because Spring sucks at dealing with situations where the
// class instance changes
clazz = (Class) classes.get(className);
}
}
if (clazz == null) {
if (appContext.containsBean(className)) {
clazz = appContext.getBean(className).getClass();
} else {
clazz = super.getClassInstance(className);
}
if (useClassCache) {
synchronized(classes) {
classes.put(className, clazz);
}
}
}
return clazz;
}
/**
* Allows for ObjectFactory implementations that support
* Actions without no-arg constructors.
*
* @return false
*/
@Override
public boolean isNoArgConstructorRequired() {
return false;
}
/**
* Enable / disable caching of classes loaded by Spring.
*
* @param useClassCache enable / disable class cache
*/
public void setUseClassCache(boolean useClassCache) {
this.useClassCache = useClassCache;
}
/**
* Determines if the autowire strategy is always followed when creating beans
*
* @param alwaysRespectAutowireStrategy True if the strategy is always used
*/
public void setAlwaysRespectAutowireStrategy(boolean alwaysRespectAutowireStrategy) {
this.alwaysRespectAutowireStrategy = alwaysRespectAutowireStrategy;
}
}