/* < * 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.PropertyEditor; import java.beans.PropertyEditorManager; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import javax.annotation.PostConstruct; import javax.el.MethodExpression; import org.w3c.dom.Node; import com.caucho.config.Config; import com.caucho.config.ConfigException; import com.caucho.config.attribute.Attribute; import com.caucho.config.attribute.EnvironmentAttribute; import com.caucho.config.attribute.FlowAttribute; import com.caucho.config.attribute.ListValueAttribute; import com.caucho.config.attribute.SetValueAttribute; import com.caucho.config.program.ConfigProgram; import com.caucho.config.program.ContainerProgram; import com.caucho.config.program.PropertyStringProgram; import com.caucho.config.types.AnnotationConfig; import com.caucho.config.types.RawString; import com.caucho.config.xml.XmlBeanAttribute; import com.caucho.config.xml.XmlBeanConfig; import com.caucho.config.xml.XmlBeanType; import com.caucho.el.Expr; import com.caucho.loader.AddLoaderListener; import com.caucho.loader.DynamicClassLoader; import com.caucho.loader.Environment; import com.caucho.loader.EnvironmentBean; import com.caucho.loader.EnvironmentClassLoader; import com.caucho.loader.EnvironmentLocal; import com.caucho.util.IoUtil; import com.caucho.util.L10N; import com.caucho.util.QDate; import com.caucho.vfs.Path; import com.caucho.vfs.ReadStream; import com.caucho.vfs.Vfs; import com.caucho.xml.QName; /** * Factory for returning type strategies. */ public class TypeFactory implements AddLoaderListener { private static final Logger log = Logger.getLogger(TypeFactory.class.getName()); private static L10N L = new L10N(TypeFactory.class); private static final String RESIN_NS = "http://caucho.com/ns/resin"; private static final HashMap<Class<?>,ConfigType<?>> _primitiveTypes = new HashMap<Class<?>,ConfigType<?>>(); private static final EnvironmentLocal<TypeFactory> _localFactory = new EnvironmentLocal<TypeFactory>(); private static final ClassLoader _systemClassLoader; private static final Object _introspectLock = new Object(); private final EnvironmentClassLoader _loader; private final TypeFactory _parent; private final HashSet<URL> _configSet = new HashSet<URL>(); private final HashMap<String,ArrayList<String>> _packageImportMap = new HashMap<String,ArrayList<String>>(); private final ConcurrentHashMap<String,ConfigType<?>> _typeMap = new ConcurrentHashMap<String,ConfigType<?>>(); private final HashMap<String,XmlBeanType<?>> _customBeanMap = new HashMap<String,XmlBeanType<?>>(); private final ConcurrentHashMap<QName,ConfigType<?>> _attrMap = new ConcurrentHashMap<QName,ConfigType<?>>(); private final ConcurrentHashMap<QName,Class<?>> _customClassMap = new ConcurrentHashMap<QName,Class<?>>(); private final HashMap<QName,Attribute> _listAttrMap = new HashMap<QName,Attribute>(); private final HashMap<QName,Attribute> _setAttrMap = new HashMap<QName,Attribute>(); private final ConcurrentHashMap<QName,Attribute> _envAttrMap = new ConcurrentHashMap<QName,Attribute>(); private final HashMap<String,NamespaceConfig> _nsMap = new HashMap<String,NamespaceConfig>(); private final HashSet<URL> _driverTypeSet = new HashSet<URL>(); private final HashMap<String,HashMap<String,String>> _driverTypeMap = new HashMap<String,HashMap<String,String>>(); private final AtomicBoolean _isInInit = new AtomicBoolean(); private TypeFactory(ClassLoader loader) { _loader = Environment.getEnvironmentClassLoader(loader); _localFactory.set(this, loader); if (_loader != null) { _parent = getFactory(_loader.getParent()); _loader.addLoaderListener(this); } else _parent = null; init(loader); } /** * Returns the appropriate strategy. */ public static ConfigType<?> getType(Object bean) { if (bean instanceof XmlBeanConfig<?>) return ((XmlBeanConfig<?>) bean).getConfigType(); else if (bean instanceof AnnotationConfig) return ((AnnotationConfig) bean).getConfigType(); return getType(bean.getClass()); } /** * Returns the appropriate strategy. */ public static <T> ConfigType<T> getType(Class<T> type) { TypeFactory factory = getFactory(type.getClassLoader()); return factory.getConfigTypeImpl(type); } /** * Returns the appropriate strategy. */ public static ConfigType<?> getType(Type type) { return getType((Class) type); } /** * Returns the appropriate strategy. */ public static Class<?> loadClass(QName qName) { return getFactory().loadClassImpl(qName); } /** * Returns the appropriate strategy. */ public static Class<?> loadClass(String pkg, String name) { return getFactory().loadClassImpl(pkg, name); } public static TypeFactory create() { return getFactory(); } public static TypeFactory getFactory() { return getFactory(Thread.currentThread().getContextClassLoader()); } public static TypeFactory getFactory(ClassLoader loader) { if (loader == null) loader = _systemClassLoader; TypeFactory factory = _localFactory.getLevel(loader); if (factory == null) { factory = new TypeFactory(loader); _localFactory.set(factory, loader); factory.init(loader); } return factory; } /** * Returns an environment type. */ public ConfigType<?> getEnvironmentType(QName name) { ConfigType<?> type = _attrMap.get(name); if (type != null) return type == NotFoundConfigType.NULL ? null : type; type = getEnvironmentTypeRec(name); if (type != null) { return type; } if (! "".equals(name.getNamespaceURI())) { type = getEnvironmentType(new QName(name.getLocalName())); if (type != null) { _attrMap.put(name, type); return type; } } _attrMap.put(name, NotFoundConfigType.NULL); return null; } /** * Returns an environment type. */ protected ConfigType<?> getEnvironmentTypeRec(QName name) { ConfigType<?> type = _attrMap.get(name); if (type != null) { return type == NotFoundConfigType.NULL ? null : type; } if (_parent != null) type = _parent.getEnvironmentTypeRec(name); if (type != null) { _attrMap.put(name, type); return type; } String uri = name.getNamespaceURI(); NamespaceConfig ns = _nsMap.get(uri); if (ns != null) { type = ns.getBean(name.getLocalName()); if (type != null) { _attrMap.put(name, type); return type; } } if (RESIN_NS.equals(uri)) uri = "urn:java:ee"; if (uri != null && uri.startsWith("urn:java:")) { String pkg = uri.substring("urn:java:".length()); String className = name.getLocalName(); Class<?> cl = loadClassImpl(pkg, className); if (cl != null) { type = getType(cl); _attrMap.put(name, type); return type; } } _attrMap.put(name, NotFoundConfigType.NULL); return null; } /** * Returns an environment type. */ public Attribute getListAttribute(QName name) { synchronized (_listAttrMap) { Attribute attr = _listAttrMap.get(name); if (attr != null) return attr; ConfigType<?> type = getEnvironmentType(name); if (type == null) return null; attr = new ListValueAttribute(type); _listAttrMap.put(name, attr); return attr; } } /** * Returns an environment type. */ public Attribute getSetAttribute(QName name) { synchronized (_setAttrMap) { Attribute attr = _setAttrMap.get(name); if (attr != null) return attr; ConfigType<?> type = getEnvironmentType(name); if (type == null) return null; attr = new SetValueAttribute(type); _setAttrMap.put(name, attr); return attr; } } /** * Returns an environment type. */ public Attribute getEnvironmentAttribute(QName name) { Attribute attr = _envAttrMap.get(name); if (attr != null) return attr; ConfigType<?> type = getEnvironmentType(name); if (type == null) return null; if (type instanceof FlowBeanType<?>) attr = new FlowAttribute(type); else if (type.isEnvBean()) { attr = new EnvironmentAttribute(type); } else { // attr = new XmlBeanAttribute(null, type); //Thread.dumpStack(); return null; } _envAttrMap.put(name, attr); return attr; } private Class<?> loadClassImpl(QName qName) { Class<?> cl = _customClassMap.get(qName); if (cl != null) return cl == void.class ? null : cl; String uri = qName.getNamespaceURI(); String localName = qName.getLocalName(); if (! uri.startsWith("urn:java:")) throw new IllegalStateException(L.l("'{0}' is an unexpected namespace, expected 'urn:java:...'", uri)); String packageName = uri.substring("uri:java:".length()); cl = loadClassImpl(packageName, localName); if (cl != null) _customClassMap.put(qName, cl); else _customClassMap.put(qName, void.class); return cl; } private Class<?> loadClassImpl(String pkg, String name) { ClassLoader loader = _loader; if (_loader == null) loader = _systemClassLoader; ArrayList<String> pkgList = loadPackageList(pkg); DynamicClassLoader dynLoader = null; if (loader instanceof DynamicClassLoader) dynLoader = (DynamicClassLoader) loader; for (String pkgName : pkgList) { try { Class<?> cl; if (dynLoader != null) cl = dynLoader.loadClassImpl(pkgName + '.' + name, false); else cl = Class.forName(pkgName + '.' + name, false, loader); if (cl != null) return cl; } catch (ClassNotFoundException e) { log.log(Level.ALL, e.toString(), e); } } return null; } private ArrayList<String> loadPackageList(String pkg) { synchronized (_packageImportMap) { ArrayList<String> pkgList = _packageImportMap.get(pkg); if (pkgList != null) return pkgList; pkgList = new ArrayList<String>(); pkgList.add(pkg); InputStream is = null; try { ClassLoader loader = _loader; if (loader == null) loader = _systemClassLoader; is = loader.getResourceAsStream(pkg.replace('.', '/') + "/namespace"); if (is != null) { ReadStream in = Vfs.openRead(is); String line; while ((line = in.readLine()) != null) { for (String name : line.split("[ \t\r\n]+")) { if (! "".equals(name)) { if (! pkgList.contains(name)) pkgList.add(name); } } } } } catch (IOException e) { log.log(Level.FINE, e.toString(), e); } finally { IoUtil.close(is); } _packageImportMap.put(pkg, pkgList); return pkgList; } } private ConfigType getConfigTypeImpl(Class type) { ConfigType strategy = _typeMap.get(type.getName()); if (strategy == null) { strategy = _primitiveTypes.get(type); if (strategy == null) strategy = createType(type); _typeMap.putIfAbsent(type.getName(), strategy); strategy = _typeMap.get(type.getName()); } strategy.carefulIntrospect(); return strategy; } ConfigType createType(Class<?> type) { PropertyEditor editor = null; if (ConfigType.class.isAssignableFrom(type)) { try { return (ConfigType) type.newInstance(); } catch (Exception e) { throw ConfigException.create(e); } } else if ((editor = findEditor(type)) != null) return new PropertyEditorType(type, editor); else if (type.getEnumConstants() != null) return new EnumType(type); else if (Set.class.isAssignableFrom(type)) return new SetType(type); else if (Collection.class.isAssignableFrom(type) && ! Queue.class.isAssignableFrom(type)) { // jms/2300 return new ListType(type); } else if (Map.class.isAssignableFrom(type) && type.getName().startsWith("java.util")) { return new MapType(type); } else if (EnvironmentBean.class.isAssignableFrom(type)) return new EnvironmentBeanType(type); else if (FlowBean.class.isAssignableFrom(type)) return new FlowBeanType(type); else if (type.isArray()) { Class<?> compType = type.getComponentType(); return new ArrayType(getType(compType), compType); } else if (Annotation.class.isAssignableFrom(type)) { return new AnnotationInterfaceType(type); } else if (type.isInterface()) { return new InterfaceType(type); } else if (type == ConfigProgram.class) return new ConfigProgramType(type); else if (Modifier.isAbstract(type.getModifiers())) { return new AbstractBeanType(type); } else return new InlineBeanType(type); } /** * Returns the Java bean property editor */ private static PropertyEditor findEditor(Class<?> type) { // none of the caucho classes has a ProperyEditorManager if (type.getName().startsWith("com.caucho")) return null; else return PropertyEditorManager.findEditor(type); } /** * Returns the appropriate strategy. */ public static <T> XmlBeanType<T> getCustomBeanType(Class<T> type) { TypeFactory factory = getFactory(type.getClassLoader()); return factory.getCustomBeanTypeImpl(type); } private <T> XmlBeanType<T> getCustomBeanTypeImpl(Class<T> type) { synchronized (_customBeanMap) { XmlBeanType<?> beanType = _customBeanMap.get(type.getName()); if (beanType == null) { beanType = new XmlBeanType<T>(type); _customBeanMap.put(type.getName(), beanType); } return (XmlBeanType<T>) beanType; } } /** * Initialize the type strategy factory with files in META-INF/caucho * * @param loader the owning class loader * @throws Exception */ private void init(ClassLoader loader) { if (! _isInInit.getAndSet(true)) return; try { _nsMap.clear(); _driverTypeSet.clear(); _driverTypeMap.clear(); if (_parent == null) { addNamespace(NamespaceConfig.NS_DEFAULT); addNamespace(NamespaceConfig.NS_RESIN); addNamespace(NamespaceConfig.NS_RESIN_CORE); addNamespace(NamespaceConfig.URN_RESIN); } } catch (RuntimeException e) { throw e; } catch (Exception e) { throw ConfigException.create(e); } finally { _isInInit.set(false); } } protected boolean hasConfig(URL url) { if (_configSet.contains(url)) return true; else if (_parent != null) return _parent.hasConfig(url); else return false; } /** * Returns a driver by the url */ public Class<?> getDriverClassByUrl(Class<?> api, String url) { String scheme; int p = url.indexOf(':'); if (p >= 0) scheme = url.substring(0, p); else scheme = url; String typeName = getDriverType(api.getName(), scheme); if (typeName == null) { ArrayList<String> schemes = new ArrayList<String>(); getDriverSchemes(schemes, api.getName()); Collections.sort(schemes); throw new ConfigException(L.l("'{0}' is an unknown scheme for driver '{1}'. The available schemes are '{2}'", scheme, api.getName(), schemes)); } try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class<?> cl = Class.forName(typeName, false, loader); if (! api.isAssignableFrom(cl)) throw new ConfigException(L.l("'{0}' is not assignable to '{1}' for scheme '{2}'", cl.getName(), api.getName(), scheme)); return cl; } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new ConfigException(L.l("'{0}' is an undefined class for scheme '{1}'", typeName, scheme), e); } } /** * Returns a driver by the scheme */ public Class<?> getDriverClassByScheme(Class<?> api, String scheme) { String typeName = getDriverType(api.getName(), scheme); if (typeName == null) { ArrayList<String> schemes = new ArrayList<String>(); getDriverSchemes(schemes, api.getName()); Collections.sort(schemes); throw new ConfigException(L.l("'{0}' is an unknown scheme for driver '{1}'. The available schemes are '{2}'", scheme, api.getName(), schemes)); } try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class<?> cl = Class.forName(typeName, false, loader); if (! api.isAssignableFrom(cl)) throw new ConfigException(L.l("'{0}' is not assignable to '{1}' for scheme '{2}'", cl.getName(), api.getName(), scheme)); return cl; } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new ConfigException(L.l("'{0}' is an undefined class for scheme '{1}'", typeName, scheme), e); } } public ContainerProgram getUrlProgram(String url) { String properties = ""; int p = url.indexOf(':'); if (p >= 0) { properties = url.substring(p + 1); } else return null; String []props = properties.split("[;]"); if (props.length == 0) return null; ContainerProgram program = new ContainerProgram(); for (String prop : props) { if (prop.length() == 0) continue; String []values = prop.split("[=]"); if (values.length != 2) throw new ConfigException(L.l("'{0}' is an invalid URL. Bean URL syntax is 'scheme:prop1=value1;prop2=value2'", url)); program.addProgram(new PropertyStringProgram(values[0], values[1])); } return program; } /** * Returns the classname of the given driver. * * @param apiType the driver API * @param scheme the configuration scheme */ public String getDriverType(String apiType, String scheme) { HashMap<String,String> driverMap = getDriverTypeMap(apiType); return driverMap.get(scheme); } /** * Returns a list of schemes supported by the api type. * * @param schemes the return list of schemes scheme * @param apiType the driver API */ public void getDriverSchemes(ArrayList<String> schemes, String apiType) { HashMap<String,String> driverMap = getDriverTypeMap(apiType); ClassLoader loader = _loader; if (_loader == null) loader = _systemClassLoader; for (Map.Entry<String,String> entry : driverMap.entrySet()) { String scheme = entry.getKey(); String type = entry.getValue(); try { Class cl = Class.forName(type, false, loader); if (cl != null) schemes.add(scheme); } catch (Exception e) { log.finest(apiType + " schemes: " + e.toString()); } } } /** * Loads the map for a driver. */ private HashMap<String,String> getDriverTypeMap(String apiType) { synchronized (_driverTypeMap) { HashMap<String,String> driverMap = _driverTypeMap.get(apiType); if (driverMap == null) { driverMap = new HashMap<String,String>(); if (_parent != null) driverMap.putAll(_parent.getDriverTypeMap(apiType)); loadDriverTypeMap(driverMap, apiType); _driverTypeMap.put(apiType, driverMap); } return driverMap; } } /** * Reads the drivers from the META-INF/caucho */ private void loadDriverTypeMap(HashMap<String,String> driverMap, String apiType) { try { ClassLoader loader = _loader; if (loader == null) loader = _systemClassLoader; Enumeration<URL> urls = loader.getResources("META-INF/caucho/com.caucho.config.uri/" + apiType); while (urls.hasMoreElements()) { URL url = urls.nextElement(); if (hasDriver(url)) continue; _driverTypeSet.add(url); InputStream is = url.openStream(); try { Properties props = new Properties(); props.load(is); for (Map.Entry entry : props.entrySet()) { driverMap.put((String) entry.getKey(), (String) entry.getValue()); } } finally { is.close(); } } } catch (Exception e) { throw ConfigException.create(e); } } protected boolean hasDriver(URL url) { synchronized (_driverTypeSet) { if (_driverTypeSet.contains(url)) return true; else if (_parent != null) return _parent.hasDriver(url); else return false; } } // // AddLoaderListener // public boolean isEnhancer() { return false; } /** * Called with the loader config changes. */ public void addLoader(EnvironmentClassLoader loader) { init(loader); } // // Configuration methods // /** * Adds an new environment attribute. */ private void addNamespace(NamespaceConfig ns) { _nsMap.put(ns.getName(), ns); } public String toString() { return getClass().getSimpleName() + "[" + _loader + "]"; } static { _primitiveTypes.put(boolean.class, BooleanPrimitiveType.TYPE); _primitiveTypes.put(byte.class, BytePrimitiveType.TYPE); _primitiveTypes.put(short.class, ShortPrimitiveType.TYPE); _primitiveTypes.put(int.class, IntegerPrimitiveType.TYPE); _primitiveTypes.put(long.class, LongPrimitiveType.TYPE); _primitiveTypes.put(float.class, FloatPrimitiveType.TYPE); _primitiveTypes.put(double.class, DoublePrimitiveType.TYPE); _primitiveTypes.put(char.class, CharacterPrimitiveType.TYPE); _primitiveTypes.put(Boolean.class, BooleanType.TYPE); _primitiveTypes.put(Byte.class, ByteType.TYPE); _primitiveTypes.put(Short.class, ShortType.TYPE); _primitiveTypes.put(Integer.class, IntegerType.TYPE); _primitiveTypes.put(Long.class, LongType.TYPE); _primitiveTypes.put(Float.class, FloatType.TYPE); _primitiveTypes.put(Double.class, DoubleType.TYPE); _primitiveTypes.put(Character.class, CharacterType.TYPE); _primitiveTypes.put(Object.class, ObjectType.TYPE); _primitiveTypes.put(String.class, StringType.TYPE); _primitiveTypes.put(RawString.class, RawStringType.TYPE); _primitiveTypes.put(String[].class, StringArrayType.TYPE); _primitiveTypes.put(Class.class, ClassType.TYPE); _primitiveTypes.put(Path.class, PathType.TYPE); _primitiveTypes.put(File.class, FileType.TYPE); _primitiveTypes.put(URL.class, UrlType.TYPE); _primitiveTypes.put(Pattern.class, PatternType.TYPE); _primitiveTypes.put(Level.class, LevelBuilder.TYPE); _primitiveTypes.put(Locale.class, LocaleType.TYPE); _primitiveTypes.put(Node.class, NodeType.TYPE); _primitiveTypes.put(QDate.class, QDateType.TYPE); _primitiveTypes.put(Date.class, DateType.TYPE); _primitiveTypes.put(Properties.class, PropertiesType.TYPE); _primitiveTypes.put(Expr.class, ExprType.TYPE); // _primitiveTypes.put(DataSource.class, DataSourceType.TYPE); _primitiveTypes.put(MethodExpression.class, MethodExpressionType.TYPE); ClassLoader systemClassLoader = null; try { systemClassLoader = ClassLoader.getSystemClassLoader(); } catch (Exception e) { } _systemClassLoader = systemClassLoader; } }