Skip to content

Commit

Permalink
Fixes #8913 - Review Jetty XML syntax to allow calling JDK methods (#…
Browse files Browse the repository at this point in the history
…8915)

* Fixes #8913 - Review Jetty XML syntax to allow calling JDK methods

Now `<Call>`, `<Get>` and `<Set>` elements can use the `class` attribute
to specify the exact class to perform method lookup.

Improved support for `<Property>`, `<SystemProperty>` and `<Env>` so that
attribute `name` is now optional (as specified in the DTD), and a
`deprecated` attribute may be present instead.
This is necessary to terminally deprecate properties that have
no replacement.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
  • Loading branch information
sbordet committed Nov 18, 2022
1 parent 8de1bad commit b43e2e5
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ var mystring = new String();

If an object with the id `mystring` already exists, then it is not created again but rather just referenced.

Within element `<Configure>`, the created object (if any) is in xref:og-xml-syntax-scope[scope] and may be the implicit target of other, nested, elements.

Typically the `<Configure>` element is used to configure a `Server` instance or `ContextHandler` subclasses such as `WebAppContext` that represent web applications.

[[og-xml-syntax-arg]]
Expand Down Expand Up @@ -187,12 +189,38 @@ This is equivalent to the following Java code:
var myhost = InetAddress.getByName("jdk.java.net");
----

The `class` attribute (or `<Class>` element) can also be used to specify the Java class or interface to use to lookup the non-``static`` method name.
This is necessary when the object in scope, onto which the `<Call>` would be applied, is an instance of a class that is not visible to Jetty classes, or not accessible because it is not `public`.
For example:

[source,xml,subs=normal]
----
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure>
<Call class="java.util.concurrent.Executors" name="newSingleThreadScheduledExecutor">
#<Call class="java.util.concurrent.ExecutorService" name="shutdown" />#
</Call>
</Configure>
----

In the example above, `Executors.newSingleThreadScheduledExecutor()` returns an object whose class is a private JDK implementation class.
Without an explicit `class` attribute (or `<Class>` element), it is not possible to invoke the method `shutdown()` when it is obtained via reflection from the private JDK implementation class, because while the method is `public`, the private JDK implementation class is not, therefore this exception is thrown:

[source]
----
java.lang.IllegalAccessException: class org.eclipse.jetty.xml.XmlConfiguration$JettyXmlConfiguration (in module org.eclipse.jetty.xml) cannot access a member of class java.util.concurrent.Executors$DelegatedExecutorService (in module java.base) with modifiers "public"
----

The solution is to explicitly use the `class` attribute (or `<Class>` element) of the `<Call>` element that is invoking the `shutdown()` method, specifying a publicly accessible class or interface that the object in scope extends or implements (in the example above `java.util.concurrent.ExecutorService`).

[[og-xml-syntax-get]]
===== `<Get>`

Element `<Get>` retrieves the value of a JavaBean property specified by the mandatory `name` attribute.

If the JavaBean property is `foo` (or `Foo`), `<Get>` first attempts to invoke _method_ `getFoo()`; failing that, attempts to retrieve the value from _field_ `foo` (or `Foo`).
If the JavaBean property is `foo` (or `Foo`), `<Get>` first attempts to invoke _method_ `getFoo()` or _method_ `isFoo()`; failing that, attempts to retrieve the value from _field_ `foo` (or `Foo`).

[source,xml]
----
Expand All @@ -212,6 +240,8 @@ If the JavaBean property is `foo` (or `Foo`), `<Get>` first attempts to invoke _
</Configure>
----

The `class` attribute (or `<Class>` element) allows to perform `static` calls, or to lookup the getter method from the specified class, as described in the xref:og-xml-syntax-call[`<Call>` section].

[[og-xml-syntax-set]]
===== `<Set>`

Expand All @@ -235,6 +265,8 @@ If the JavaBean property is `foo` (or `Foo`), `<Set>` first attempts to invoke _
</Configure>
----

The `class` attribute (or `<Class>` element) allows to perform `static` calls, or to lookup the setter method from the specified class, as described in the xref:og-xml-syntax-call[`<Call>` section].

[[og-xml-syntax-map]]
===== `<Map>` and `<Entry>`

