From 786e349d94bc052028f59c037a6fcff7eb83d335 Mon Sep 17 00:00:00 2001 From: Atsushi Yoshikawa Date: Sat, 9 Mar 2019 11:57:22 +0900 Subject: [PATCH] new feature reloadable i18n codelist #11 --- .../codelist/i18n/AbstractI18nCodeList.java | 127 ++++++++- .../codelist/i18n/ReloadableI18nCodeList.java | 33 +++ .../codelist/i18n/SimpleI18nCodeList.java | 89 ++----- .../i18n/SimpleReloadableI18nCodeList.java | 228 ++++++++++++++++ .../i18n/AbstractI18nCodeListTest.java | 28 +- .../SimpleReloadableI18nCodeListTest.java | 252 ++++++++++++++++++ 6 files changed, 680 insertions(+), 77 deletions(-) create mode 100644 terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/ReloadableI18nCodeList.java create mode 100644 terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/SimpleReloadableI18nCodeList.java create mode 100644 terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/test/java/org/terasoluna/gfw/common/codelist/i18n/SimpleReloadableI18nCodeListTest.java diff --git a/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/AbstractI18nCodeList.java b/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/AbstractI18nCodeList.java index 68e4b4b39..fa4cf8837 100644 --- a/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/AbstractI18nCodeList.java +++ b/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/AbstractI18nCodeList.java @@ -15,9 +15,16 @@ */ package org.terasoluna.gfw.common.codelist.i18n; +import java.util.Locale; import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import org.terasoluna.gfw.common.codelist.AbstractCodeList; /** @@ -25,7 +32,35 @@ * by implementing {I18nCodeList} interface. */ public abstract class AbstractI18nCodeList extends AbstractCodeList implements - I18nCodeList { + I18nCodeList, InitializingBean { + + /** + * Logger. + */ + private static final Logger logger = LoggerFactory.getLogger( + AbstractI18nCodeList.class); + + /** + * The locales codelist available. + */ + private Set codeListLocales; + + /** + * The default locale as fallback.
+ * if extend this and override default value of fallbackTo, affects {@link #afterPropertiesSet afterPropertiesSet}. + * @since 5.5.1 + */ + protected Locale fallbackTo; + + /** + * Sets the default locale as fallback. + * @param fallbackTo the default locale as fallback + * @since 5.5.1 + */ + public void setFallbackTo(Locale fallbackTo) { + Assert.notNull(fallbackTo, "fallbackTo must not be null"); + this.fallbackTo = fallbackTo; + } /** *

@@ -39,4 +74,94 @@ public Map asMap() { return asMap(LocaleContextHolder.getLocale()); } + /** + *

+ * Returns codelist for the specified locale.
+ * if there is no codelist for the specified locale, returns it by {@code fallbackTo} locale. + *

+ * @param locale locale of codelist + * @see org.terasoluna.gfw.common.codelist.i18n.I18nCodeList#asMap(java.util.Locale) + */ + @Override + public Map asMap(Locale locale) { + Assert.notNull(locale, "locale is null"); + return obtainMap(locale); + } + + /** + * This method is called after the properties of the codelist are set. + *

+ * check whether codelist of fallbackTo locale is defined.
+ * fallbackTo locale provided by {@link #fallbackTo fallbackTo} or default locale using {@link Locale#getDefault + * Locale#getDefault}.
+ * default locale is fallbackTo to it's language locale. + *

+ * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + @Override + public void afterPropertiesSet() { + codeListLocales = registerCodeListLocales(); + Assert.notNull(codeListLocales, "codeListLocales must not null."); + + if (fallbackTo == null) { + Locale defaultLocale = Locale.getDefault(); + fallbackTo = resolveLocale(defaultLocale); + Assert.notNull(fallbackTo, "No codelist for default locale ('" + + defaultLocale + "' and '" + defaultLocale.getLanguage() + + "'). Please define codelist for default locale or set locale already defined in codelist to fallbackTo."); + } else { + Assert.isTrue(codeListLocales.contains(fallbackTo), + "No codelist found for fallback locale '" + fallbackTo + + "', it must be defined."); + } + } + + /** + * Returns the locale resolved in the following order. + *
    + *
  1. Returns the specified locale if defined corresponding codelist.
  2. + *
  3. Returns the language part of the specified locale if defined corresponding codelist.
  4. + *
  5. Returns the {@code fallbackTo} locale.
  6. + *
+ * @param locale locale for codelist + * @return resolved locale + */ + protected Locale resolveLocale(Locale locale) { + if (codeListLocales.contains(locale)) { + logger.debug("Found codelist for specified locale '{}'.", locale); + return locale; + } + + String lang = locale.getLanguage(); + if (StringUtils.hasLength(lang) && !lang.equals(locale.toString())) { + Locale langOnlyLocale = new Locale(lang); + if (codeListLocales.contains(langOnlyLocale)) { + logger.debug( + "Found codelist for specified locale '{}' (language only).", + locale); + return langOnlyLocale; + } + } + + logger.debug( + "There is no codelist for specified locale '{}'. Use '{}' as fallback.", + locale, fallbackTo); + return fallbackTo; + } + + /** + * Register the locales codelist available. + *

+ * Called from {@link #afterPropertiesSet()} + *

+ * @return Set available locales + */ + abstract protected Set registerCodeListLocales(); + + /** + * Obtain the codelist of specified locale and returns it as a map. + * @param locale locale of codelist + * @return Map codelist information + */ + abstract protected Map obtainMap(Locale locale); } diff --git a/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/ReloadableI18nCodeList.java b/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/ReloadableI18nCodeList.java new file mode 100644 index 000000000..9bddbe460 --- /dev/null +++ b/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/ReloadableI18nCodeList.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2013-2017 NTT DATA Corporation + * + * Licensed 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. + */ +package org.terasoluna.gfw.common.codelist.i18n; + +import org.terasoluna.gfw.common.codelist.ReloadableCodeList; + +/** + * Adds Reloadable support to {@link I18nCodeList} + * + * @since 5.5.1 + */ +public interface ReloadableI18nCodeList extends I18nCodeList, + ReloadableCodeList { + + /** + * reloads the codelist recursively + * @param recursive whether or not reload recursively. + */ + void refresh(boolean recursive); +} diff --git a/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/SimpleI18nCodeList.java b/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/SimpleI18nCodeList.java index 5bdcf39d9..23494b0f0 100644 --- a/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/SimpleI18nCodeList.java +++ b/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/SimpleI18nCodeList.java @@ -18,12 +18,12 @@ import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import org.terasoluna.gfw.common.codelist.CodeList; import com.google.common.base.Supplier; @@ -227,13 +227,6 @@ public class SimpleI18nCodeList extends AbstractI18nCodeList implements */ Table codeListTable; - /** - * the default locale as fallback.
- * if extend this and override default value of fallbackTo, affects {@link #afterPropertiesSet afterPropertiesSet}. - * @since 5.5.1 - */ - protected Locale fallbackTo; - /** * supplier to return a {@link LinkedHashMap} object. */ @@ -244,20 +237,6 @@ public LinkedHashMap get() { } }; - /** - *

- * returns row of codelist table.
- * if there is no codelist for the specified locale, returns it by {@code fallbackTo} locale. - *

- * @see org.terasoluna.gfw.common.codelist.i18n.I18nCodeList#asMap(java.util.Locale) - */ - @Override - public Map asMap(Locale locale) { - Assert.notNull(locale, "locale is null"); - Locale resolvedLocale = resolveLocale(locale); - return codeListTable.row(resolvedLocale); - } - /** * set table by rows ({@link Map}).
*

@@ -325,72 +304,34 @@ public void setColumns(Map> cols) { } /** - * Sets the default locale as fallback.
- * @param fallbackTo the default locale as fallback - * @since 5.5.1 + * Resolve locale and obtain the codelist of specified locale. + * @see org.terasoluna.gfw.common.codelist.i18n.AbstractI18nCodeList#obtainMap(java.util.Locale) */ - public void setFallbackTo(Locale fallbackTo) { - Assert.notNull(fallbackTo, "fallbackTo must not be null"); - this.fallbackTo = fallbackTo; + @Override + protected Map obtainMap(Locale locale) { + return codeListTable.row(resolveLocale(locale)); } /** + * This method is called after the properties of the codelist are set. *

- * check whether codeListTable is initialized.
- * check whether codelist of fallbackTo locale is defined.
- * fallbackTo locale provided by {@link #fallbackTo fallbackTo} or default locale using {@link Locale#getDefault - * Locale#getDefault}.
- * default locale is fallbackTo to it's language locale. + * check whether codeListTable is initialized. *

- * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + * @see org.terasoluna.gfw.common.codelist.i18n.AbstractI18nCodeList#afterPropertiesSet() */ @Override public void afterPropertiesSet() { Assert.notNull(codeListTable, "codeListTable is not initialized!"); - if (fallbackTo == null) { - Locale defaultLocale = Locale.getDefault(); - fallbackTo = resolveLocale(defaultLocale); - Assert.notNull(fallbackTo, "No codelist for default locale ('" - + defaultLocale + "' and '" + defaultLocale.getLanguage() - + "'). Please define codelist for default locale or set locale already defined in codelist to fallbackTo."); - } else { - Assert.isTrue(codeListTable.containsRow(fallbackTo), - "No codelist found for fallback locale '" + fallbackTo - + "', it must be defined."); - } + super.afterPropertiesSet(); } /** - * Returns the locale resolved in the following order. - *
    - *
  1. Returns the specified locale if defined corresponding codelist.
  2. - *
  3. Returns the language part of the specified locale if defined corresponding codelist.
  4. - *
  5. Returns the {@code fallbackTo} locale.
  6. - *
- * @param locale locale for codelist - * @return resolved locale + * Register locales of {@link #codeListTable}. + * @see org.terasoluna.gfw.common.codelist.i18n.AbstractI18nCodeList#registerCodeListLocales() */ - protected Locale resolveLocale(Locale locale) { - if (codeListTable.containsRow(locale)) { - logger.debug("Found codelist for specified locale '{}'.", locale); - return locale; - } - - String lang = locale.getLanguage(); - if (StringUtils.hasLength(lang) && !lang.equals(locale.toString())) { - Locale langOnlyLocale = new Locale(lang); - if (codeListTable.containsRow(langOnlyLocale)) { - logger.debug( - "Found codelist for specified locale '{}' (language only).", - locale); - return langOnlyLocale; - } - } - - logger.debug( - "There is no codelist for specified locale '{}'. Use '{}' as fallback.", - locale, fallbackTo); - return fallbackTo; + @Override + protected Set registerCodeListLocales() { + return codeListTable.rowKeySet(); } /** diff --git a/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/SimpleReloadableI18nCodeList.java b/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/SimpleReloadableI18nCodeList.java new file mode 100644 index 000000000..fbd00bc21 --- /dev/null +++ b/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/main/java/org/terasoluna/gfw/common/codelist/i18n/SimpleReloadableI18nCodeList.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2013-2017 NTT DATA Corporation + * + * Licensed 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. + */ +package org.terasoluna.gfw.common.codelist.i18n; + +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.terasoluna.gfw.common.codelist.ReloadableCodeList; + +import com.google.common.base.Supplier; +import com.google.common.collect.Maps; +import com.google.common.collect.Table; +import com.google.common.collect.Tables; + +/** + * Reloadable implementation of {@link I18nCodeList}
+ *

+ * {@link I18nCodeList} has a table of codelist.
+ * Each row is a codelist for each language and represented as unmodifiable linked hash maps.
+ * The key of rows is {@link Locale}.
+ *

+ *

+ * To build a table of codelist, set a map of the {@link Locale} and the corresponding {@link ReloadableCodeList}.
+ *

+ * + * + *

set by rows with {@link ReloadableCodeList}

+ * + *
+ * <bean id="CL_I18N_WEEK"
+ *     class="org.terasoluna.gfw.common.codelist.i18n.ReloadableI18nCodeList">
+ *     <property name="rowsByCodeList">
+ *         <util:map>
+ *             <entry key="en" value-ref="CL_PRICE_EN" />
+ *             <entry key="ja" value-ref="CL_PRICE_JA" />
+ *         </util:map>
+ *     </property>
+ * </bean>
+ *
+ * <bean id="AbstractJdbcCodeList"
+ *     class="org.terasoluna.gfw.common.codelist.JdbcCodeList" abstract="true">
+ *     <property name="jdbcTemplate" ref="jdbcTemplateForCodeList" />
+ * </bean>
+ *
+ *
+ * <bean id="CL_PRICE_EN" parent="AbstractJdbcCodeList">
+ *     <property name="querySql"
+ *         value="SELECT code, label FROM price WHERE locale = 'en' ORDER BY code" />
+ *     <property name="valueColumn" value="code" />
+ *     <property name="labelColumn" value="label" />
+ * </bean>
+ *
+ * <bean id="CL_PRICE_JA" parent="AbstractJdbcCodeList">
+ *     <property name="querySql"
+ *         value="SELECT code, label FROM price WHERE locale = 'ja' ORDER BY code" />
+ *     <property name="valueColumn" value="code" />
+ *     <property name="labelColumn" value="label" />
+ * </bean>
+ *
+ * 
+ * + * @since 5.5.1 + */ +public class SimpleReloadableI18nCodeList extends AbstractI18nCodeList + implements ReloadableI18nCodeList, + InitializingBean { + /** + * Logger. + */ + private static final Logger logger = LoggerFactory.getLogger( + SimpleReloadableI18nCodeList.class); + + /** + * Codelist table. + */ + private Table codeListTable; + + /** + * Codelist for each locale. + */ + private Map codeLists; + + /** + * Lazy initialization flag. + */ + private boolean lazyInit = false; + + /** + * Supplier to return a {@link LinkedHashMap} object. + */ + private static final Supplier> LINKED_HASH_MAP_SUPPLIER = new Supplier>() { + @Override + public LinkedHashMap get() { + return Maps.newLinkedHashMap(); + } + }; + + /** + * Set ({@link ReloadableCodeList}) for each locale. + *

+ * The key is {@link Locale} and the value is {@link ReloadableCodeList}. + *

+ * @param codeLists ({@link ReloadableCodeList}) for each locale + */ + public void setRowsByCodeList(Map codeLists) { + this.codeLists = codeLists; + } + + /** + * Flag that determines whether the codelist information needs to be eager fetched. + * @param lazyInit flag + */ + public void setLazyInit(boolean lazyInit) { + this.lazyInit = lazyInit; + } + + /** + * Reloads the codelist. + * @see org.terasoluna.gfw.common.codelist.ReloadableCodeList#refresh() + * @see org.terasoluna.gfw.common.codelist.i18n.SimpleReloadableI18nCodeList#refresh(boolean) + */ + @Override + public void refresh() { + refresh(true); + } + + /** + * Reloads the codelist recursively. + * @param recursive whether or not reload recursively. + * @see org.terasoluna.gfw.common.codelist.i18n.ReloadableI18nCodeList#refresh(boolean) + */ + @Override + public void refresh(boolean recursive) { + if (recursive) { + for (ReloadableCodeList codeList : codeLists.values()) { + codeList.refresh(); + } + } + + if (logger.isDebugEnabled()) { + logger.debug("refresh codelist codeListId={}", getCodeListId()); + } + Table table = createTable(); + for (Map.Entry e : codeLists.entrySet()) { + Locale locale = e.getKey(); + Map row = e.getValue().asMap(); + for (Map.Entry re : row.entrySet()) { + String value = re.getKey(); + String label = re.getValue(); + table.put(locale, value, label); + } + } + this.codeListTable = Tables.unmodifiableTable(table); + } + + /** + * Resolve locale and obtain the codelist of specified locale. + * @see org.terasoluna.gfw.common.codelist.i18n.AbstractI18nCodeList#obtainMap(java.util.Locale) + */ + @Override + protected Map obtainMap(Locale locale) { + // If codeListTable is null, that means it is called for the first time + // and lazyInit must be set to true + if (codeListTable == null) { + refresh(true); + } + return codeListTable.row(resolveLocale(locale)); + } + + /** + * This method is called after the properties of the codelist are set. + *

+ * check whether codeLists is initialized.
+ * Checks the lazyInit flag to determine whether the codelist should be refreshed after the properties are set.
+ * If lazyInit flag is set to true, the codelist is not refreshed immediately.
+ * If it is set to false, it is refreshed (values re-loaded) immediately after the properties are loaded + *

+ * @see org.terasoluna.gfw.common.codelist.i18n.AbstractI18nCodeList#afterPropertiesSet() + */ + @Override + public void afterPropertiesSet() { + Assert.notEmpty(codeLists, "codeLists is not initialized!"); + super.afterPropertiesSet(); + + if (!lazyInit) { + refresh(); + } + } + + /** + * Register locales of {@link #codeLists}. + * @see org.terasoluna.gfw.common.codelist.i18n.AbstractI18nCodeList#registerCodeListLocales() + */ + @Override + protected Set registerCodeListLocales() { + return codeLists.keySet(); + } + + /** + * Create table which consist of {@link LinkedHashMap} factory. + * @return table + */ + private Table createTable() { + Map> backingMap = Maps.newLinkedHashMap(); + Table table = Tables.newCustomTable(backingMap, + LINKED_HASH_MAP_SUPPLIER); + return table; + } +} diff --git a/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/test/java/org/terasoluna/gfw/common/codelist/i18n/AbstractI18nCodeListTest.java b/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/test/java/org/terasoluna/gfw/common/codelist/i18n/AbstractI18nCodeListTest.java index 7feb0e384..09953b57b 100644 --- a/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/test/java/org/terasoluna/gfw/common/codelist/i18n/AbstractI18nCodeListTest.java +++ b/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/test/java/org/terasoluna/gfw/common/codelist/i18n/AbstractI18nCodeListTest.java @@ -21,10 +21,13 @@ import java.util.Collections; import java.util.Locale; import java.util.Map; +import java.util.Set; import org.junit.Test; import org.springframework.context.i18n.LocaleContextHolder; +import com.google.common.collect.Sets; + public class AbstractI18nCodeListTest { @Test @@ -32,11 +35,15 @@ public void testAsMap() { AbstractI18nCodeList impl = new AbstractI18nCodeList() { @Override - public Map asMap(Locale locale) { + protected Set registerCodeListLocales() { + return Sets.newHashSet(); + } + + @Override + protected Map obtainMap(Locale locale) { return Collections.singletonMap("language", locale .getLanguage()); } - }; Locale.setDefault(Locale.ENGLISH); @@ -56,4 +63,21 @@ public Map asMap(Locale locale) { .getLanguage())); } + @Test(expected = IllegalArgumentException.class) + public void testRegisterAvailableLocalesNull() { + AbstractI18nCodeList impl = new AbstractI18nCodeList() { + + @Override + protected Set registerCodeListLocales() { + return null; + } + + @Override + protected Map obtainMap(Locale locale) { + return null; + } + }; + + impl.afterPropertiesSet(); + } } diff --git a/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/test/java/org/terasoluna/gfw/common/codelist/i18n/SimpleReloadableI18nCodeListTest.java b/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/test/java/org/terasoluna/gfw/common/codelist/i18n/SimpleReloadableI18nCodeListTest.java new file mode 100644 index 000000000..0145fa71a --- /dev/null +++ b/terasoluna-gfw-common-libraries/terasoluna-gfw-common/src/test/java/org/terasoluna/gfw/common/codelist/i18n/SimpleReloadableI18nCodeListTest.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2013-2017 NTT DATA Corporation + * + * Licensed 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. + */ +package org.terasoluna.gfw.common.codelist.i18n; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; + +import javax.sql.DataSource; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.test.annotation.Rollback; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.transaction.annotation.Transactional; +import org.terasoluna.gfw.common.codelist.JdbcCodeList; +import org.terasoluna.gfw.common.codelist.ReloadableCodeList; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Table; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "classpath:test-context.xml" }) +@Transactional +@Rollback +// Changed by SPR-13277 +public class SimpleReloadableI18nCodeListTest { + + static final Map locales = ImmutableMap.of(Locale.ENGLISH, + "label%03d", Locale.JAPANESE, "ラベル%03d"); + + @Autowired + NamedParameterJdbcTemplate jdbcTemplate; + + @Autowired + DataSource dataSource; + + Table tableInput; + + SimpleReloadableI18nCodeList reloadableI18nCodeList; + + JdbcCodeList codeListEnglish; + + JdbcCodeList codeListJapanese; + + public SimpleReloadableI18nCodeListTest() { + Locale.setDefault(Locale.US); + } + + @Before + public void setUp() throws Exception { + + tableInput = HashBasedTable.create(); + for (Entry locale : locales.entrySet()) { + for (int i = 0; i < 10; i++) { + tableInput.put(locale.getKey(), String.format("%03d", i), String + .format(locale.getValue(), i)); + } + } + + jdbcTemplate.getJdbcOperations().execute( + "CREATE TABLE codelist_en(code_id character varying(3) PRIMARY KEY, code_name character varying(50))"); + for (Entry column : tableInput.row(Locale.ENGLISH) + .entrySet()) { + jdbcTemplate.update( + "INSERT INTO codelist_en (code_id, code_name) VALUES (:code_id, :code_name)", + ImmutableMap.of("code_id", column.getKey(), "code_name", + column.getValue())); + } + + jdbcTemplate.getJdbcOperations().execute( + "CREATE TABLE codelist_ja(code_id character varying(3) PRIMARY KEY, code_name character varying(50))"); + for (Entry column : tableInput.row(Locale.JAPANESE) + .entrySet()) { + jdbcTemplate.update( + "INSERT INTO codelist_ja (code_id, code_name) VALUES (:code_id, :code_name)", + ImmutableMap.of("code_id", column.getKey(), "code_name", + column.getValue())); + } + + codeListEnglish = new JdbcCodeList(); + codeListEnglish.setBeanName("CL_TEST_EN"); + codeListEnglish.setDataSource(dataSource); + codeListEnglish.setLabelColumn("code_name"); + codeListEnglish.setValueColumn("code_id"); + codeListEnglish.setQuerySql( + "Select code_id, code_name from codelist_en"); + + codeListJapanese = new JdbcCodeList(); + codeListJapanese.setBeanName("CL_TEST_JA"); + codeListJapanese.setDataSource(dataSource); + codeListJapanese.setLabelColumn("code_name"); + codeListJapanese.setValueColumn("code_id"); + codeListJapanese.setQuerySql( + "Select code_id, code_name from codelist_ja"); + + reloadableI18nCodeList = new SimpleReloadableI18nCodeList(); + reloadableI18nCodeList.setBeanName("CL_TEST"); + reloadableI18nCodeList.setRowsByCodeList(ImmutableMap + . of(Locale.ENGLISH, + codeListEnglish, Locale.JAPANESE, codeListJapanese)); + } + + @After + public void tearDown() throws Exception { + jdbcTemplate.getJdbcOperations().execute("DROP TABLE codelist_en"); + jdbcTemplate.getJdbcOperations().execute("DROP TABLE codelist_ja"); + } + + @Test + public void testSetRowsByCodeList() { + + afterPropertiesSet(); + assertCodeListMap(10); + } + + @Test + public void testRefresh() { + + afterPropertiesSet(); + + // before refresh. + assertCodeListMap(10); + + // update tables of database. + updateRegisteredCodeLists(11); + + // refresh codelist. + reloadableI18nCodeList.refresh(); + + // reflect changes of registered codelists. + assertCodeListMap(11); + } + + @Test + public void testRefreshRecursively() { + + afterPropertiesSet(); + + // before refresh. + assertCodeListMap(10); + + // update tables of database. + updateRegisteredCodeLists(11); + + // refresh codelist recursively. + reloadableI18nCodeList.refresh(true); + + // reflect changes of registered codelists. + assertCodeListMap(11); + } + + @Test + public void testRefreshNonRecursively() { + + afterPropertiesSet(); + + // before refresh. + assertCodeListMap(10); + + // update tables of database. + updateRegisteredCodeLists(11); + + // refresh codelist non recursively. + reloadableI18nCodeList.refresh(false); + + // no reflect changes of registered codelists. + assertCodeListMap(10); + } + + @Test + public void testLazyInit() { + + reloadableI18nCodeList.setLazyInit(true); + afterPropertiesSet(); + + assertThat(ReflectionTestUtils.getField(reloadableI18nCodeList, + "codeListTable"), nullValue()); + assertCodeListMap(10); + } + + @Test(expected = IllegalArgumentException.class) + public void testAfterPropertiesSet() { + + reloadableI18nCodeList.setRowsByCodeList(null); + afterPropertiesSet(); + } + + private void afterPropertiesSet() { + + codeListEnglish.afterPropertiesSet(); + codeListJapanese.afterPropertiesSet(); + reloadableI18nCodeList.afterPropertiesSet(); + } + + private void assertCodeListMap(int mapSize) { + + for (Locale locale : tableInput.rowKeySet()) { + + Map mapInput = tableInput.row(locale); + Map mapOutput = reloadableI18nCodeList.asMap( + locale); + assertThat(mapOutput.size(), is(mapSize)); + for (int i = 0; i < mapSize; i++) { + assertThat(mapOutput.get(String.format("%03d", i)), is(mapInput + .get(String.format("%03d", i)))); + } + } + } + + private void updateRegisteredCodeLists(int newCode) { + + Locale locale = Locale.ENGLISH; + String column = String.format("%03d", newCode); + String value = String.format(locales.get(locale), newCode); + tableInput.put(locale, column, value); + jdbcTemplate.update( + "INSERT INTO codelist_en (code_id, code_name) VALUES (:code_id, :code_name)", + ImmutableMap.of("code_id", column, "code_name", value)); + + locale = Locale.JAPANESE; + value = String.format(locales.get(locale), newCode); + tableInput.put(locale, column, value); + jdbcTemplate.update( + "INSERT INTO codelist_ja (code_id, code_name) VALUES (:code_id, :code_name)", + ImmutableMap.of("code_id", column, "code_name", value)); + } +}