~~ $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 <<>> too completely (a simple <<>> will be enough) since it won't be called at all. ** getDefinition This is the source of <<>> 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> customizations = null; Long customizationId = null, parentCustomizationId = null; do { customizations = getJdbcTemplate().queryForList( SELECT_CUSTOMIZATION_BY_NAME_SQL, new Object[] { locale.toString() }); if (!customizations.isEmpty()) { Map 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 definitions = null; boolean finished = false; do { definitions = getJdbcTemplate() .query(SELECT_DEFINITION_SQL, new Object[] { name, customizationId }, definitionRowMapper); if (definitions.isEmpty()) { if (parentCustomizationId != null) { Map 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 <> 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 <<>> 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 <<>>. This is the source: ------------------------------------- public class TestDbTilesContainerFactory extends BasicTilesContainerFactory { /** {@inheritDoc} */ @Override protected DefinitionDAO 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 <<>> add this piece of configuration: ------------------------------------- org.apache.tiles.test.listener.TestDbTilesListener ------------------------------------- And you're done!