Expand Down
88 changes: 48 additions & 40 deletions jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -522,9 +522,7 @@ private void set(Object obj, XmlParser.Node node) throws Exception
String propertyValue = null;

Class<?> oClass = nodeClass(node);
if (oClass != null)
obj = null;
else
if (oClass == null)
oClass = obj.getClass();

// Look for a property value
Expand Down Expand Up @@ -553,9 +551,9 @@ private void set(Object obj, XmlParser.Node node) throws Exception
value = propertyValue;
Object[] arg = {value};

Class<?>[] vClass = {Object.class};
Class<?> vClass = Object.class;
if (value != null)
vClass[0] = value.getClass();
vClass = value.getClass();

if (LOG.isDebugEnabled())
LOG.debug("XML {}.{} ({})", (obj != null ? obj.toString() : oClass.getName()), setter, value);
Expand All @@ -582,8 +580,8 @@ private void set(Object obj, XmlParser.Node node) throws Exception
// Try for native match
try
{
Field type = vClass[0].getField("TYPE");
vClass[0] = (Class<?>)type.get(null);
Field type = vClass.getField("TYPE");
vClass = (Class<?>)type.get(null);
Method set = oClass.getMethod(setter, vClass);
invokeMethod(set, obj, arg);
return;
Expand Down Expand Up @@ -715,7 +713,7 @@ private void set(Object obj, XmlParser.Node node) throws Exception
}

// No Joy
String message = oClass + "." + setter + "(" + vClass[0] + ")";
String message = oClass + "." + setter + "(" + vClass + ")";
if (types != null)
message += ". Found setters for " + types;
NoSuchMethodException failure = new NoSuchMethodException(message);
Expand Down Expand Up @@ -828,19 +826,11 @@ private Object get(Object obj, XmlParser.Node node) throws Exception

Class<?> oClass;
if (clazz != null)
{
// static call
oClass = Loader.loadClass(clazz);
obj = null;
}
else if (obj != null)
{
oClass = obj.getClass();
}
else
{
throw new IllegalArgumentException(node.toString());
}

if (LOG.isDebugEnabled())
LOG.debug("XML get {}", name);
Expand Down Expand Up @@ -906,19 +896,11 @@ private Object call(Object obj, XmlParser.Node node) throws Exception

Class<?> oClass;
if (clazz != null)
{
// static call
oClass = Loader.loadClass(clazz);
obj = null;
}
else if (obj != null)
{
oClass = obj.getClass();
}
else
{
throw new IllegalArgumentException(node.toString());
}

if (LOG.isDebugEnabled())
LOG.debug("XML call {}", name);
Expand Down Expand Up @@ -955,10 +937,6 @@ private Object call(Class<?> oClass, String methodName, Object obj, Args args) t
Object[] arguments = args.applyTo(method);
if (arguments == null)
continue;
if (Modifier.isStatic(method.getModifiers()) != (obj == null))
continue;
if ((obj == null) && method.getDeclaringClass() != oClass)
continue;

