| ~~ $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(); |
| } |
| } |
| ------------------------------------- |
| |
| In <<<web.xml>>> add this piece of configuration: |
| |
| ------------------------------------- |
| <servlet> |
| <servlet-name>tiles-db</servlet-name> |
| <servlet-class>org.apache.tiles.web.startup.TilesServlet</servlet-class> |
| <init-param> |
| <param-name>org.apache.tiles.factory.AbstractTilesContainerFactory</param-name> |
| <param-value>org.apache.tiles.test.factory.TestDbTilesContainerFactory</param-value> |
| </init-param> |
| </servlet> |
| ------------------------------------- |
| |
| And you're done! |
| |