You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@struts.apache.org by Greg Reddin <gr...@fnf.com> on 2005/08/01 17:34:11 UTC

[tiles] Reloadable Configuration and Refactoring (long)

I wanted to send an update on the work I've been doing with Tiles.  I 
should have some code by today or tomorrow.  I'm refactoring the 
DefinitionsFactory structure (as described below), creating a Filter 
that will reload the factory if it's out of date, and writing unit tests 
to verify all the work.  In addition to unit tests I'm going to write a 
web app (perhaps an existing example app) that will verify everything 
works the same as before refactoring.

If anybody has time to review the approach I'm taking I'd love some 
feedback.  I'm pretty green when it comes to synchronization so please 
let me know if my assumptions about it are incorrect.  Also, I know 
Tiles pretty well, but I really hope I'm not changing its behavior at 
all with this refactoring.  I plan to do more extensive testing to 
verify that, but there's just so many nuances to definition inheritance, 
I18N, etc. that I might be getting something wrong.  Lastly, this 
refactoring does not try to preserve backwards compatibility.  It should 
not require changes to JSPs or web applications using Tiles out of the 
box.  But if anyone has customized the DefinitionsFactory structure 
rework would be required to use my mods.  If I need to support backwards 
compatibility I'll take that into consideration.  But since Tiles is a 
new standalone project, I'm not sure if backwards compatibility is an 
issue.  Please let me know what you think.  Now... on to the details:

The goals of the refactoring are:

*  Separate the concern of reading XML from the DefinitionsFactory 
structure.
*  Separate the object graph of ComponentDefinitons from the factory 
which reads and updates it.
*  Add a facility to reload the ComponentDefinitions if the config 
sources have changed.

The problem of separating the object graph from the factory is that both 
have to be stored in application scope and both will have to be 
synchronized when they are being updated.  But, the object graph does 
not have to be synchronized while the factory is being updated -- if 
that makes any difference.  Here's the basic flow of the Reload Filter

     ComponentDefinitionsSet definitions = (ComponentDefinitionsSet)
         servletContext.getAttribute(KEY);
     DefinitionsFactory factory = (DefinitionsFactory)
         servletContext.getAttribute(OTHER_KEY);
     if (factory instanceof ReloadableDefiniitionsFactory) {
         if (((ReloadableDefinitionsFactory) factory).refeshRequired()) {
             synchronized (factory) {
                 ComponentDefinitions newDefs =
                         factory.readDefinitions();
             }

             synchronized (definitions) {
                 definitions.reset(newDefs);
             }
         }
     }



The revised DefinitionsFactory interface looks like this:

public interface DefinitionsFactory {
   public void init(Map params) throws DefinitonsFactoryException;
   public void addSource(Object source);
   public ComponentDefinitionsSet readDefinitions()
         throws DefinitionsFactoryException;
   public void addDefinitions(ComponentDefinitionsSet definitions,
         Locale locale)
         throws DefinitionsFactoryException;
}

The default implementation is UrlDefinitionsFactory.  It takes URL-based 
sources and reads Tiles configuration via a DefinitionsReader interface 
(described later).  It has the following properties:

*  addSource takes an URL object that represents a base XML file 
(tiles-defs.xml).  Internally, it takes an InputStream from this URL to 
pass to the DefinitionsReader.

*  readDefinitions calls the DefinitionsReader for each source (XML 
file) and returns a consolidated object graph.

*  addDefinitions takes an existing object graph and a locale, reads 
localized definitions, and adds them to the object graph.


A factory signifies it is reloadable by implementing the 
ReloadableDefinitionsFactory interface.  It is as follows:

public interface ReloadableDefinitionsFactory {
   public boolean refreshRequired();
}

This method will return true if the definitions sources have been modified.


Reading definitions from a data source has been externalized into its 
own interface.  The reader interface is:

public interface DefinitionsReader {
   public void init(Map params) throws DefinitionsFactoryException;
   public Map read(Object source) throws DefinitionsFactoryException;
}

The defualt implementation is DigesterDefinitionsReader.  The init 
method sets up digester with its parameters.  The read method reads XML 
data from an input stream and returns a Map of definitions.  It does 
*not* do Definitions inheritance.  It simply returns a Map of 
definitions to the factory and leaves the inheritance up to the 
ComponentDefinitionsSet object graph described below:


Here is the object graph interface:

public interface ComponentDefinitionsSet {
   public void addDefinition(ComponentDefinition);
   public ComponentDefinition getDefinition(String name);
   public ComponentDefinition getDefinition(String name, Locale locale);
   public boolean isLocaleProcessed(Locale locale);
   public void addDefinitions(Map definitons);
   public void addLocaleSpecificDefinitions(
         Map definitions, Locale locale));
   public void resolveInheritances();
   public void reset(ComponentDefinitions newDefs);
}

The object contains 2 maps of definitions: a base map keyed by name, and 
map of locale-specific definitions.  The locale-specific map is actually 
a map of maps of definitions.  It is keyed by locale and the definitions 
maps are keyed by name.

I think most of the methods are self-explanatory, but here's a few notes:

*  The isLocaleProcessed method returns true if a given locale has been 
processed.  The purpose of this method is to avoid unnecessary 
synchronization.  If a locale is given and it has already been processed 
but a configuration for that locale does not exist, there is no reason 
to lock the definitions to read the files again.

*  The reset method is basically a copy constructor.  It clears the 
existing object graph and copies in the new one.  The reason I modelled 
it this way is for synchronization purposes.  If you pull the 
definitions reference from ServletContext and synchronize it to update 
it, then change the reference to point to a new object and place that 
new object in ServletContext, threads that are still referencing the old 
object will still be using old data.  But if we lock the definitions 
object then update its contents, anyone who is referencing that object 
will see the new contents.

If you made it this far, thanks :-)  Please let me know what you think. 
  I'll have code soon, I promise!!

Greg

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@struts.apache.org
For additional commands, e-mail: dev-help@struts.apache.org