blob: 0ae93d57e709c45680deb6362da196e530b96cdb [file] [log] [blame]
~~ $Id$
~~
~~ Licensed to the Apache Software Foundation (ASF) under one
~~ or more contributor license agreements. See the NOTICE file
~~ distributed with this work for additional information
~~ regarding copyright ownership. The ASF licenses this file
~~ to you under the Apache License, Version 2.0 (the
~~ "License"); you may not use this file except in compliance
~~ with the License. You may obtain a copy of the License at
~~
~~ http://www.apache.org/licenses/LICENSE-2.0
~~
~~ Unless required by applicable law or agreed to in writing,
~~ software distributed under the License is distributed on an
~~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~~ KIND, either express or implied. See the License for the
~~ specific language governing permissions and limitations
~~ under the License.
~~
-----------
Loading definitions from a database
-----------
Loading definitions from a database
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
{{{/download-21.html}source distribution of Tiles}}, under the "tiles-test"
module.
* Database design
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.
[../images/db-schema.png] Database schema that will be used for the example.
* Create a Definition DAO
Essentially, the main programming task is the development of a
{{{../apidocs/org/apache/tiles/definition/dao/DefinitionDAO.html}definition DAO}}.
This interface is designed to provide an easy customization of definitions
retrieval.
The method that you should implement is only
{{{../apidocs/org/apache/tiles/definition/dao/DefinitionDAO.html#getDefinition(java.lang.String,%20K)}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.
** getDefinition
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:
[[1]] the current customization (in this case, the client's locale) is
identified;
[[2]] it is tried to retrieve the locale from the DB: if it is not found, it
tries with the parent locale (the parent locale of "en_US" is "en") until one
is found, or the default (no locale) is used;
[[3]] the definition for the supported minimum-parent-locale is retrieved and
passed to the caller.
** Retrieval of the definition from the DB
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:
[[1]] search for a definition that is usable with the customization id
(id of the locale in the DB) that is suggested by the caller;
[[2]] if the definition has not been found, the parent customization id is
used and the operation at point 1 is done, until a definition is found;
[[3]] if the definition has been found, the attributes are loaded from the DB.
[]
Notice that the definition's inheritance <<is not resolved>> because it will
be done by the implementation of
{{{../apidocs/org/apache/tiles/definition/DefinitionsFactory.html}DefinitionsFactory}}
that will call the definitions DAO multiple times to resolve inheritance.
The <<<DbDefinition>>> is a simple extension of
{{{../apidocs/org/apache/tiles/Definition.html}Definition}}
with the addition of an id.
* Configuration
To use the definitions DAO we need to configure Tiles to use an alternate
{{{../apidocs/org/apache/tiles/definition/DefinitionsFactory.html}DefinitionsFactory}}
that is
{{{../apidocs/org/apache/tiles/definition/LocaleDefinitionsFactory.html}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
{{{../apidocs/org/apache/tiles/factory/BasicTilesContainerFactory.html}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!