try
{
Expand Down Expand Up @@ -1177,27 +1155,39 @@ private Object propertyObj(XmlParser.Node node) throws Exception
{
AttrOrElementNode aoeNode = new AttrOrElementNode(node, "Id", "Name", "Deprecated", "Default");
String id = aoeNode.getString("Id");
String name = aoeNode.getString("Name", true);
String name = aoeNode.getString("Name", false);
List<Object> deprecated = aoeNode.getList("Deprecated");
String dftValue = aoeNode.getString("Default");

if (name == null && deprecated.isEmpty())
throw new IllegalStateException("Invalid <Property> element");

// Look for a value
Map<String, String> properties = _configuration.getProperties();
String value = properties.get(name);
String value = name == null ? null : properties.get(name);

// Look for a deprecated name value
String alternate = null;
if (!deprecated.isEmpty())
{
for (Object d : deprecated)
{
String v = properties.get(StringUtil.valueOf(d));
if (d == null)
continue;
String v = properties.get(d.toString());
if (v != null)
{
if (value == null)
LOG.warn("Property '{}' is deprecated, use '{}' instead", d, name);
{
if (name == null)
LOG.warn("Property '{}' is deprecated, no replacement available", d);
else
LOG.warn("Property '{}' is deprecated, use '{}' instead", d, name);
}
else
LOG.warn("Property '{}' is deprecated, value from '{}' used", d, name);
{
LOG.warn("Property '{}' is deprecated, value from '{}' used instead", d, name);
}
}
if (alternate == null)
alternate = v;
Expand Down Expand Up @@ -1228,12 +1218,15 @@ private Object systemPropertyObj(XmlParser.Node node) throws Exception
{
AttrOrElementNode aoeNode = new AttrOrElementNode(node, "Id", "Name", "Deprecated", "Default");
String id = aoeNode.getString("Id");
String name = aoeNode.getString("Name", true);
String name = aoeNode.getString("Name", false);
List<Object> deprecated = aoeNode.getList("Deprecated");
String dftValue = aoeNode.getString("Default");

if (name == null && deprecated.isEmpty())
throw new IllegalStateException("Invalid <SystemProperty> element");

// Look for a value
String value = System.getProperty(name);
String value = name == null ? null : System.getProperty(name);

// Look for a deprecated name value
String alternate = null;
Expand All @@ -1247,9 +1240,16 @@ private Object systemPropertyObj(XmlParser.Node node) throws Exception
if (v != null)
{
if (value == null)
LOG.warn("SystemProperty '{}' is deprecated, use '{}' instead", d, name);
{
if (name == null)
LOG.warn("SystemProperty '{}' is deprecated, no replacement available", d);
else
LOG.warn("SystemProperty '{}' is deprecated, use '{}' instead", d, name);
}
else
{
LOG.warn("SystemProperty '{}' is deprecated, value from '{}' used", d, name);
}
}
if (alternate == null)
alternate = v;
Expand Down Expand Up @@ -1281,22 +1281,30 @@ private Object envObj(XmlParser.Node node) throws Exception
{
AttrOrElementNode aoeNode = new AttrOrElementNode(node, "Id", "Name", "Deprecated", "Default");
String id = aoeNode.getString("Id");
String name = aoeNode.getString("Name", true);
String name = aoeNode.getString("Name", false);
List<Object> deprecated = aoeNode.getList("Deprecated");
String dftValue = aoeNode.getString("Default");

if (name == null && deprecated.isEmpty())
throw new IllegalStateException("Invalid <Env> element");

// Look for a value
String value = System.getenv(name);
String value = name == null ? null : System.getenv(name);

// Look for a deprecated name value
if (value == null && !deprecated.isEmpty())
{
for (Object d : deprecated)
{
value = System.getenv(StringUtil.valueOf(d));
if (d == null)
continue;
value = System.getenv(d.toString());
if (value != null)
{
LOG.warn("Property '{}' is deprecated, use '{}' instead", d, name);
if (name == null)
LOG.warn("Property '{}' is deprecated, no replacement available", d);
else
LOG.warn("Property '{}' is deprecated, use '{}' instead", d, name);
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ System Property Element.
This element allows JVM System properties to be retrieved as
part of the value of elements such as Set, Put, Arg, etc.
The name attribute specifies the property name and the optional
default argument provides a default value.
default attribute provides a default value.
<SystemProperty name="Test" default="value" />
Expand All @@ -303,15 +303,14 @@ Environment variable Element.
This element allows OS Environment variables to be retrieved as
part of the value of elements such as Set, Put, Arg, etc.
The name attribute specifies the env variable name and the optional
default argument provides a default value.
default attribute provides a default value.
<Env name="Test" default="value" />
This is equivalent to:
String v=System.getEnv("Test");
if (v==null) v="value";
-->
<!ELEMENT Env (Id?,Name?,Deprecated*,Default?) >
<!ATTLIST Env %ID_ATTR; %NAME_ATTR; %DEPRECATED_ATTR; %DEFAULT_ATTR; >
Expand All @@ -321,7 +320,7 @@ This is equivalent to:
Property Element.
This element allows arbitrary properties to be retrieved by name.
The name attribute specifies the property name and the optional
default argument provides a default value.
default attribute provides a default value.
-->
<!ELEMENT Property (Id?,Name?,Deprecated*,Default?) >
<!ATTLIST Property %ID_ATTR; %NAME_ATTR; %DEPRECATED_ATTR; %DEFAULT_ATTR; >

0 comments on commit b43e2e5

Please sign in to comment.