A typical request from Tiles users was to be able to load Tiles definitions from a database. With Tiles 2.1, it is a pretty easy task.
The source of the example is available in the source distribution of Tiles, under the "tiles-test" module.
The first step is to design the database schema. Here we will show a small example that is far from perfect, anyway it will be enough for our example. The following is the picture of the database schema we will use.
Essentially, the main programming task is the development of a definition DAO. This interface is designed to provide an easy customization of definitions retrieval.
The method that you should implement is only getDefinition. You don't have to implement getDefinitions too completely (a simple throw UnsupportedOperationException will be enough) since it won't be called at all.
This is the source of getDefinition in our example: the JDBC DAO support of Spring is used to have a cleaner code.
/** {@inheritDoc} */ @SuppressWarnings("unchecked") public Definition getDefinition(String name, Locale locale) { List<Map<String, Object>> customizations = null; Long customizationId = null, parentCustomizationId = null; do { customizations = getJdbcTemplate().queryForList( SELECT_CUSTOMIZATION_BY_NAME_SQL, new Object[] { locale.toString() }); if (!customizations.isEmpty()) { Map<String, Object> customization = customizations.get(0); customizationId = ((Number) customization.get("ID")).longValue(); parentCustomizationId = numberToLong((Number) customization.get("PARENT_ID")); } else { locale = LocaleUtil.getParentLocale(locale); } } while (customizations.isEmpty()); return getDefinition(name, customizationId, parentCustomizationId, locale); }
In other words:
At this point the definition must be retrieved from the DB.
@SuppressWarnings("unchecked") protected DbDefinition getDefinition(String name, Long baseCustomizationId, Long baseParentCustomizationId, Locale locale) { DbDefinition definition = null; Long customizationId = baseCustomizationId; Long parentCustomizationId = baseParentCustomizationId; List<DbDefinition> definitions = null; boolean finished = false; do { definitions = getJdbcTemplate() .query(SELECT_DEFINITION_SQL, new Object[] { name, customizationId }, definitionRowMapper); if (definitions.isEmpty()) { if (parentCustomizationId != null) { Map<String, Object> customization = getJdbcTemplate().queryForMap( SELECT_CUSTOMIZATION_BY_ID_SQL, new Object[] { parentCustomizationId }); customizationId = ((Number) customization.get("ID")).longValue(); parentCustomizationId = numberToLong((Number) customization.get("PARENT_ID")); } else { finished = true; } } else { definition = definitions.get(0); finished = true; } } while (!finished); if (definition != null) { AttributeRowMapper attributeRowMapper = new AttributeRowMapper(definition); getJdbcTemplate().query(SELECT_ATTRIBUTES_SQL, new Object[] { definition.getId() }, attributeRowMapper); } return definition; }
The steps that are followed are:
Notice that the definition's inheritance is not resolved because it will be done by the implementation of DefinitionsFactory that will call the definitions DAO multiple times to resolve inheritance.
The DbDefinition is a simple extension of Definition with the addition of an id.
To use the definitions DAO we need to configure Tiles to use an alternate DefinitionsFactory that is LocaleDefinitionsFactory. This definitions factory resolves definitions one by one, by retrieving all extended definitions through calls to the definition DAO.
Though it may seem slow, it is memory-efficient and it is effective when the number of definitions is high.
Here will be using pure Java configuration. A new class extending BasicTilesContainerFactory must be created. It will be called TestDbTilesContainerFactory. This is the source:
public class TestDbTilesContainerFactory extends BasicTilesContainerFactory { /** {@inheritDoc} */ @Override protected DefinitionDAO<Locale> createLocaleDefinitionDao(Object context, TilesApplicationContext applicationContext, TilesRequestContextFactory contextFactory, LocaleResolver resolver) { LocaleDbDefinitionDAO definitionDao = new LocaleDbDefinitionDAO(); definitionDao.setDataSource((DataSource) applicationContext .getApplicationScope().get("dataSource")); return definitionDao; } /** {@inheritDoc} */ @Override protected LocaleDefinitionsFactory instantiateDefinitionsFactory( Object context, TilesApplicationContext applicationContext, TilesRequestContextFactory contextFactory, LocaleResolver resolver) { return new LocaleDefinitionsFactory(); } }
Create a Tiles listener this way:
public class TestDbTilesListener extends AbstractTilesListener { @Override protected TilesInitializer createTilesInitializer() { return new TestDbTilesInitializer(); } private static class TestDbTilesInitializer extends AbstractTilesInitializer { @Override protected AbstractTilesContainerFactory createContainerFactory( TilesApplicationContext context) { return new TestDbTilesContainerFactory(); } } }
In web.xml add this piece of configuration:
<listener> <listener-class>org.apache.tiles.test.listener.TestDbTilesListener</listener-class> </listener>
And you're done!