/*
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.el.ELContext;
import javax.el.ELException;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import com.caucho.config.attribute.Attribute;
import com.caucho.config.inject.InjectManager;
import com.caucho.config.type.ConfigType;
import com.caucho.config.type.TypeFactory;
import com.caucho.config.types.DirVar;
import com.caucho.config.types.FileVar;
import com.caucho.config.xml.XmlConfigContext;
import com.caucho.el.EL;
import com.caucho.el.EnvironmentContext;
import com.caucho.loader.Environment;
import com.caucho.loader.EnvironmentClassLoader;
import com.caucho.loader.EnvironmentLocal;
import com.caucho.relaxng.CompactVerifierFactoryImpl;
import com.caucho.relaxng.Schema;
import com.caucho.relaxng.Verifier;
import com.caucho.relaxng.VerifierFilter;
import com.caucho.util.DisplayableException;
import com.caucho.util.L10N;
import com.caucho.vfs.Path;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.Vfs;
import com.caucho.xml.DOMBuilder;
import com.caucho.xml.QAttr;
import com.caucho.xml.QDocument;
import com.caucho.xml.QName;
import com.caucho.xml.Xml;
/**
* Facade for Resin's configuration builder.
*/
public class Config {
private static final L10N L = new L10N(Config.class);
private static final Logger log
= Logger.getLogger(Config.class.getName());
private static final EnvironmentLocal<ConfigProperties> _envProperties
= new EnvironmentLocal<ConfigProperties>();
// the context class loader of the config
private ClassLoader _classLoader;
private boolean _isEL = true;
private boolean _isIgnoreEnvironment;
private boolean _allowResinInclude;
public Config()
{
this(Thread.currentThread().getContextClassLoader());
}
/**
* @param loader the class loader environment to use.
*/
public Config(ClassLoader loader)
{
_classLoader = loader;
}
/**
* Set true if resin:include should be allowed.
*/
public void setResinInclude(boolean useResinInclude)
{
_allowResinInclude = useResinInclude;
}
/**
* True if EL expressions are allowed
*/
public boolean isEL()
{
return _isEL;
}
/**
* True if EL expressions are allowed
*/
public void setEL(boolean isEL)
{
_isEL = isEL;
}
/**
* True if environment tags are ignored
*/
public boolean isIgnoreEnvironment()
{
return _isIgnoreEnvironment;
}
/**
* True if environment tags are ignored
*/
public void setIgnoreEnvironment(boolean isIgnore)
{
_isIgnoreEnvironment = isIgnore;
}
/**
* Returns an environment property
*/
public static Object getProperty(String key)
{
ConfigProperties props = _envProperties.get();
if (props != null)
return props.get(key);
else
return null;
}
/**
* Sets a environment property
*/
public static void setProperty(String key, Object value)
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
setProperty(key, value, loader);
}
/**
* Sets a environment property
*/
public static void setProperty(String key, Object value, ClassLoader loader)
{
ConfigProperties props = _envProperties.getLevel(loader);
if (props == null) {
props = createConfigProperties(loader);
}
props.put(key, value);
}
private static ConfigProperties createConfigProperties(ClassLoader loader)
{
EnvironmentClassLoader envLoader
= Environment.getEnvironmentClassLoader(loader);
ConfigProperties props = _envProperties.getLevel(envLoader);
if (props != null)
return props;
if (envLoader != null) {
ConfigProperties parent = createConfigProperties(envLoader.getParent());
props = new ConfigProperties(parent);
}
else
props = new ConfigProperties(null);
_envProperties.set(props, envLoader);
return props;
}
/**
* Configures a bean with a configuration file.
*/
public Object configure(Object obj, Path path)
throws ConfigException, IOException
{
try {
QDocument doc = parseDocument(path, null);
return configure(obj, doc.getDocumentElement());
} catch (RuntimeException e) {
throw e;
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw ConfigException.create(e);
}
}
/**
* Configures a bean with a configuration file.
*/
public Object configure(Object obj, InputStream is)
throws Exception
{
QDocument doc = parseDocument(is, null);
return configure(obj, doc.getDocumentElement());
}
/**
* Configures a bean with a configuration file and schema.
*/
public Object configure(Object obj, Path path, String schemaLocation)
throws ConfigException
{
try {
Schema schema = findCompactSchema(schemaLocation);
QDocument doc = parseDocument(path, schema);
return configure(obj, doc.getDocumentElement());
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw LineConfigException.create(e);
}
}
/**
* Configures a bean with a configuration file and schema.
*/
public Object configure(Object obj, Path path, Schema schema)
throws ConfigException
{
try {
QDocument doc = parseDocument(path, schema);
return configure(obj, doc.getDocumentElement());
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw ConfigException.create(e);
}
}
/**
* Configures a bean with a configuration file.
*/
public Object configure(Object obj,
InputStream is,
String schemaLocation)
throws Exception
{
Schema schema = findCompactSchema(schemaLocation);
QDocument doc = parseDocument(is, schema);
return configure(obj, doc.getDocumentElement());
}
/**
* Configures a bean with a configuration file.
*/
public Object configure(Object obj,
InputStream is,
Schema schema)
throws Exception
{
QDocument doc = parseDocument(is, schema);
return configure(obj, doc.getDocumentElement());
}
/**
* Configures a bean with a DOM.
*/
public Object configure(Object obj, Node topNode)
throws Exception
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_classLoader);
XmlConfigContext builder = createBuilder();
setProperty("__FILE__", FileVar.__FILE__);
setProperty("__DIR__", DirVar.__DIR__);
return builder.configure(obj, topNode);
} finally {
thread.setContextClassLoader(oldLoader);
}
}
/**
* Configures a bean with a configuration file and schema.
*/
public void configureBean(Object obj,
Path path,
String schemaLocation)
throws Exception
{
Schema schema = findCompactSchema(schemaLocation);
QDocument doc = parseDocument(path, schema);
configureBean(obj, doc.getDocumentElement());
}
/**
* Configures a bean with a configuration file and schema.
*/
public void configureBean(Object obj, Path path)
throws Exception
{
QDocument doc = parseDocument(path, null);
configureBean(obj, doc.getDocumentElement());
}
/**
* Configures a bean with a DOM. configureBean does not
* apply init() or replaceObject().
*/
public void configureBean(Object obj, Node topNode)
throws Exception
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_classLoader);
XmlConfigContext builder = createBuilder();
InjectManager webBeans = InjectManager.create();
setProperty("__FILE__", FileVar.__FILE__);
setProperty("__DIR__", DirVar.__DIR__);
builder.configureBean(obj, topNode);
} finally {
thread.setContextClassLoader(oldLoader);
}
}
private XmlConfigContext createBuilder()
{
return new XmlConfigContext(this);
}
/**
* Configures a bean with a configuration file and schema.
*/
public void configureBean(Object obj,
Path path,
Schema schema)
throws Exception
{
QDocument doc = parseDocument(path, schema);
configureBean(obj, doc.getDocumentElement());
}
/**
* Configures the bean from a path
*/
private QDocument parseDocument(Path path, Schema schema)
throws LineConfigException, IOException, org.xml.sax.SAXException
{
// server/2d33
SoftReference<QDocument> docRef = null;//_parseCache.get(path);
QDocument doc;
if (docRef != null) {
doc = docRef.get();
if (doc != null && ! doc.isModified())
return doc;
}
ReadStream is = path.openRead();
try {
doc = parseDocument(is, schema);
// _parseCache.put(path, new SoftReference<QDocument>(doc));
return doc;
} finally {
is.close();
}
}
/**
* Configures the bean from an input stream.
*/
private QDocument parseDocument(InputStream is, Schema schema)
throws LineConfigException,
IOException,
org.xml.sax.SAXException
{
QDocument doc = new QDocument();
DOMBuilder builder = new DOMBuilder();
builder.init(doc);
String systemId = null;
String filename = null;
if (is instanceof ReadStream) {
systemId = ((ReadStream) is).getPath().getURL();
filename = ((ReadStream) is).getPath().getUserPath();
}
doc.setSystemId(systemId);
builder.setSystemId(systemId);
doc.setRootFilename(filename);
builder.setFilename(filename);
builder.setSkipWhitespace(true);
InputSource in = new InputSource();
in.setByteStream(is);
in.setSystemId(systemId);
Xml xml = new Xml();
xml.setOwner(doc);
xml.setResinInclude(_allowResinInclude);
xml.setFilename(filename);
if (schema != null) {
Verifier verifier = schema.newVerifier();
VerifierFilter filter = verifier.getVerifierFilter();
filter.setParent(xml);
filter.setContentHandler(builder);
filter.setErrorHandler(builder);
filter.parse(in);
}
else {
xml.setContentHandler(builder);
xml.parse(in);
}
return doc;
}
private Schema findCompactSchema(String location)
throws IOException, ConfigException
{
try {
if (location == null)
return null;
Thread thread = Thread.currentThread();
ClassLoader loader = thread.getContextClassLoader();
if (loader == null)
loader = ClassLoader.getSystemClassLoader();
URL url = loader.getResource(location);
if (url == null)
return null;
Path path = Vfs.lookup(URLDecoder.decode(url.toString()));
// VerifierFactory factory = VerifierFactory.newInstance("http://caucho.com/ns/compact-relax-ng/1.0");
CompactVerifierFactoryImpl factory;
factory = new CompactVerifierFactoryImpl();
return factory.compileSchema(path);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw ConfigException.create(e);
}
}
/**
* Returns true if the class can be instantiated.
*/
public static void checkCanInstantiate(Class beanClass)
throws ConfigException
{
if (beanClass == null)
throw new ConfigException(L.l("null classes can't be instantiated."));
else if (beanClass.isInterface())
throw new ConfigException(L.l("'{0}' must be a concrete class. Interfaces cannot be instantiated.", beanClass.getName()));
else if (! Modifier.isPublic(beanClass.getModifiers()))
throw new ConfigException(L.l("Custom bean class '{0}' is not public. Bean classes must be public, concrete, and have a zero-argument constructor.", beanClass.getName()));
else if (Modifier.isAbstract(beanClass.getModifiers()))
throw new ConfigException(L.l("Custom bean class '{0}' is abstract. Bean classes must be public, concrete, and have a zero-argument constructor.", beanClass.getName()));
Constructor []constructors = beanClass.getDeclaredConstructors();
Constructor constructor = null;
for (int i = 0; i < constructors.length; i++) {
if (constructors[i].getParameterTypes().length == 0) {
constructor = constructors[i];
break;
}
}
if (constructor == null)
throw new ConfigException(L.l("Custom bean class '{0}' doesn't have a zero-arg constructor. Bean classes must be have a zero-argument constructor.", beanClass.getName()));
if (! Modifier.isPublic(constructor.getModifiers())) {
throw new ConfigException(L.l("The zero-argument constructor for '{0}' isn't public. Bean classes must have a public zero-argument constructor.", beanClass.getName()));
}
}
/**
* Returns true if the class can be instantiated.
*/
public static void validate(Class cl, Class api)
throws ConfigException
{
checkCanInstantiate(cl);
if (! api.isAssignableFrom(cl)) {
throw new ConfigException(L.l("{0} must implement {1}.",
cl.getName(), api.getName()));
}
}
/**
* Returns true if the class can be instantiated using zero args constructor
* or constructor that accepts an instance of class passed in type argument
*/
public static void checkCanInstantiate(Class beanClass,
Class type)
throws ConfigException
{
if (beanClass == null)
throw new ConfigException(L.l("null classes can't be instantiated."));
else if (beanClass.isInterface())
throw new ConfigException(L.l(
"'{0}' must be a concrete class. Interfaces cannot be instantiated.",
beanClass.getName()));
else if (! Modifier.isPublic(beanClass.getModifiers()))
throw new ConfigException(L.l(
"Custom bean class '{0}' is not public. Bean classes must be public, concrete, and have a zero-argument constructor.",
beanClass.getName()));
else if (Modifier.isAbstract(beanClass.getModifiers()))
throw new ConfigException(L.l(
"Custom bean class '{0}' is abstract. Bean classes must be public, concrete, and have a zero-argument constructor.",
beanClass.getName()));
Constructor [] constructors = beanClass.getDeclaredConstructors();
Constructor zeroArgsConstructor = null;
Constructor singleArgConstructor = null;
for (int i = 0; i < constructors.length; i++) {
if (constructors [i].getParameterTypes().length == 0) {
zeroArgsConstructor = constructors [i];
if (singleArgConstructor != null)
break;
}
else if (type != null
&& constructors [i].getParameterTypes().length == 1 &&
type.isAssignableFrom(constructors[i].getParameterTypes()[0])) {
singleArgConstructor = constructors [i];
if (zeroArgsConstructor != null)
break;
}
}
if (zeroArgsConstructor == null
&& singleArgConstructor == null)
if (type != null)
throw new ConfigException(L.l(
"Custom bean class '{0}' doesn't have a zero-arg constructor, or a constructor accepting parameter of type '{1}'. Bean class '{0}' must have a zero-argument constructor, or a constructor accepting parameter of type '{1}'",
beanClass.getName(),
type.getName()));
else
throw new ConfigException(L.l(
"Custom bean class '{0}' doesn't have a zero-arg constructor. Bean classes must have a zero-argument constructor.",
beanClass.getName()));
if (singleArgConstructor != null) {
if (! Modifier.isPublic(singleArgConstructor.getModifiers()) &&
(zeroArgsConstructor == null ||
! Modifier.isPublic(zeroArgsConstructor.getModifiers()))) {
throw new ConfigException(L.l(
"The constructor for bean '{0}' accepting parameter of type '{1}' is not public. Constructor accepting parameter of type '{1}' must be public.",
beanClass.getName(),
type.getName()));
}
}
else if (zeroArgsConstructor != null) {
if (! Modifier.isPublic(zeroArgsConstructor.getModifiers()))
throw new ConfigException(L.l(
"The zero-argument constructor for '{0}' isn't public. Bean classes must have a public zero-argument constructor.",
beanClass.getName()));
}
}
public static void validate(Class cl, Class api, Class type)
throws ConfigException
{
checkCanInstantiate(cl, type);
if (! api.isAssignableFrom(cl)) {
throw new ConfigException(L.l("{0} must implement {1}.",
cl.getName(), api.getName()));
}
}
/**
* Sets an attribute with a value.
*
* @param obj the bean to be set
* @param attr the attribute name
* @param value the attribute value
*/
public static void setAttribute(Object obj, String attr, Object value)
{
ConfigType<?> type = TypeFactory.getType(obj.getClass());
QName attrName = new QName(attr);
Attribute attrStrategy = type.getAttribute(attrName);
if (attrStrategy == null)
throw new ConfigException(L.l("{0}: '{1}' is an unknown attribute.",
obj.getClass().getName(),
attrName.getName()));
value = attrStrategy.getConfigType().valueOf(value);
attrStrategy.setValue(obj, attrName, value);
}
/**
* Sets an attribute with a value.
*
* @param obj the bean to be set
* @param attr the attribute name
* @param value the attribute value
*/
public static void setStringAttribute(Object obj, String attr, String value)
throws Exception
{
XmlConfigContext builder = new XmlConfigContext();
QAttr qAttr = new QAttr(attr);
qAttr.setValue(value);
builder.configureAttribute(obj, qAttr);
}
public static void init(Object bean)
throws ConfigException
{
try {
ConfigType type = TypeFactory.getType(bean.getClass());
type.init(bean);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw ConfigException.create(e);
}
}
public static void inject(Object bean)
throws ConfigException
{
try {
ConfigType type = TypeFactory.getType(bean.getClass());
type.inject(bean);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw ConfigException.create(e);
}
}
public static Object replaceObject(Object bean) throws Exception
{
ConfigType type = TypeFactory.getType(bean.getClass());
return type.replaceObject(bean);
}
/**
* Returns the variable resolver.
*/
public static ELContext getEnvironment()
{
XmlConfigContext builder = XmlConfigContext.getCurrentBuilder();
if (builder != null) {
return builder.getELContext();
}
else
return EL.getEnvironment();
}
/**
* Returns the variable resolver.
*/
public static ConfigELContext getELContext()
{
XmlConfigContext builder = XmlConfigContext.getCurrentBuilder();
if (builder != null) {
return builder.getELContext();
}
else
return null;
}
/**
* Sets an EL configuration variable.
*/
public static Object getCurrentVar(String var)
{
// return InjectManager.create().findByName(var);
return getProperty(var);
}
/**
* Evaluates an EL string in the context.
*/
public static String evalString(String str)
throws ELException
{
return EL.evalString(str, getEnvironment());
}
/**
* Evaluates an EL string in the context.
*/
public static String evalString(String str, HashMap<String,Object> varMap)
throws ELException
{
return EL.evalString(str, getEnvironment(varMap));
}
/**
* Evaluates an EL boolean in the context.
*/
public static boolean evalBoolean(String str)
throws ELException
{
return EL.evalBoolean(str, getEnvironment());
}
public static ELContext getEnvironment(HashMap<String,Object> varMap)
{
if (varMap != null)
return new EnvironmentContext(varMap);
else
return new EnvironmentContext();
}
public static ConfigException error(Field field, String msg)
{
return new ConfigException(location(field) + msg);
}
public static ConfigException error(Method method, String msg)
{
return new ConfigException(location(method) + msg);
}
public static RuntimeException createLine(String systemId, int line,
Throwable e)
{
while (e.getCause() != null
&& (e instanceof InstantiationException
|| e instanceof InvocationTargetException
|| e.getClass().equals(ConfigRuntimeException.class))) {
e = e.getCause();
}
if (e instanceof LineConfigException)
throw (LineConfigException) e;
String lines = getSourceLines(systemId, line);
String loc = systemId + ":" + line + ": ";
if (e instanceof DisplayableException) {
return new LineConfigException(loc + e.getMessage() + "\n" + lines, e);
}
else
return new LineConfigException(loc + e + "\n" + lines, e);
}
public static String location(Field field)
{
String className = field.getDeclaringClass().getName();
return className + "." + field.getName() + ": ";
}
public static String location(Method method)
{
String className = method.getDeclaringClass().getName();
return className + "." + method.getName() + ": ";
}
private static String getSourceLines(String systemId, int errorLine)
{
if (systemId == null)
return "";
ReadStream is = null;
try {
is = Vfs.lookup().lookup(systemId).openRead();
int line = 0;
StringBuilder sb = new StringBuilder("\n\n");
String text;
while ((text = is.readLine()) != null) {
line++;
if (errorLine - 2 <= line && line <= errorLine + 2) {
sb.append(line);
sb.append(": ");
sb.append(text);
sb.append("\n");
}
}
return sb.toString();
} catch (IOException e) {
log.log(Level.FINEST, e.toString(), e);
return "";
} finally {
if (is != null)
is.close();
}
}
static class ConfigProperties {
private ConfigProperties _parent;
private HashMap<String,Object> _properties = new HashMap<String,Object>(8);
ConfigProperties(ConfigProperties parent)
{
_parent = parent;
}
public Object get(String key)
{
Object value = _properties.get(key);
if (value != null)
return value;
else if (_parent != null)
return _parent.get(key);
else
return null;
}
public void put(String key, Object value)
{
_properties.put(key, value);
}
}
}