BZZZZT (private joke)
:D:D
On the other-hand, maybe I'm missing the point ...
that seems to be the case. It'll probably be difficult to get it across though. Lets try a different approach....
Abstract ========================================================================
Scripting languages have an ideal syntax for configuration
files. They are *way* cleaner and simpler than XML, making
things simpler for the user. They are *way* more 'natural'
than any other solution, making things simpler for the
component author. They are easier to support in a container
than XML. A scripting language like BeanShell can 'pretend' to be java,
allowing all the type safety features in java development
tools to kick in. The downside with scripts might be that they are usually
active rather than passive, and that can be a security risk.
Though standard java security management can be used to avoid
those risks. My earlier e-mail was mostly about the worries you have
when you take the next step after a scriptable configuration:
a scriptable lifecycle (or even scriptable everything).Avalon-style Configuration
========================================================================
Configuration, avalon-style, is essentially the capturing of the component-intrinsic variants of a component (as opposed to its invariants or the container-intrinsic variants) into a generic value object. This value object is created by end users, typically using xml, according to a specification provided by the component author (ideally an xml schema, often some javadoc examples), and provided to the component by the container during the component startup phase.
Configuration validation would ideally be part of the container's featureset, however, currently, most containers don't do configuration validation and such validation is, in practice, left up to the components. Also, in practice, xml-based configuration validation can be very hard; xml simply isn't built for programming. Example:
MyBlah.xconf
--------------------------------------------------------------fragment--
<config>
<numberOfThreads>15</numberOfThreads>
<!-- ... -->
</config>MyBlah.xconf.wrong
--------------------------------------------------------------fragment--
<config>
<numberOfThreads>fifteen</numberOfThreads>
<!-- ... -->
</config>MyBlah.xconf.wrong2
--------------------------------------------------------------fragment--
<config>
<numberOfThreads>101</numberOfThreads>
<!-- ... -->
</config>MyBlah.xconf.wrong3
--------------------------------------------------------------fragment--
<config>
<numberOfThread>20</numberOfThread>
<!-- ... -->
</config>MyBlah.java ------------------------------------------------------------------------ import org.apache.avalon.framework.configuration.*;
package com.blah;
public class MyBlah implements Blah, Configurable
{
private int m_threads; public void configure( Configuration config )
throws ConfigurationException
{
setNumberOfThreads(
config.getChild("numberOfThreads").getValueAsInteger() );
// will fail
} public void setNumberOfThreads( int threads )
{
assert threads > 0 : "number of threads must be bigger than 1";
assert threads < 100 :
"number of threads must be smaller than 100";m_threads = threads; } }
Fail-safe components thus do something like
--------------------------------------------------------------fragment--
public void configure( Configuration config )
{
int numberofthreads;
try
{
numberofthreads =
config.getChild("threads").getValueAsInteger();
}
catch( ConfigurationException ce )
{
numberofthreads = DEFAULT_NUMBER_OF_THREADS;
getLog().warn(
"An error occured parsing the configuration: unable figure out"
+ " the number of threads; proceeding with the default value: "
+ DEFAULT_NUMBER_OF_THREADS, ce );
}
setNumberOfThreads( numberofthreads );
}
public final static int DEFAULT_NUMBER_OF_THREADS = 15;
------------------------------------------------------------------------There is quite a bit of argument verification (a try/catch clause, provision of default values on failure, two assertions) in addition to requiring the component author to deal with conversion from a generic configuration interface into the component-specific information.
Any of the failures in the config files above will only be detected at runtime (unless the component author provides a DTD and the user has an editor that checks against that DTD *and* takes the time to configure the tool to use the DTD). An error message might look like (in the case of a good component writer and a good container):
container.log --------------------------------------------------------------fragment-- ERROR - MyBlah - An error occured parsing the configuration: unable figure out the number of threads; proceeding with the default value: 15 A configuration error occured on line 2 of MyBlah.xconf.wrong2: <stack trace goes here/>
but in practice its usually not quite as neat.
Scripted-style configuration
========================================================================
Configuration, scripted, is essentially the capturing of the component-intrinsic variants of a component (as opposed to its invariants; often the container-intrinsic variants are subject to some scripting somewhere as well) either into a component-specific value object or directly into the component state. In both cases, the creation of the value object or the setting of the state happens in a script.
The creation of the component-specific value object or the setting of the component state is done by end users, typically using a scripting language, according to a specification provided by the component author (in the case of a component-specific value object, the specification is encoded into the value object, in the case of component state, the specification is encoded into the component its work interface).
Configuration validation would ideally built into an end user tool that enforces appropriate contracts (in the case of configuration specified as java code, an IDE works very well). In practice, that is only feasible using beanshell in 'strict' mode; dominant in the field are other scripting languages and a trial-and-error approach.
Consider the above MyBlah class as a POJO component which is scripted by a beanshell script in 'strict' mode (that means only actual java syntax is allowed, no beanshell-specific shortcuts):
MyBlah2.java ------------------------------------------------------------------------ package com.blah;
public class MyBlah implements Blah
{
public final static int DEFAULT_NUMBER_OF_THREADS = 15;
int numberofthreads = DEFAULT_NUMBER_OF_THREADS; public void setNumberOfThreads( int threads )
{
assert threads > 0 : "number of threads must be bigger than 1";
assert threads < 100 :
"number of threads must be smaller than 100";m_threads = threads; } }
MyBlah.configurator.strict.bsh ------------------------------------------------------------------------ import org.apache.avalon.framework.configuration.Configurator; import com.blah.MyBlah;
public class MyBlahConfigurator implements Configurator
{
public void configure( Object component )
{
assert component instanceof MyBlah :
"component must be instance of MyBlah!"; MyBlah blah = (MyBlah)component;
component.setNumberOfThreads( 15 );
}
}
------------------------------------------------------------------------trick your editor into believing ".bsh" files are similar java files, but should not be compiled, and you have near-complete type safety. We have just a little bit of error-checking left (three assertions), things are /completely/ type-safe, and any stack traces in the event of an error will likely provide a lot more clarity. The downside: your end user is required to know a bit of java. The configuration file is more than a bit complex (though, I would argue, not *that* much more complex than xml).
So we put beanshell into its "enhanced" mode, move some of the code into the container (the instanceof check, in particular; casts are not neccessary in beanshell, and we do a `with(component)`), and you reduce the above class definition to:
MyBlah.init.bsh (provided by end user) ------------------------------------------------------------------------ numberOfThreads = 15;
MyBlah.init.bsh.wrong (provided by end user) ------------------------------------------------------------------------ numberOfThreads = 150;
MyBlah.init.bsh.wrong2 (provided by end user) ------------------------------------------------------------------------ numberOfThread = 10;
MyBlah.init.default.bsh (provided by component developer) ------------------------------------------------------------------------ numberOfThreads = 15;
(yes, really, this can be a valid beanshell code snippet and I've got proof-of-concept code that illustrates this). The downside here is that there is currently no tool around which can ensure compile-time (let alone editor-time, like you get in 'strict' mode) safety. But it sure as hell is a lot cleaner, and because the format is so much simpler, mistakes might not be so easily made.
An error message might look like:
container.log --------------------------------------------------------------fragment-- ERROR - MyBlah - An error occured parsing configuration file MyBlah.init.bsh.wrong on line 1: assertion failed: number of threads must be smaller than 100. Proceeding with the default value: 15 <stack trace goes here/>
The configuration is a lot friendlier than using xml, any error message can be just as friendly and just as specific, the container code that handles all this can be just as simple, and, what's more, there is less checking to do for the component author.
The security issue
========================================================================
The potential downside with scripts is that they are active, whereas value objects are passive. Example:
MyBlah.init.bsh.trytohack (provided by end user) ------------------------------------------------------------pseudocode-- numberOfThreads = 15;
stop(); // attempt to stop the MyBlah component dispose();
// wreak havoc import com.blah.bank.AccountManager;
billGatesAccount = 12345; myAccount = 54321; AccountManager.transfer( 100000, billGatesAccount, myAccount ); ------------------------------------------------------------------------
the solution here is to
- either implement a java security policy that denies a configuration script the ability to do anything but configure its component, or, more simple and more common, or
- to assume that whomever has capability of changing the configuration can be trusted.
Giving up a little more safety
========================================================================
So what was I all on about with sacrificing safety and soundness for flexibility and stuff? Well, the above just scripts configuration. However, it can often be a good idea to just script the complete startup sequence. The only thing you let the container do is the dependency management and provision of an "execution context"; the component instantation and initialization is all up to the script. So something like:
MyBlah.startup.bsh ------------------------------------------------------------pseudocode-- logger = context.getLogger() workingDirectory = ContextUtil.getWorkingDirectory( context ) otherComponent = container.get( OtherComponent.ROLE )
component = MyBlah( logger, workingDirectory, otherComponent ) component.numberOfThreads = 15 component.initialize() ------------------------------------------------------------------------
you give up some safety here, because you are now depending on the configuration script to properly handle the lifecycle startup contract (in this case, the scripter might forget to call initialize(). What you gain, of course, is flexibility: you can support any component lifecycle with no extra container code or any kind of specification at all, by virtue of allowing a component-developer-provided, possibly end-user modified script to deal with it.
And you don't have to stop there. I'm currently experimenting with exactly how much functionality to move into scripts. I'm working on stuff which has the following in its Main class:
Main.java
--------------------------------------------------------------fragment--
public final static void main( String[] args ) throws Exception
{
Log log = null;
Server server = null; try
{
Interpreter i = new Interpreter();
i.set( "args", args );
i.source( "startup.bsh" ); log = (Log)i.get( "log" );
server = (Server)i.get( "server" );Now, /this/ is definately an approach where "startup.bsh" is pretty elaborate and places a significant burden on its maintainer to make sure it works.
Conclusion
========================================================================
Scripted configuration is definately worth a big BZZZZT! Its way cleaner, cooler, faster, simpler, smarter and simply better than xml-based configuration.
You can go further than scripted configuration and script large parts of your application. Only *then* are you on slippery ground.
g'night!
- Leo
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
