/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.config.type;
import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.InjectionTarget;
import org.w3c.dom.Node;
import com.caucho.config.ConfigException;
import com.caucho.config.DependencyBean;
import com.caucho.config.TagName;
import com.caucho.config.annotation.DisableConfig;
import com.caucho.config.annotation.NonEL;
import com.caucho.config.attribute.AddAttribute;
import com.caucho.config.attribute.Attribute;
import com.caucho.config.attribute.CreateAttribute;
import com.caucho.config.attribute.ProgramAttribute;
import com.caucho.config.attribute.PropertyAttribute;
import com.caucho.config.attribute.SetterAttribute;
import com.caucho.config.attribute.TextAttribute;
import com.caucho.config.inject.InjectManager;
import com.caucho.config.inject.InjectionTargetBuilder;
import com.caucho.config.inject.ManagedBeanImpl;
import com.caucho.config.inject.OwnerCreationalContext;
import com.caucho.config.program.ConfigProgram;
import com.caucho.config.program.PropertyStringProgram;
import com.caucho.config.types.RawString;
import com.caucho.config.xml.XmlBeanAttribute;
import com.caucho.config.xml.XmlBeanConfig;
import com.caucho.config.xml.XmlConfigContext;
import com.caucho.util.L10N;
import com.caucho.vfs.Dependency;
import com.caucho.vfs.PersistentDependency;
import com.caucho.xml.QName;
import com.caucho.xml.QNode;
/**
* Represents an inline bean type for configuration.
*/
public class InlineBeanType<T> extends ConfigType<T>
{
private static final L10N L = new L10N(InlineBeanType.class);
private static final Logger log
= Logger.getLogger(InlineBeanType.class.getName());
private static final String RESIN_NS
= "http://caucho.com/ns/resin";
public static final QName TEXT = new QName("#text");
public static final QName VALUE = new QName("value");
private static final Object _introspectLock = new Object();
private final Class<T> _beanClass;
private ConcurrentHashMap<QName,Attribute> _nsAttributeMap
= new ConcurrentHashMap<QName,Attribute>();
private HashMap<String,Attribute> _attributeMap
= new HashMap<String,Attribute>();
private Constructor<T> _stringConstructor;
private Method _valueOf;
private Method _setParent;
private Method _replaceObject;
private Method _setConfigLocation;
private Method _setConfigUriLocation;
private Method _setConfigNode;
private Attribute _addText;
private Attribute _addProgram;
private Attribute _addContentProgram;
private Attribute _addBean; // add(Object)
private Attribute _setProperty;
private boolean _isEL;
private HashMap<Class<?>,Attribute> _addMethodMap
= new HashMap<Class<?>,Attribute>();
private Attribute _addCustomBean;
private AnnotatedType<T> _annotatedType;
private ManagedBeanImpl<T> _bean;
private InjectionTarget<T> _injectionTarget;
//private ArrayList<ConfigProgram> _injectList;
private ArrayList<ConfigProgram> _initList;
private boolean _isIntrospecting;
private boolean _isIntrospected;
private ArrayList<InlineBeanType<?>> _pendingChildList
= new ArrayList<InlineBeanType<?>>();
public InlineBeanType(Class<T> beanClass)
{
_beanClass = beanClass;
if (EnvBean.class.isAssignableFrom(beanClass))
setEnvBean(true);
}
/**
* Returns the given type.
*/
@Override
public Class<T> getType()
{
return _beanClass;
}
@Override
public boolean isEL()
{
return _isEL;
}
protected void setAddCustomBean(Attribute addCustomBean)
{
_addCustomBean = addCustomBean;
}
protected void setAddAnnotation(Attribute addAnnotation)
{
}
/**
* Creates a new instance
*/
@Override
public Object create(Object parent, QName name)
{
try {
InjectManager cdiManager
= InjectManager.create(_beanClass.getClassLoader());
if (_injectionTarget == null) {
if (_beanClass.isInterface())
throw new ConfigException(L.l("{0} cannot be instantiated because it is an interface",
_beanClass.getName()));
AnnotatedType<T> type = getAnnotatedType();
InjectionTargetBuilder<T> builder
= new InjectionTargetBuilder<T>(cdiManager, type);
// server/2m09
// builder.setGenerateInterception(false);
_injectionTarget = builder;
// _bean.getInjectionPoints();
}
InjectionTarget<T> injection = _injectionTarget;
CreationalContext<T> env = new OwnerCreationalContext<T>(_bean);
T bean = injection.produce(env);
injection.inject(bean, env);
if (_setParent != null
&& parent != null
&& _setParent.getParameterTypes()[0].isAssignableFrom(parent.getClass())) {
try {
_setParent.invoke(bean, parent);
} catch (IllegalArgumentException e) {
throw ConfigException.create(_setParent,
L.l("{0}: setParent value of '{1}' is not valid",
bean, parent),
e);
} catch (Exception e) {
throw ConfigException.create(_setParent, e);
}
}
return bean;
} catch (Exception e) {
throw ConfigException.create(e);
}
}
private AnnotatedType<T> getAnnotatedType()
{
if (_annotatedType == null) {
InjectManager cdiManager
= InjectManager.create(_beanClass.getClassLoader());
_annotatedType = cdiManager.createAnnotatedType(_beanClass);
}
return _annotatedType;
}
/**
* Returns a constructor with a given number of arguments
*/
@Override
public Constructor<T> getConstructor(int count)
{
for (Constructor<?> ctor : _beanClass.getConstructors()) {
if (ctor.getParameterTypes().length == count)
return (Constructor<T>) ctor;
}
throw new ConfigException(L.l("{0} does not have any constructor with {1} arguments",
this, count));
}
/**
* Called before the children are configured.
*/
@Override
public void beforeConfigure(XmlConfigContext env, Object bean, Node node)
{
super.beforeConfigure(env, bean, node);
if (_setConfigNode != null) {
try {
_setConfigNode.invoke(bean, node);
} catch (Exception e) {
throw ConfigException.create(e);
}
}
if (_setConfigLocation != null && node instanceof QNode) {
String filename = ((QNode) node).getFilename();
int line = ((QNode) node).getLine();
try {
// _setConfigLocation.invoke(bean, filename, line);
_setConfigLocation.invoke(bean, filename, line);
} catch (Exception e) {
throw ConfigException.create(e);
}
}
if (_setConfigUriLocation != null && node instanceof QNode) {
String uri = ((QNode) node).getBaseURI();
int line = ((QNode) node).getLine();
try {
_setConfigUriLocation.invoke(bean, uri, line);
} catch (Exception e) {
throw ConfigException.create(e);
}
}
if (bean instanceof DependencyBean) {
DependencyBean dependencyBean = (DependencyBean) bean;
ArrayList<Dependency> dependencyList = env.getDependencyList();
if (dependencyList != null) {
for (Dependency depend : dependencyList) {
dependencyBean.addDependency((PersistentDependency) depend);
}
}
}
}
/**
* Returns the attribute based on the given name.
*/
@Override
public Attribute getAttribute(QName name)
{
Attribute attr = _nsAttributeMap.get(name);
if (attr == null) {
attr = getAttributeImpl(name);
if (attr != null)
_nsAttributeMap.put(name, attr);
}
return attr;
}
protected Attribute getAttributeImpl(QName name)
{
// server/2r10 vs jms/2193
// attr = _attributeMap.get(name.getLocalName().toLowerCase(Locale.ENGLISH));
Attribute attr = _attributeMap.get(name.getLocalName());
if (attr != null)
return attr;
String uri = name.getNamespaceURI();
if (uri == null || ! uri.startsWith("urn:java"))
return null;
Class<?> cl = createClass(name);
if (cl != null) {
attr = getAddAttribute(cl);
if (attr != null)
return attr;
}
// server/13jm - environment beans have priority over the custom bean
ConfigType<?> envBean = TypeFactory.getFactory().getEnvironmentType(name);
if (envBean != null && envBean.isEnvBean())
return null;
if (_addCustomBean != null) {
return _addCustomBean;
}
else if (_addBean != null) {
return _addBean;
}
return null;
}
@Override
public Attribute getAddBeanAttribute(QName qName)
{
return _addBean;
}
/**
* Returns any add attributes to add arbitrary content
*/
@Override
public Attribute getAddAttribute(Class<?> cl)
{
if (cl == null)
return null;
Attribute attr = _addMethodMap.get(cl);
if (attr != null) {
return attr;
}
for (Class<?> iface : cl.getInterfaces()) {
attr = getAddAttribute(iface);
if (attr != null)
return attr;
}
return getAddAttribute(cl.getSuperclass());
}
private Class<?> createClass(QName name)
{
String uri = name.getNamespaceURI();
if (uri.equals(RESIN_NS)) {
return createResinClass(name.getLocalName());
}
if (! uri.startsWith("urn:java:"))
return null;
String pkg = uri.substring("urn:java:".length());
return TypeFactory.loadClass(pkg, name.getLocalName());
}
private Class<?> createResinClass(String name)
{
Class<?> cl = TypeFactory.loadClass("ee", name);
return cl;
}
/**
* Returns the program attribute.
*/
@Override
public Attribute getProgramAttribute()
{
if (_setProperty != null)
return _setProperty;
else
return _addProgram;
}
/**
* Returns the content program attribute (program excluding if, choose).
*/
@Override
public Attribute getContentProgramAttribute()
{
return _addContentProgram;
}
/**
* Initialize the type
*/
@Override
public void inject(Object bean)
{
introspectInject();
/*
for (int i = 0; i < _injectList.size(); i++)
_injectList.get(i).inject(bean, null);
*/
}
/**
* Initialize the type
*/
@Override
public void init(Object bean)
{
introspectInject();
for (int i = 0; i < _initList.size(); i++)
_initList.get(i).inject(bean, null);
}
/**
* Return true if the object is replaced
*/
@Override
public boolean isReplace()
{
return _replaceObject != null;
}
/**
* Replace the type with the generated object
*/
@Override
public Object replaceObject(Object bean)
{
if (_replaceObject != null) {
try {
return _replaceObject.invoke(bean);
} catch (Exception e) {
throw ConfigException.create(_replaceObject, e);
}
}
else
return bean;
}
/**
* Converts the string to the given value.
*/
public Object valueOf(String text)
{
if (_valueOf != null) {
try {
return _valueOf.invoke(null, text);
} catch (Exception e) {
throw ConfigException.create(e);
}
}
else if (_stringConstructor != null) {
try {
return _stringConstructor.newInstance(text);
} catch (Exception e) {
throw ConfigException.create(e);
}
}
else if (_addText != null) {
Object bean = create(null, TEXT);
_addText.setText(bean, TEXT, text);
inject(bean);
init(bean);
return bean;
}
else if (_addProgram != null || _addContentProgram != null) {
Object bean = create(null, TEXT);
inject(bean);
try {
ConfigProgram program = new PropertyStringProgram(TEXT, text);
if (_addProgram != null)
_addProgram.setValue(bean, TEXT, program);
else
_addContentProgram.setValue(bean, TEXT, program);
} catch (Exception e) {
throw ConfigException.create(e);
}
init(bean);
return bean;
}
else if ("".equals(text.trim())) {
Object bean = create(null, TEXT);
inject(bean);
init(bean);
return bean;
}
throw new ConfigException(L.l("Can't convert to '{0}' from '{1}'.",
_beanClass.getName(), text));
}
public boolean isConstructableFromString()
{
return _valueOf != null ||
_stringConstructor != null ||
_addText != null ||
_addProgram != null ||
_addContentProgram != null;
}
/**
* Converts the string to the given value.
*/
@Override
public Object valueOf(Object value)
{
if (value == null)
return null;
else if (value instanceof String)
return valueOf((String) value);
else if (_beanClass.isAssignableFrom(value.getClass()))
return value;
else if (value.getClass().getName().startsWith("java.lang."))
return valueOf(String.valueOf(value));
else
return value;
}
//
// Introspection
//
/**
* Introspect the bean for configuration
*/
@Override
public void introspect()
{
// long startTime = System.currentTimeMillis();
synchronized (_introspectLock) {
if (_isIntrospecting)
return;
_isIntrospecting = true;
try {
// ioc/20h4 - after to deal with recursion
introspectParent();
//Method []methods = _beanClass.getMethods();
if (! _isIntrospected) {
_isIntrospected = true;
_isEL = ! _beanClass.isAnnotationPresent(NonEL.class);
try {
Method []methods = _beanClass.getDeclaredMethods();
introspectMethods(methods);
} catch (NoClassDefFoundError e) {
log.fine(_beanClass + " " + e);
}
}
} finally {
_isIntrospecting = false;
}
}
introspectComplete();
//long endTime = System.currentTimeMillis();
}
private void introspectComplete()
{
ArrayList<InlineBeanType<?>> childList
= new ArrayList<InlineBeanType<?>>(_pendingChildList);
// ioc/20h4
for (InlineBeanType<?> child : childList) {
child.introspectParent();
child.introspectComplete();
}
}
private boolean isIntrospecting()
{
if (_isIntrospecting)
return true;
Class<?> parentClass = _beanClass.getSuperclass();
if (parentClass != null) {
ConfigType<?> parentType = TypeFactory.getType(parentClass);
if (parentType instanceof InlineBeanType<?>) {
InlineBeanType<?> parentBean = (InlineBeanType<?>) parentType;
return parentBean.isIntrospecting();
}
}
return false;
}
private void introspectParent()
{
Class<?> parentClass = _beanClass.getSuperclass();
if (parentClass != null) {
ConfigType<?> parentType = TypeFactory.getType(parentClass);
if (parentType instanceof InlineBeanType<?>) {
InlineBeanType<?> parentBean = (InlineBeanType<?>) parentType;
if (! parentBean._isIntrospected)
parentBean.introspect();
// ioc/20h4
if (parentBean.isIntrospecting()) {
if (! parentBean._pendingChildList.contains(this))
parentBean._pendingChildList.add(this);
return;
}
if (_setParent == null)
_setParent = parentBean._setParent;
if (_replaceObject == null)
_replaceObject = parentBean._replaceObject;
if (_setConfigLocation == null)
_setConfigLocation = parentBean._setConfigLocation;
if (_setConfigUriLocation == null)
_setConfigUriLocation = parentBean._setConfigUriLocation;
if (_setConfigNode == null)
_setConfigNode = parentBean._setConfigNode;
if (_addText == null)
_addText = parentBean._addText;
if (_addProgram == null)
_addProgram = parentBean._addProgram;
if (_addContentProgram == null)
_addContentProgram = parentBean._addContentProgram;
if (_setProperty == null)
_setProperty = parentBean._setProperty;
if (_addCustomBean == null)
_addCustomBean = parentBean._addCustomBean;
for (Map.Entry<QName,Attribute> entry : parentBean._nsAttributeMap.entrySet()) {
if (_nsAttributeMap.get(entry.getKey()) == null)
_nsAttributeMap.put(entry.getKey(), entry.getValue());
}
for (Map.Entry<String,Attribute> entry : parentBean._attributeMap.entrySet()) {
if (_attributeMap.get(entry.getKey()) == null)
_attributeMap.put(entry.getKey(), entry.getValue());
}
_addMethodMap.putAll(parentBean._addMethodMap);
}
}
}
/**
* Introspect the beans methods for setters
*/
public void introspectMethods(Method []methods)
{
Constructor<?> []constructors = _beanClass.getConstructors();
_stringConstructor = findConstructor(constructors, String.class);
HashMap<String,Method> createMap = new HashMap<String,Method>(8);
fillCreateMap(createMap, methods);
HashMap<String,Method> setterMap = new HashMap<String,Method>(8);
fillSetterMap(setterMap, methods);
for (Method method : methods) {
if (method.getAnnotation(DisableConfig.class) != null)
continue;
Class<?> []paramTypes = method.getParameterTypes();
String name = method.getName();
if ("replaceObject".equals(name) && paramTypes.length == 0) {
_replaceObject = method;
_replaceObject.setAccessible(true);
continue;
}
if ("valueOf".equals(name)
&& paramTypes.length == 1
&& String.class.equals(paramTypes[0])
&& Modifier.isStatic(method.getModifiers())) {
_valueOf = method;
_valueOf.setAccessible(true);
continue;
}
if (Modifier.isStatic(method.getModifiers()))
continue;
if (! Modifier.isPublic(method.getModifiers()))
continue;
if ((name.equals("addBuilderProgram") || name.equals("addProgram"))
&& paramTypes.length == 1
&& paramTypes[0].equals(ConfigProgram.class)) {
ConfigType<?> type = TypeFactory.getType(paramTypes[0]);
_addProgram = new ProgramAttribute(method, type);
}
else if (name.equals("addContentProgram")
&& paramTypes.length == 1
&& paramTypes[0].equals(ConfigProgram.class)) {
ConfigType<?> type = TypeFactory.getType(paramTypes[0]);
_addContentProgram = new ProgramAttribute(method, type);
}
else if ((name.equals("setConfigLocation")
&& paramTypes.length == 2
&& paramTypes[0].equals(String.class)
&& paramTypes[1].equals(int.class))) {
_setConfigLocation = method;
}
else if ((name.equals("setConfigUriLocation")
&& paramTypes.length == 2
&& paramTypes[0].equals(String.class)
&& paramTypes[1].equals(int.class))) {
_setConfigUriLocation = method;
}
else if ((name.equals("setConfigNode")
&& paramTypes.length == 1
&& paramTypes[0].equals(Node.class))) {
_setConfigNode = method;
}
else if ((name.equals("addCustomBean")
&& paramTypes.length == 1
&& paramTypes[0].equals(XmlBeanConfig.class))) {
ConfigType<?> customBeanType
= TypeFactory.getType(XmlBeanConfig.class);
_addCustomBean = new XmlBeanAttribute(method, customBeanType);
}
else if ((name.equals("addAnnotation")
&& paramTypes.length == 1
&& paramTypes[0].equals(Annotation.class))) {
ConfigType<?> customBeanType
= TypeFactory.getType(XmlBeanConfig.class);
_addCustomBean = new XmlBeanAttribute(method, customBeanType);
}
else if (name.equals("setProperty")
&& paramTypes.length == 2
&& paramTypes[0].equals(String.class)) {
ConfigType<?> type = TypeFactory.getType(paramTypes[1]);
PropertyAttribute attr = new PropertyAttribute(method, type);
_setProperty = attr;
}
else if (name.equals("setParent")
&& paramTypes.length == 1) {
// XXX: use annotation
_setParent = method;
}
else if (name.equals("add")
&& paramTypes.length == 1) {
ConfigType<?> type = TypeFactory.getType(paramTypes[0]);
Attribute addAttr = new AddAttribute(method, type);
_addMethodMap.put(paramTypes[0], addAttr);
// _addBean = addAttr;
}
else if ((name.startsWith("set") || name.startsWith("add"))
&& paramTypes.length == 1
&& createMap.get(name.substring(3)) == null) {
String className = name.substring(3);
String xmlName = toXmlName(name.substring(3));
TagName tagName = method.getAnnotation(TagName.class);
if (tagName != null) {
for (String propName : tagName.value()) {
addProp(propName, method);
}
}
else
addProp(xmlName, method);
addProp(toCamelName(className), method);
}
else if ((name.startsWith("create")
&& paramTypes.length == 0
&& ! void.class.equals(method.getReturnType()))) {
Class<?> type = method.getReturnType();
Method setter = setterMap.get(name.substring(6));
CreateAttribute attr = new CreateAttribute(method, type, setter);
String xmlName = toXmlName(name.substring(6));
TagName tagName = method.getAnnotation(TagName.class);
if (tagName != null) {
for (String propName : tagName.value()) {
addProp(propName, attr);
}
}
else {
addProp(xmlName, attr);
}
}
}
}
private void addProp(String propName,
Method method)
{
Attribute attr;
Class<?> []paramTypes = method.getParameterTypes();
Class<?> type = paramTypes[0];
if (propName.equals("text")
&& (type.equals(String.class)
|| type.equals(RawString.class))) {
attr = new TextAttribute(method, type);
_addText = attr;
_attributeMap.put("#text", attr);
}
else
attr = new SetterAttribute(method, type);
addProp(propName, attr);
}
private void addProp(String propName, Attribute attr)
{
Attribute oldAttr = _attributeMap.get(propName);
if (oldAttr == null) {
_attributeMap.put(propName, attr);
}
else if (attr.equals(oldAttr)) {
}
else if (oldAttr.isConfigurable() && ! attr.isConfigurable()) {
}
else if (attr.isConfigurable() && ! oldAttr.isConfigurable()) {
_attributeMap.put(propName, attr);
}
else if (attr.isAssignableFrom(oldAttr)) {
}
else if (oldAttr.isAssignableFrom(attr)) {
_attributeMap.put(propName, attr);
}
else {
log.fine(L.l("{0}: conflicting attribute for '{1}' between {2} and {3}",
this, propName, attr, oldAttr));
}
// server/2e28 vs jms/2193
// _attributeMap.put(className, attr);
if (propName.equals("value")) {
_attributeMap.put("#text", attr);
// server/12aa
if (_addText == null)
_addText = attr;
}
}
/**
* Introspect the bean for configuration
*/
private void introspectInject()
{
synchronized (_introspectLock) {
if (_initList != null)
return;
ArrayList<ConfigProgram> initList = new ArrayList<ConfigProgram>();
InjectionTargetBuilder.introspectInit(initList, getAnnotatedType());
// InjectionTargetBuilder.introspectInit(initList, _beanClass);
_initList = initList;
}
}
private static Constructor findConstructor(Constructor<?> []constructors,
Class<?> ...types)
{
for (Constructor<?> ctor : constructors) {
Class<?> []paramTypes = ctor.getParameterTypes();
if (isMatch(paramTypes, types))
return ctor;
}
return null;
}
private static boolean isMatch(Class<?> []aTypes, Class<?> []bTypes)
{
if (aTypes.length != bTypes.length)
return false;
for (int i = aTypes.length - 1; i >= 0; i--) {
if (! aTypes[i].equals(bTypes[i]))
return false;
}
return true;
}
private void fillCreateMap(HashMap<String,Method> createMap,
Method []methods)
{
for (Method method : methods) {
String name = method.getName();
if (name.startsWith("create")
&& ! name.equals("create")
&& method.getParameterTypes().length == 0) {
createMap.put(name.substring("create".length()), method);
}
}
}
private void fillSetterMap(HashMap<String,Method> setterMap,
Method []methods)
{
for (Method method : methods) {
String name = method.getName();
if (name.length() > 3
&& (name.startsWith("add") || name.startsWith("set"))
&& method.getParameterTypes().length == 1) {
setterMap.put(name.substring("set".length()), method);
}
}
}
private String toXmlName(String name)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
char ch = name.charAt(i);
if (Character.isUpperCase(ch)
&& i > 0
&& (Character.isLowerCase(name.charAt(i - 1))
|| (i + 1 < name.length()
&& Character.isLowerCase(name.charAt(i + 1))))) {
sb.append('-');
}
sb.append(Character.toLowerCase(ch));
}
return sb.toString();
}
private String toCamelName(String name)
{
return Introspector.decapitalize(name);
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + _beanClass.getName() + "]";
}
}