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