JicarillaWiki   TheSpecificInstanceProblem UserPreferences
 
HelpContents Search Diffs Info Edit Subscribe XML Print View

This wiki is in 'slumber' mode, just like its associated sourceforge project. Edits are disabled, the content is potentially stale and is not maintained. That said, it contains some really useful stuff still. Enjoy!


The Problem

Consider a PicoContainer-compatible component like:

public class DataSourceAggregrator 
{ 
  public DataSourceAggregrator( DataSource ds1, DataSource ds2 ) { /* ... */ } 
} 

that won't work. Pico doesn't know which datasource to provide. The problem has existed in avalon for ages. How does one select a specific instance?

Previous attempts to solve it

A Selector-based approach

The ServiceSelector is an attempt to solve it.You might use something like that in Pico like so:

public class DataSourceAggregrator 
{ 
  public DataSourceAggregrator( Selector dsSelector ) 
  { 
    DataSource ds1 = (DataSource)dsSelector.select( "hint1" ); 
    DataSource ds2 = (DataSource)dsSelector.select( "hint2" ); 
    /* ... */ 
  } 
} 

and then you need to manually construct a Selector instance and feed it to Pico. You need to build that yourself since otherwise you run into the same problem as we did in the first place!

In the meantime, we again sacrifice our precious type safety.

A Selector for each component

To avoid the type safety issue, apps I wrote on top of AvalonPhoenix used component-specific managers:

public class DataSourceAggregrator 
{ 
  public DataSourceAggregrator( DataSourceManager dsm ) 
  { 
    DataSource ds1 = dsm.getDatasource( "hint1" ); 
    DataSource ds2 = dsm.getDatasource( "hint2" ); 
    /* ... */ 
  } 
} 

