org.eclipse.jetty.xml.XmlConfiguration Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.xml;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.lang.reflect.Array;
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.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.ArrayQueue;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlParser.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/* ------------------------------------------------------------ */
/**
* Configure Objects from XML. This class reads an XML file conforming to the configure.dtd DTD and uses it to configure and object by calling set, put or other
* methods on the object.
*
*
* The actual XML file format may be changed (eg to spring XML) by implementing the {@link ConfigurationProcessorFactory} interfaces to be found by the
* ServiceLoader
by using the DTD and first tag element in the file. Note that DTD will be null if validation is off.
*
*/
public class XmlConfiguration
{
private static final Logger LOG = Log.getLogger(XmlConfiguration.class);
private static final Class>[] __primitives =
{ Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE };
private static final Class>[] __primitiveHolders =
{ Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class };
private static final Class>[] __supportedCollections =
{ ArrayList.class,ArrayQueue.class,HashSet.class,Queue.class,List.class,Set.class,Collection.class,};
private static final Iterable> __factoryLoader;
private static final XmlParser __parser = initParser();
static
{
Iterable> loader=null;
try
{
// Use reflection to look up 1.6 service loader
// loader=ServiceLoader.load(ConfigurationProcessorFactory.class);
Class> slc = ClassLoader.getSystemClassLoader().loadClass("java.util.ServiceLoader");
Method load = slc.getMethod("load",Class.class);
loader=(Iterable>)load.invoke(null,ConfigurationProcessorFactory.class);
}
catch(Exception e)
{
LOG.ignore(e);
}
finally
{
__factoryLoader=loader;
}
}
/* ------------------------------------------------------------ */
private URL _url;
private String _dtd;
private ConfigurationProcessor _processor;
private final Map _idMap = new HashMap();
private final Map _propertyMap = new HashMap();
/* ------------------------------------------------------------ */
private synchronized static XmlParser initParser()
{
XmlParser parser = new XmlParser();
URL config60 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_6_0.dtd",true);
URL config76 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_7_6.dtd",true);
parser.redirectEntity("configure.dtd",config76);
parser.redirectEntity("configure_1_0.dtd",config60);
parser.redirectEntity("configure_1_1.dtd",config60);
parser.redirectEntity("configure_1_2.dtd",config60);
parser.redirectEntity("configure_1_3.dtd",config60);
parser.redirectEntity("configure_6_0.dtd",config60);
parser.redirectEntity("configure_7_6.dtd",config76);
parser.redirectEntity("http://jetty.mortbay.org/configure.dtd",config76);
parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",config76);
parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",config76);
parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",config76);
parser.redirectEntity("-//Jetty//Configure//EN",config76);
return parser;
}
/* ------------------------------------------------------------ */
/**
* Reads and parses the XML configuration file.
*
* @param configuration the URL of the XML configuration
* @throws IOException if the configuration could not be read
* @throws SAXException if the configuration could not be parsed
*/
public XmlConfiguration(URL configuration) throws SAXException, IOException
{
synchronized (__parser)
{
_url=configuration;
setConfig(__parser.parse(configuration.toString()));
_dtd=__parser.getDTD();
}
}
/* ------------------------------------------------------------ */
/**
* Reads and parses the XML configuration string.
*
* @param configuration String of XML configuration commands excluding the normal XML preamble.
* The String should start with a "<Configure ....>" element.
* @throws IOException if the configuration could not be read
* @throws SAXException if the configuration could not be parsed
*/
public XmlConfiguration(String configuration) throws SAXException, IOException
{
configuration = "\n"
+ configuration;
InputSource source = new InputSource(new StringReader(configuration));
synchronized (__parser)
{
setConfig( __parser.parse(source));
_dtd=__parser.getDTD();
}
}
/* ------------------------------------------------------------ */
/**
* Reads and parses the XML configuration stream.
*
* @param configuration An input stream containing a complete configuration file
* @throws IOException if the configuration could not be read
* @throws SAXException if the configuration could not be parsed
*/
public XmlConfiguration(InputStream configuration) throws SAXException, IOException
{
InputSource source = new InputSource(configuration);
synchronized (__parser)
{
setConfig(__parser.parse(source));
_dtd=__parser.getDTD();
}
}
/* ------------------------------------------------------------ */
private void setConfig(XmlParser.Node config)
{
if ("Configure".equals(config.getTag()))
{
_processor=new JettyXmlConfiguration();
}
else if (__factoryLoader!=null)
{
for ( Object factory : __factoryLoader)
{
// use reflection to get 1.6 methods
Method gcp;
try
{
gcp = factory.getClass().getMethod("getConfigurationProcessor",String.class,String.class);
_processor = (ConfigurationProcessor) gcp.invoke(factory,_dtd,config.getTag());
}
catch (Exception e)
{
LOG.warn(e);
}
if (_processor!=null)
break;
}
if (_processor==null)
throw new IllegalStateException("Unknown configuration type: "+config.getTag()+" in "+this);
}
else
{
throw new IllegalArgumentException("Unknown XML tag:"+config.getTag());
}
_processor.init(_url,config,_idMap, _propertyMap);
}
/* ------------------------------------------------------------ */
public Map getIdMap()
{
return _idMap;
}
/* ------------------------------------------------------------ */
/**
* @param map the ID map
* @deprecated use {@link #getIdMap()}.put(...)
*/
@Deprecated
public void setIdMap(Map map)
{
_idMap.clear();
_idMap.putAll(map);
}
/* ------------------------------------------------------------ */
/**
* @param map the properties map
* @deprecated use {@link #getProperties()}.putAll(...)
*/
@Deprecated
public void setProperties(Map map)
{
_propertyMap.clear();
_propertyMap.putAll(map);
}
/* ------------------------------------------------------------ */
public Map getProperties()
{
return _propertyMap;
}
/* ------------------------------------------------------------ */
/**
* Applies the XML configuration script to the given object.
*
* @param obj The object to be configured, which must be of a type or super type
* of the class attribute of the <Configure> element.
* @throws Exception if the configuration fails
* @return the configured object
*/
public Object configure(Object obj) throws Exception
{
return _processor.configure(obj);
}
/* ------------------------------------------------------------ */
/**
* Applies the XML configuration script.
* If the root element of the configuration has an ID, an object is looked up by ID and its type checked
* against the root element's type.
* Otherwise a new object of the type specified by the root element is created.
*
* @return The newly created configured object.
* @throws Exception if the configuration fails
*/
public Object configure() throws Exception
{
return _processor.configure();
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private static class JettyXmlConfiguration implements ConfigurationProcessor
{
XmlParser.Node _config;
Map _idMap;
Map _propertyMap;
public void init(URL url, XmlParser.Node config, Map idMap, Map properties)
{
_config=config;
_idMap=idMap;
_propertyMap=properties;
}
/* ------------------------------------------------------------ */
public Object configure(Object obj) throws Exception
{
// Check the class of the object
Class> oClass = nodeClass(_config);
if (oClass != null && !oClass.isInstance(obj))
{
String loaders = (oClass.getClassLoader()==obj.getClass().getClassLoader())?"":"Object Class and type Class are from different loaders.";
throw new IllegalArgumentException("Object of class '"+obj.getClass().getCanonicalName()+"' is not of type '" + oClass.getCanonicalName()+"'. "+loaders);
}
configure(obj,_config,0);
return obj;
}
/* ------------------------------------------------------------ */
public Object configure() throws Exception
{
Class> oClass = nodeClass(_config);
String id = _config.getAttribute("id");
Object obj = id == null?null:_idMap.get(id);
if (obj == null && oClass != null)
obj = oClass.newInstance();
if (oClass != null && !oClass.isInstance(obj))
throw new ClassCastException(oClass.toString());
configure(obj,_config,0);
return obj;
}
/* ------------------------------------------------------------ */
private static Class> nodeClass(XmlParser.Node node) throws ClassNotFoundException
{
String className = node.getAttribute("class");
if (className == null)
return null;
return Loader.loadClass(XmlConfiguration.class,className,true);
}
/* ------------------------------------------------------------ */
/**
* Recursive configuration routine.
* This method applies the nested Set, Put, Call, etc. elements to the given object.
*
* @param obj the object to configure
* @param cfg the XML nodes of the configuration
* @param i the index of the XML nodes
* @throws Exception if the configuration fails
*/
public void configure(Object obj, XmlParser.Node cfg, int i) throws Exception
{
String id = cfg.getAttribute("id");
if (id != null)
_idMap.put(id,obj);
for (; i < cfg.size(); i++)
{
Object o = cfg.get(i);
if (o instanceof String)
continue;
XmlParser.Node node = (XmlParser.Node)o;
try
{
String tag = node.getTag();
if ("Set".equals(tag))
set(obj,node);
else if ("Put".equals(tag))
put(obj,node);
else if ("Call".equals(tag))
call(obj,node);
else if ("Get".equals(tag))
get(obj,node);
else if ("New".equals(tag))
newObj(obj,node);
else if ("Array".equals(tag))
newArray(obj,node);
else if ("Ref".equals(tag))
refObj(obj,node);
else if ("Property".equals(tag))
propertyObj(node);
else
throw new IllegalStateException("Unknown tag: " + tag);
}
catch (Exception e)
{
LOG.warn("Config error at " + node,e.toString());
throw e;
}
}
}
/* ------------------------------------------------------------ */
/*
* Call a set method. This method makes a best effort to find a matching set method. The type of the value is used to find a suitable set method by 1.
* Trying for a trivial type match. 2. Looking for a native type match. 3. Trying all correctly named methods for an auto conversion. 4. Attempting to
* construct a suitable value from original value. @param obj
*
* @param node
*/
private void set(Object obj, XmlParser.Node node) throws Exception
{
String attr = node.getAttribute("name");
String name = "set" + attr.substring(0,1).toUpperCase(Locale.ENGLISH) + attr.substring(1);
Object value = value(obj,node);
Object[] arg =
{ value };
Class> oClass = nodeClass(node);
if (oClass != null)
obj = null;
else
oClass = obj.getClass();
Class>[] vClass =
{ Object.class };
if (value != null)
vClass[0] = value.getClass();
if (LOG.isDebugEnabled())
LOG.debug("XML " + (obj != null?obj.toString():oClass.getName()) + "." + name + "(" + value + ")");
// Try for trivial match
try
{
Method set = oClass.getMethod(name,vClass);
set.invoke(obj,arg);
return;
}
catch (IllegalArgumentException e)
{
LOG.ignore(e);
}
catch (IllegalAccessException e)
{
LOG.ignore(e);
}
catch (NoSuchMethodException e)
{
LOG.ignore(e);
}
// Try for native match
try
{
Field type = vClass[0].getField("TYPE");
vClass[0] = (Class>)type.get(null);
Method set = oClass.getMethod(name,vClass);
set.invoke(obj,arg);
return;
}
catch (NoSuchFieldException e)
{
LOG.ignore(e);
}
catch (IllegalArgumentException e)
{
LOG.ignore(e);
}
catch (IllegalAccessException e)
{
LOG.ignore(e);
}
catch (NoSuchMethodException e)
{
LOG.ignore(e);
}
// Try a field
try
{
Field field = oClass.getField(attr);
if (Modifier.isPublic(field.getModifiers()))
{
field.set(obj,value);
return;
}
}
catch (NoSuchFieldException e)
{
LOG.ignore(e);
}
// Search for a match by trying all the set methods
Method[] sets = oClass.getMethods();
Method set = null;
for (int s = 0; sets != null && s < sets.length; s++)
{
Class>[] paramTypes = sets[s].getParameterTypes();
if (name.equals(sets[s].getName()) && paramTypes.length == 1)
{
// lets try it
try
{
set = sets[s];
sets[s].invoke(obj,arg);
return;
}
catch (IllegalArgumentException e)
{
LOG.ignore(e);
}
catch (IllegalAccessException e)
{
LOG.ignore(e);
}
try
{
for (Class> c : __supportedCollections)
if (paramTypes[0].isAssignableFrom(c))
{
sets[s].invoke(obj,convertArrayToCollection(value,c));
return;
}
}
catch (IllegalAccessException e)
{
LOG.ignore(e);
}
}
}
// Try converting the arg to the last set found.
if (set != null)
{
try
{
Class> sClass = set.getParameterTypes()[0];
if (sClass.isPrimitive())
{
for (int t = 0; t < __primitives.length; t++)
{
if (sClass.equals(__primitives[t]))
{
sClass = __primitiveHolders[t];
break;
}
}
}
Constructor> cons = sClass.getConstructor(vClass);
arg[0] = cons.newInstance(arg);
set.invoke(obj,arg);
return;
}
catch (NoSuchMethodException e)
{
LOG.ignore(e);
}
catch (IllegalAccessException e)
{
LOG.ignore(e);
}
catch (InstantiationException e)
{
LOG.ignore(e);
}
}
// No Joy
throw new NoSuchMethodException(oClass + "." + name + "(" + vClass[0] + ")");
}
/**
* @param array the array to convert
* @param collectionType the desired collection type
* @return a collection of the desired type if the array can be converted
*/
private static Collection> convertArrayToCollection(Object array, Class> collectionType)
{
Collection> collection = null;
if (array.getClass().isArray())
{
if (collectionType.isAssignableFrom(ArrayList.class))
collection = convertArrayToArrayList(array);
else if (collectionType.isAssignableFrom(HashSet.class))
collection = new HashSet