the disadvantage here is of course the amount of code you need to write (a Manager interface, a Manager implementation, and specific code in your constructor (or the service() method) to talk to the manager.

XML Metadata

Well, that all sucked. The idea that took hold @ avalon is to populate the ServiceManager itself with the various components by using distinct roles. What happens now is that you need to figure out how to tell the container what component to provide in what role. This is essentially what Spring and all the other projects are doing. It might look like

public class DataSourceAggregrator 
{ 
  public DataSourceAggregrator( DataSource ds1, DataSource ds2 ) { /* ... */ } 
} 

with

<?xml version="1.0"?> 
 
<components> 
 
  <component class="mydriver.DataSource" interface="javax.sql.DataSource" name="ds1"> 
    <constructor> 
      <arg>jdbc://mydriver:myhost:myuser:mypassword:mydb/</arg> 
    </constructor> 
  </component> 
  <component class="mydriver.DataSource" interface="javax.sql.DataSource" name="ds2"> 
    <constructor> 
      <arg>jdbc://mydriver:myotherhost:myuser:mypassword:mydb/</arg> 
    </constructor> 
  </component> 
 
  <component class="myapp.DataSourceAggregator" name="dsa"> 
    <constructor> 
      <arg><ref name="ds1"/></arg> 
      <arg><ref name="ds2"/></arg> 
    </constructor> 
  </component> 
 
</components> 

brilliant, right? Problem solved, type safety preserved? No! We have type safety in java at the cost of a new xml-based programming language that has no type safety, no debugger, no exception handling, etc etc. Consider how hard it is to find the problem in this file:

<?xml version="1.0"?> 
 
<components> 
 
  <component class="mydriver.DataSource" interface="javax.sql.DataSource" name="ds1"> 
    <constructor> 
      <arg>jdbc://mydriver:myhost:myuser:mypassword:mydb/</arg> 
    </constructor> 
  </component> 
  <component class="mydriver.DataSource" interface="javax.sql.DataSource" name="ds2"> 
    <constructor> 
      <arg>jdbc://mydriver:myotherhost:myuser:mypassword:mydb/</arg> 
    </constructor> 
  </component> 
 
  <component class="myapp.DataSourceAggregator" name="dsa"> 
    <constructor> 
      <arg><ref name="ds1"/></arg> 
      <arg><ref name="ds1"/></arg> 
    </constructor> 
  </component> 
 
</components> 

(in case you don't spot it: the constructor for the aggregator receives the same instance two times, which is not desired).

Attribute Metadata

Okay, so xml sucks for programming. How about we write a program to generate xml from something more intuitive? Like this:

/** 
 * @component.key ref="ds1" 
 */ 
public class MyDataSource { /* ... */ } 

you could use a tool like XDoclet to figure out that, when this MyDataSource is loaded, it should be associated with the parameter named "ds1" in the constructor. All existing solutions today are less elegant; they require more attributes and declarations than this to work. A comprehensive example of such a setup is at http://avalon.apache.org/sandbox/merlin/meta/.

You still have the disadvantages of lack of type safety, but at least data is in the same sourcefile, and the declaration of your setup is simpler because the parser can figure out relationships between the java source and the attributes.

However, reliance on this approach creates another problem: what if you don't have control over the sourcefile? To still be able to use a component, you must still be able to hand-edit the xml. Which creates the same problems all over again.

Back to registries and factories?

Disgruntled, we might be tempted to start using registries of some kind again, coupled with unique URIs, whenever we need something complex. This is indeed feasible, as successful integration of existing jndi-based applications with AvalonPhoenix has shown. But the problem with IoC is that, once you allow it to be thrown out, all the additional additional security and predicatability it offers is again gone.

A scriptable setup

I think I might have the best alternative.

What about recognizing that we have a problem still unsolved: we want type safety, we want IoC and SoC, we don't want "static", nor do we want jndi, now do we want container-specific pseudo-languages in xml or javadoc, but we do need to defer binding component implementations until runtime.

Here's an approach:

public class DataSourceAggregrator 
{ 
  public DataSourceAggregrator( DataSource ds1, DataSource ds2 ) { /* ... */ } 
} 

# conf/myapp.DataSourceAggregator.init.py 
instance = constructor( container.get( "datasource1" ), container.get( "datasource2" ) ) 

instead of designing our own scripting language, we use one of the best available, and only use it to solve the part of the problem we can't comfortably do in java. You are now free to refactor your component, perhaps like so:

public class DataSourceAggregrator 
{ 
  public DataSourceAggregrator() { /* ... */ } 
  public addDataSource( DataSource ds ) { /* ... */ } 
} 

# conf/myapp.DataSourceAggregator.init.py 
instance = constructor() 
instance.addDataSource( container.dependencies.datasource1 ) 
instance.addDataSource( container.dependencies.datasource2 ) 

the best part is that the python script is compiled into a regular java class at runtime, which allows us to use a normal debugger to catch any problems. Furthermore, jython will still provide us with meaningful exceptions in case something does go wrong.

You might be tempted into

public class DataSourceAggregrator 
{ 
  public DataSourceAggregrator() { /* ... */ } 
  /** 
   * @component.init="instance.addDataSource( container.get( 'datasource1' ) )" 
   * @component.init="instance.addDataSource( container.get( 'datasource2' ) )" 
   */ 
  public addDataSource( DataSource ds ) { /* ... */ } 
} 

to generate the python script from attributes, reducing the size of the sourcefile again. But I've found that having a hand-written seperate script file works better, since you get the syntax checking support of your favorite python editor. What might make sense is

/** 
 * @component.init.script = "conf/myapp.DataSourceAggregator.init.py" 
 * @component.shutdown.script = "conf/myapp.DataSourceAggregator.shutdown.py" 
 */ 
public class DataSourceAggregrator 
{ 
  public DataSourceAggregrator() { /* ... */ } 
  public addDataSource( DataSource ds ) { /* ... */ } 
} 

to allow multiple scripts to be used for a single component. Of course, you could also accomplish that using a script like

# conf/myapp.DataSourceAggregator.init.py 
include('conf/common.init.py') 

I've yet to try either.

Taking it further

Once you get something like this set up, it makes no sense at all anymore to place any kind of limitation on what constitutes a valid component. Simply set up an environment that will allow jython scripts to handle initialization and composition for you, provide some default scripts and some utility code, and your framework stays lean and mean.

Addendum: argument names for selection

Back to the original problem:

public class DataSourceAggregrator 
{ 
  public DataSourceAggregrator( DataSource ds1, DataSource ds2 ) { /* ... */ } 
} 

consider the reason this is not a problem when you do composition by hand: there is some logical information associated with the argument *name* and you use that to do sensible argument ordering. Unfortunately, the name of the arguments is not available inside the class files, only inside the sources and the javadoc. But it should be possible to have a parser generate this script:

# conf/myapp.DataSourceAggregator.init.py 
instance = constructor( container.get( "ds1" ), container.get( "ds2" ) ) 

It seems like the pico/nano people will be looking at solving this stuff like that.


PythonPowered
EditText of this page (last modified 2007-02-18 14:14:22)
FindPage by browsing, title search , text search or an index
Or try one of these actions: AttachFile, LikePages, LocalSiteMap, SpellCheck

Creative Commons License
The contents of this wiki are licensed under a Creative Commons License.