VIVO-12 NIHVIVO-4011 Provide config and GUI for selecting Locale
This commit is contained in:
parent
1ba6204815
commit
bb6b2fa970
19 changed files with 1346 additions and 10 deletions
|
@ -651,10 +651,80 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
An absolute file path, pointing to the root directory of the Harvester utility.
|
Force VIVO to use a specific language or Locale instead of those
|
||||||
You must include the final slash.
|
specified by the browser.
|
||||||
|
This affects RDF data retrieved from the model, if RDFService.languageFilter is true.
|
||||||
|
This also affects the text of pages that have been modified to support multiple languages.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr class="odd_row">
|
||||||
|
<td>
|
||||||
|
languages.forceLocale
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
en_US
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
A list of supported languages or Locales that the user may choose to
|
||||||
|
use instead of the one specified by the browser. Selection images must
|
||||||
|
be available in the i18n/images directory of the theme.
|
||||||
|
This affects RDF data retrieved from the model, if RDFService.languageFilter is true.
|
||||||
|
This also affects the text of pages that have been modified to support multiple languages.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd_row">
|
||||||
|
<td>
|
||||||
|
languages.selectableLocales
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
en, es, fr_FR
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<b>For developers only.</b>
|
||||||
|
Defeat the Freemarker template cache, so each template
|
||||||
|
is read from disk on each request. This permits developers to immediately
|
||||||
|
see the effect of changes to the template. The default is <code>false</code>, which
|
||||||
|
means that a cached copy of each template will be used for 60 seconds
|
||||||
|
before the disk is checked for a new version.
|
||||||
|
<br/><b>Setting this option to "true" slows down VIVO performance.</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd_row">
|
||||||
|
<td>
|
||||||
|
developer.defeatFreemarkerCache
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
false
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<b>For developers only.</b>
|
||||||
|
Defeat the cache of language-specific text strings,
|
||||||
|
so the language file is read from disk on each request.
|
||||||
|
This permits developers to immediately
|
||||||
|
see the effect of changes to the text strings.
|
||||||
|
The default is <code>false</code>, which means that the language file is
|
||||||
|
read when VIVO starts up, or when a new theme is selected.
|
||||||
|
<br/><b>Setting this option to "true" slows down VIVO performance.</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd_row">
|
||||||
|
<td>
|
||||||
|
developer.defeatI18nCache = true
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
false
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<h3 id="deploy">VI. Compile and deploy</h3>
|
<h3 id="deploy">VI. Compile and deploy</h3>
|
||||||
|
|
|
@ -119,3 +119,46 @@ proxy.eligibleTypeList = http://www.w3.org/2002/07/owl#Thing
|
||||||
# header supplied by the browser. Default is true if not set.
|
# header supplied by the browser. Default is true if not set.
|
||||||
#
|
#
|
||||||
RDFService.languageFilter = true
|
RDFService.languageFilter = true
|
||||||
|
|
||||||
|
#
|
||||||
|
# Force VIVO to use a specific language or Locale instead of those
|
||||||
|
# specified by the browser. This affects RDF data retrieved from the model,
|
||||||
|
# if RDFService.languageFilter is true. This also affects the text of pages
|
||||||
|
# that have been modified to support multiple languages.
|
||||||
|
#
|
||||||
|
# languages.forceLocale = en_US
|
||||||
|
|
||||||
|
#
|
||||||
|
# A list of supported languages or Locales that the user may choose to
|
||||||
|
# use instead of the one specified by the browser. Selection images must
|
||||||
|
# be available in the i18n/images directory of the theme. This affects
|
||||||
|
# RDF data retrieved from the model, if RDFService.languageFilter is true.
|
||||||
|
# This also affects the text of pages that have been modified to support
|
||||||
|
# multiple languages.
|
||||||
|
#
|
||||||
|
# This should not be used with languages.forceLocale, which will override it.
|
||||||
|
#
|
||||||
|
# languages.selectableLocales = en, es, fr
|
||||||
|
|
||||||
|
#
|
||||||
|
# For developers only: Setting this option to "true" slows down VIVO performance.
|
||||||
|
#
|
||||||
|
# Defeat the Freemarker template cache, so each template is read from disk
|
||||||
|
# on each request. This permits developers to immediately see the effect of
|
||||||
|
# changes to the template. The default is <code>false</code>, which means
|
||||||
|
# that a cached copy of each template will be used for 60 seconds before
|
||||||
|
# the disk is checked for a new version.
|
||||||
|
#
|
||||||
|
# developer.defeatFreemarkerCache = true
|
||||||
|
|
||||||
|
#
|
||||||
|
# For developers only: Setting this option to "true" slows down VIVO performance.
|
||||||
|
#
|
||||||
|
# Defeat the cache of language-specific text strings, so the language file
|
||||||
|
# is read from disk on each request. This permits developers to immediately
|
||||||
|
# see the effect of changes to the text strings. The default is
|
||||||
|
# <code>false</code>, which means that the language file is read when
|
||||||
|
# VIVO starts up, or when a new theme is selected.
|
||||||
|
#
|
||||||
|
# developer.defeatI18nCache = true
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ package edu.cornell.mannlib.vitro.webapp.controller.freemarker;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
|
@ -200,7 +200,7 @@ public class I18n {
|
||||||
log.debug("Paths are '" + themeI18nPath + "' and '" + appI18nPath
|
log.debug("Paths are '" + themeI18nPath + "' and '" + appI18nPath
|
||||||
+ "'");
|
+ "'");
|
||||||
|
|
||||||
return VivoResourceBundle.getBundle(bundleName, ctx, appI18nPath,
|
return VitroResourceBundle.getBundle(bundleName, ctx, appI18nPath,
|
||||||
themeI18nPath, this);
|
themeI18nPath, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,8 @@ import org.apache.commons.logging.LogFactory;
|
||||||
*
|
*
|
||||||
* In all_es.properties: account_email_html = @@file accountEmail_es.html
|
* In all_es.properties: account_email_html = @@file accountEmail_es.html
|
||||||
*/
|
*/
|
||||||
public class VivoResourceBundle extends ResourceBundle {
|
public class VitroResourceBundle extends ResourceBundle {
|
||||||
private static final Log log = LogFactory.getLog(VivoResourceBundle.class);
|
private static final Log log = LogFactory.getLog(VitroResourceBundle.class);
|
||||||
|
|
||||||
private static final String FILE_FLAG = "@@file ";
|
private static final String FILE_FLAG = "@@file ";
|
||||||
private static final String MESSAGE_FILE_NOT_FOUND = "File {1} not found for property {0}.";
|
private static final String MESSAGE_FILE_NOT_FOUND = "File {1} not found for property {0}.";
|
||||||
|
@ -51,11 +51,11 @@ public class VivoResourceBundle extends ResourceBundle {
|
||||||
// Factory method
|
// Factory method
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
public static VivoResourceBundle getBundle(String bundleName,
|
public static VitroResourceBundle getBundle(String bundleName,
|
||||||
ServletContext ctx, String appI18nPath, String themeI18nPath,
|
ServletContext ctx, String appI18nPath, String themeI18nPath,
|
||||||
Control control) {
|
Control control) {
|
||||||
try {
|
try {
|
||||||
return new VivoResourceBundle(bundleName, ctx, appI18nPath,
|
return new VitroResourceBundle(bundleName, ctx, appI18nPath,
|
||||||
themeI18nPath, control);
|
themeI18nPath, control);
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
log.debug(e);
|
log.debug(e);
|
||||||
|
@ -78,7 +78,7 @@ public class VivoResourceBundle extends ResourceBundle {
|
||||||
private final Properties defaults;
|
private final Properties defaults;
|
||||||
private final Properties properties;
|
private final Properties properties;
|
||||||
|
|
||||||
private VivoResourceBundle(String bundleName, ServletContext ctx,
|
private VitroResourceBundle(String bundleName, ServletContext ctx,
|
||||||
String appI18nPath, String themeI18nPath, Control control)
|
String appI18nPath, String themeI18nPath, Control control)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this.bundleName = bundleName;
|
this.bundleName = bundleName;
|
|
@ -0,0 +1,100 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.i18n.selection;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServlet;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.LocaleUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.beans.DisplayMessage;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this at /selectLocale&selection=[locale_string]
|
||||||
|
*
|
||||||
|
* For example: /selectLocale&selection=en_US or /selectLocale&selection=es
|
||||||
|
*
|
||||||
|
* Write an error to the log (and to DisplayMessage) if the selection is not
|
||||||
|
* syntactically valid.
|
||||||
|
*
|
||||||
|
* Write a warning to the log if the selection code is not one of the selectable
|
||||||
|
* Locales from runtime.properties, or if the selection code is not recognized
|
||||||
|
* by the system.
|
||||||
|
*
|
||||||
|
* Set the new Locale in the Session using SelectedLocale and return to the
|
||||||
|
* referrer.
|
||||||
|
*/
|
||||||
|
public class LocaleSelectionController extends HttpServlet {
|
||||||
|
private static final Log log = LogFactory
|
||||||
|
.getLog(LocaleSelectionController.class);
|
||||||
|
|
||||||
|
public static final String PARAMETER_SELECTION = "selection";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
String referrer = req.getHeader("referer");
|
||||||
|
|
||||||
|
String selectedLocale = req.getParameter(PARAMETER_SELECTION);
|
||||||
|
|
||||||
|
try {
|
||||||
|
processSelectedLocale(req, selectedLocale);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to process the user's Locale selection", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(referrer)) {
|
||||||
|
resp.sendRedirect(UrlBuilder.getHomeUrl());
|
||||||
|
} else {
|
||||||
|
resp.sendRedirect(referrer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processSelectedLocale(HttpServletRequest req,
|
||||||
|
String selectedLocale) {
|
||||||
|
if (StringUtils.isBlank(selectedLocale)) {
|
||||||
|
log.debug("No '" + PARAMETER_SELECTION + "' parameter");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Locale locale = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
locale = LocaleUtils.toLocale(selectedLocale.trim());
|
||||||
|
log.debug("Locale selection is " + locale);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.error("Failed to convert the selection to a Locale", e);
|
||||||
|
DisplayMessage.setMessage(req,
|
||||||
|
"There was a problem in the system. "
|
||||||
|
+ "Your language choice was rejected.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Locale> selectables = SelectedLocale.getSelectableLocales(req);
|
||||||
|
if (!selectables.contains(locale)) {
|
||||||
|
log.warn("User selected a locale '" + locale
|
||||||
|
+ "' that was not in the list: " + selectables);
|
||||||
|
} else if (!LocaleUtils.isAvailableLocale(locale)) {
|
||||||
|
log.warn("User selected an unrecognized locale: '" + locale + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedLocale.setSelectedLocale(req, locale);
|
||||||
|
log.debug("Setting selected locale to " + locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
doGet(req, resp);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.i18n.selection;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.DataGetter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the data for the selectable Locales, so the Freemarker template can
|
||||||
|
* create a row of flag images that will select the desired locale.
|
||||||
|
*
|
||||||
|
* If there are no selectable Locales in runtime.properties, we return an empty
|
||||||
|
* map. (selectLocale?? will return false)
|
||||||
|
*
|
||||||
|
* If the Locale has been forced by runtime.properties, we do the same.
|
||||||
|
*
|
||||||
|
* If there are selectable Locales, the returned map will contain a structure
|
||||||
|
* like this:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {selectLocale={
|
||||||
|
* selectLocaleUrl = [the URL for the form action to select a Locale]
|
||||||
|
* locales={ [a list of maps]
|
||||||
|
* { [a map for each Locale]
|
||||||
|
* code = [the code for the Locale, e.g. "en_US"]
|
||||||
|
* label = [the alt text for the Locale, e.g. "Spanish (Spain)"]
|
||||||
|
* imageUrl = [the URL of the image that represents the Locale]
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class LocaleSelectionDataGetter implements DataGetter {
|
||||||
|
private static final Log log = LogFactory
|
||||||
|
.getLog(LocaleSelectionDataGetter.class);
|
||||||
|
|
||||||
|
private final VitroRequest vreq;
|
||||||
|
|
||||||
|
public LocaleSelectionDataGetter(VitroRequest vreq) {
|
||||||
|
this.vreq = vreq;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getData(Map<String, Object> valueMap) {
|
||||||
|
List<Locale> selectables = SelectedLocale.getSelectableLocales(vreq);
|
||||||
|
if (selectables.isEmpty()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<String, Object>();
|
||||||
|
result.put("selectLocaleUrl", UrlBuilder.getUrl("/selectLocale"));
|
||||||
|
result.put("locales", buildLocalesList(selectables));
|
||||||
|
|
||||||
|
Map<String, Object> bodyMap = new HashMap<String, Object>();
|
||||||
|
bodyMap.put("selectLocale", result);
|
||||||
|
log.debug("Sending these values: " + bodyMap);
|
||||||
|
return bodyMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Map<String, Object>> buildLocalesList(List<Locale> selectables) {
|
||||||
|
Locale currentLocale = SelectedLocale.getCurrentLocale(vreq);
|
||||||
|
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
|
||||||
|
for (Locale locale : selectables) {
|
||||||
|
try {
|
||||||
|
list.add(buildLocaleMap(locale, currentLocale));
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
log.warn("Can't show the Locale selector for '" + locale
|
||||||
|
+ "': " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> buildLocaleMap(Locale locale,
|
||||||
|
Locale currentLocale) throws FileNotFoundException {
|
||||||
|
Map<String, Object> map = new HashMap<String, Object>();
|
||||||
|
map.put("code", locale.toString());
|
||||||
|
map.put("label", locale.getDisplayName(currentLocale));
|
||||||
|
map.put("imageUrl", LocaleSelectorUtilities.getImageUrl(vreq, locale));
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.i18n.selection;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletRequestWrapper;
|
||||||
|
|
||||||
|
import org.apache.commons.collections.EnumerationUtils;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for a Locale in the ServletContext or the Session that should override
|
||||||
|
* the Locale in the ServletRequest.
|
||||||
|
*
|
||||||
|
* If there is such a Locale, wrap the ServletRequest so it behaves as if that
|
||||||
|
* is the preferred Locale.
|
||||||
|
*
|
||||||
|
* Otherwise, just process the request as usual.
|
||||||
|
*/
|
||||||
|
public class LocaleSelectionFilter implements Filter {
|
||||||
|
private static final Log log = LogFactory
|
||||||
|
.getLog(LocaleSelectionFilter.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig filterConfig) throws ServletException {
|
||||||
|
// Nothing to do at startup.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
// Nothing to do at shutdown.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response,
|
||||||
|
FilterChain chain) throws IOException, ServletException {
|
||||||
|
if (request instanceof HttpServletRequest) {
|
||||||
|
HttpServletRequest hreq = (HttpServletRequest) request;
|
||||||
|
|
||||||
|
Locale overridingLocale = SelectedLocale.getOverridingLocale(hreq);
|
||||||
|
log.debug("overriding Locale is " + overridingLocale);
|
||||||
|
|
||||||
|
if (overridingLocale != null) {
|
||||||
|
request = new LocaleSelectionRequestWrapper(hreq,
|
||||||
|
overridingLocale);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.debug("Not an HttpServletRequest.");
|
||||||
|
}
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// Helper classes
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the selected Locale as the preferred Locale of the request.
|
||||||
|
*/
|
||||||
|
private static class LocaleSelectionRequestWrapper extends
|
||||||
|
HttpServletRequestWrapper {
|
||||||
|
private final HttpServletRequest request;
|
||||||
|
private final Locale selectedLocale;
|
||||||
|
|
||||||
|
public LocaleSelectionRequestWrapper(HttpServletRequest request,
|
||||||
|
Locale selectedLocale) {
|
||||||
|
super(request);
|
||||||
|
|
||||||
|
if (request == null) {
|
||||||
|
throw new NullPointerException("request may not be null.");
|
||||||
|
}
|
||||||
|
this.request = request;
|
||||||
|
|
||||||
|
if (selectedLocale == null) {
|
||||||
|
throw new NullPointerException(
|
||||||
|
"selectedLocale may not be null.");
|
||||||
|
}
|
||||||
|
this.selectedLocale = selectedLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Locale getLocale() {
|
||||||
|
return selectedLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put the selected Locale on the front of the list of acceptable
|
||||||
|
* Locales.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
@Override
|
||||||
|
public Enumeration getLocales() {
|
||||||
|
List list = EnumerationUtils.toList(request.getLocales());
|
||||||
|
list.remove(selectedLocale);
|
||||||
|
list.add(0, selectedLocale);
|
||||||
|
return Collections.enumeration(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.i18n.selection;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletContextEvent;
|
||||||
|
import javax.servlet.ServletContextListener;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.LocaleUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the ConfigurationProperties for a forced locale, or for a
|
||||||
|
* comma-separate list of selectable locales.
|
||||||
|
*
|
||||||
|
* Create the appropriate Locale objects and store them in the ServletContext.
|
||||||
|
*/
|
||||||
|
public class LocaleSelectionSetup implements ServletContextListener {
|
||||||
|
/**
|
||||||
|
* If this is set, the locale is forced. No selection will be offered to the
|
||||||
|
* user, and browser locales will be ignored.
|
||||||
|
*/
|
||||||
|
public static final String PROPERTY_FORCE_LOCALE = "languages.forceLocale";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the list of locales that the user may select. There should be a
|
||||||
|
* national flag or symbol available for each supported locale.
|
||||||
|
*/
|
||||||
|
public static final String PROPERTY_SELECTABLE_LOCALES = "languages.selectableLocales";
|
||||||
|
|
||||||
|
private ServletContext ctx;
|
||||||
|
private StartupStatus ss;
|
||||||
|
private ConfigurationProperties props;
|
||||||
|
|
||||||
|
private String forceString;
|
||||||
|
private String selectableString;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void contextInitialized(ServletContextEvent sce) {
|
||||||
|
ctx = sce.getServletContext();
|
||||||
|
ss = StartupStatus.getBean(ctx);
|
||||||
|
props = ConfigurationProperties.getBean(sce);
|
||||||
|
|
||||||
|
readProperties();
|
||||||
|
|
||||||
|
if (isForcing() && hasSelectables()) {
|
||||||
|
warnAboutOverride();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isForcing()) {
|
||||||
|
forceLocale();
|
||||||
|
} else if (hasSelectables()) {
|
||||||
|
setUpSelections();
|
||||||
|
} else {
|
||||||
|
reportNoLocales();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readProperties() {
|
||||||
|
forceString = props.getProperty(PROPERTY_FORCE_LOCALE, "");
|
||||||
|
selectableString = props.getProperty(PROPERTY_SELECTABLE_LOCALES, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isForcing() {
|
||||||
|
return StringUtils.isNotBlank(forceString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasSelectables() {
|
||||||
|
return StringUtils.isNotBlank(selectableString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void warnAboutOverride() {
|
||||||
|
ss.warning(this, "'" + PROPERTY_FORCE_LOCALE + "' will override '"
|
||||||
|
+ PROPERTY_SELECTABLE_LOCALES + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void forceLocale() {
|
||||||
|
try {
|
||||||
|
Locale forceLocale = buildLocale(forceString);
|
||||||
|
SelectedLocale.setForcedLocale(ctx, forceLocale);
|
||||||
|
ssInfo("Setting forced locale to '" + forceLocale + "'.");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
ssWarning("Problem in '" + PROPERTY_FORCE_LOCALE + "': "
|
||||||
|
+ e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUpSelections() {
|
||||||
|
List<Locale> locales = new ArrayList<Locale>();
|
||||||
|
for (String string : splitSelectables()) {
|
||||||
|
try {
|
||||||
|
locales.add(buildLocale(string));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
ssWarning("Problem in '" + PROPERTY_SELECTABLE_LOCALES + "': "
|
||||||
|
+ e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedLocale.setSelectableLocales(ctx, locales);
|
||||||
|
ssInfo("Setting selectable locales to '" + locales + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] splitSelectables() {
|
||||||
|
return selectableString.split("\\s*,\\s*");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reportNoLocales() {
|
||||||
|
ssInfo("There is no Locale information.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ssInfo(String message) {
|
||||||
|
ss.info(this, message + showPropertyValues());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ssWarning(String message) {
|
||||||
|
ss.warning(this, message + showPropertyValues());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String showPropertyValues() {
|
||||||
|
return " In runtime.properties, '" + PROPERTY_FORCE_LOCALE
|
||||||
|
+ "' is set to '" + forceString + "', '"
|
||||||
|
+ PROPERTY_SELECTABLE_LOCALES + "' is set to '"
|
||||||
|
+ selectableString + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
private Locale buildLocale(String localeString)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
Locale locale = LocaleUtils.toLocale(localeString);
|
||||||
|
|
||||||
|
if (!LocaleUtils.isAvailableLocale(locale)) {
|
||||||
|
ssWarning("'" + locale + "' is not a recognized locale.");
|
||||||
|
}
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void contextDestroyed(ServletContextEvent arg0) {
|
||||||
|
// Nothing to do at shutdown.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.i18n.selection;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some static methods for the GUI aspects of selecting a Locale.
|
||||||
|
*/
|
||||||
|
public class LocaleSelectorUtilities {
|
||||||
|
private static final Log log = LogFactory
|
||||||
|
.getLog(LocaleSelectorUtilities.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look in the current theme directory to find a selection image for this
|
||||||
|
* Locale.
|
||||||
|
*
|
||||||
|
* Images are expected at a resource path like
|
||||||
|
* /[themeDir]/i18n/images/select_locale_[locale_code].*
|
||||||
|
*
|
||||||
|
* For example, /themes/wilma/i18n/images/select_locale_en.png
|
||||||
|
* /themes/wilma/i18n/images/select_locale_en.JPEG
|
||||||
|
* /themes/wilma/i18n/images/select_locale_en.gif
|
||||||
|
*
|
||||||
|
* To create a proper URL, prepend the context path.
|
||||||
|
*/
|
||||||
|
public static String getImageUrl(VitroRequest vreq, Locale locale)
|
||||||
|
throws FileNotFoundException {
|
||||||
|
String filename = "select_locale_" + locale + ".";
|
||||||
|
|
||||||
|
String themeDir = vreq.getAppBean().getThemeDir();
|
||||||
|
String imageDirPath = "/" + themeDir + "i18n/images/";
|
||||||
|
|
||||||
|
ServletContext ctx = vreq.getSession().getServletContext();
|
||||||
|
for (Object o : ctx.getResourcePaths(imageDirPath)) {
|
||||||
|
String resourcePath = (String) o;
|
||||||
|
if (resourcePath.contains(filename)) {
|
||||||
|
String fullPath = vreq.getContextPath() + resourcePath;
|
||||||
|
log.debug("Found image for " + locale + " at '" + fullPath
|
||||||
|
+ "'");
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new FileNotFoundException("Can't find an image for " + locale);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,204 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.i18n.selection;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility class for storing and retrieving Locale information.
|
||||||
|
*
|
||||||
|
* The static methods create beans and store them in the ServletContext or the
|
||||||
|
* session, where the information can be found later.
|
||||||
|
*/
|
||||||
|
public abstract class SelectedLocale {
|
||||||
|
private static final Log log = LogFactory.getLog(SelectedLocale.class);
|
||||||
|
|
||||||
|
/** Use this attribute on both the ServletContext and the Session. */
|
||||||
|
protected static final String ATTRIBUTE_NAME = "SELECTED_LOCALE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the forced locale in the servlet context. Clear any selectable
|
||||||
|
* Locales.
|
||||||
|
*/
|
||||||
|
public static void setForcedLocale(ServletContext ctx, Locale forcedLocale) {
|
||||||
|
log.debug("Set forced locale: " + forcedLocale);
|
||||||
|
ctx.setAttribute(ATTRIBUTE_NAME,
|
||||||
|
new ContextSelectedLocale(forcedLocale));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the selected locale in the current session.
|
||||||
|
*/
|
||||||
|
public static void setSelectedLocale(HttpServletRequest req,
|
||||||
|
Locale selectedLocale) {
|
||||||
|
log.debug("Set selected locale: " + selectedLocale);
|
||||||
|
req.getSession().setAttribute(ATTRIBUTE_NAME,
|
||||||
|
new SessionSelectedLocale(selectedLocale));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do we need to override the Locale in the current request? return the
|
||||||
|
* first of these to be found:
|
||||||
|
* <ul>
|
||||||
|
* <li>The forced Locale in the servlet context</li>
|
||||||
|
* <li>The selected Locale in the session</li>
|
||||||
|
* <li>null</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public static Locale getOverridingLocale(HttpServletRequest req) {
|
||||||
|
HttpSession session = req.getSession();
|
||||||
|
ServletContext ctx = session.getServletContext();
|
||||||
|
|
||||||
|
Object ctxInfo = ctx.getAttribute(ATTRIBUTE_NAME);
|
||||||
|
if (ctxInfo instanceof ContextSelectedLocale) {
|
||||||
|
Locale forcedLocale = ((ContextSelectedLocale) ctxInfo)
|
||||||
|
.getForcedLocale();
|
||||||
|
if (forcedLocale != null) {
|
||||||
|
log.debug("Found forced locale in the context: " + forcedLocale);
|
||||||
|
return forcedLocale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object sessionInfo = session.getAttribute(ATTRIBUTE_NAME);
|
||||||
|
if (sessionInfo instanceof SessionSelectedLocale) {
|
||||||
|
Locale selectedLocale = ((SessionSelectedLocale) sessionInfo)
|
||||||
|
.getSelectedLocale();
|
||||||
|
if (selectedLocale != null) {
|
||||||
|
log.debug("Found selected locale in the session: "
|
||||||
|
+ selectedLocale);
|
||||||
|
return selectedLocale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current Locale to use, which is the first of these to be found:
|
||||||
|
* <ul>
|
||||||
|
* <li>The forced Locale in the servlet context</li>
|
||||||
|
* <li>The selected Locale in the session</li>
|
||||||
|
* <li>The Locale from the request</li>
|
||||||
|
* <li>The default Locale for the JVM</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public static Locale getCurrentLocale(HttpServletRequest req) {
|
||||||
|
Locale overridingLocale = getOverridingLocale(req);
|
||||||
|
|
||||||
|
if (overridingLocale != null) {
|
||||||
|
return overridingLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
Locale requestLocale = req.getLocale();
|
||||||
|
if (requestLocale != null) {
|
||||||
|
log.debug("Found locale in the request: " + requestLocale);
|
||||||
|
return requestLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Using default locale: " + Locale.getDefault());
|
||||||
|
return Locale.getDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a list of selectable Locales in the servlet context, so we can
|
||||||
|
* easily build the selection panel in the GUI. Clears any forced locale.
|
||||||
|
*/
|
||||||
|
public static void setSelectableLocales(ServletContext ctx,
|
||||||
|
List<Locale> selectableLocales) {
|
||||||
|
log.debug("Setting selectable locales: " + selectableLocales);
|
||||||
|
ctx.setAttribute(ATTRIBUTE_NAME, new ContextSelectedLocale(
|
||||||
|
selectableLocales));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of selectable Locales from the servlet context. May return
|
||||||
|
* an empty list, but never returns null.
|
||||||
|
*/
|
||||||
|
public static List<Locale> getSelectableLocales(HttpServletRequest req) {
|
||||||
|
ServletContext ctx = req.getSession().getServletContext();
|
||||||
|
Object ctxInfo = ctx.getAttribute(ATTRIBUTE_NAME);
|
||||||
|
if (ctxInfo instanceof ContextSelectedLocale) {
|
||||||
|
List<Locale> selectableLocales = ((ContextSelectedLocale) ctxInfo)
|
||||||
|
.getSelectableLocales();
|
||||||
|
if (selectableLocales != null) {
|
||||||
|
log.debug("Returning selectable locales: " + selectableLocales);
|
||||||
|
return selectableLocales;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("No selectable locales were found. Returning an empty list.");
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// Bean classes
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Holds Locale information in the ServletContext. */
|
||||||
|
protected static class ContextSelectedLocale {
|
||||||
|
// Only one of these is populated.
|
||||||
|
private final Locale forcedLocale;
|
||||||
|
private final List<Locale> selectableLocales;
|
||||||
|
|
||||||
|
public ContextSelectedLocale(Locale forcedLocale) {
|
||||||
|
if (forcedLocale == null) {
|
||||||
|
throw new NullPointerException("forcedLocale may not be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.forcedLocale = forcedLocale;
|
||||||
|
this.selectableLocales = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContextSelectedLocale(List<Locale> selectableLocales) {
|
||||||
|
if (selectableLocales == null) {
|
||||||
|
selectableLocales = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.forcedLocale = null;
|
||||||
|
this.selectableLocales = selectableLocales;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Locale getForcedLocale() {
|
||||||
|
return forcedLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Locale> getSelectableLocales() {
|
||||||
|
return selectableLocales;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ContextSelectedLocale[forced=" + forcedLocale
|
||||||
|
+ ", selectable=" + selectableLocales + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds Locale information in the Session. */
|
||||||
|
protected static class SessionSelectedLocale {
|
||||||
|
private final Locale selectedLocale;
|
||||||
|
|
||||||
|
public SessionSelectedLocale(Locale selectedLocale) {
|
||||||
|
this.selectedLocale = selectedLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Locale getSelectedLocale() {
|
||||||
|
return selectedLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SessionSelectedLocale[" + selectedLocale + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ import org.apache.commons.logging.LogFactory;
|
||||||
public class StartupStatus {
|
public class StartupStatus {
|
||||||
private static final Log log = LogFactory.getLog(StartupStatus.class);
|
private static final Log log = LogFactory.getLog(StartupStatus.class);
|
||||||
|
|
||||||
private static final String ATTRIBUTE_NAME = "STARTUP_STATUS";
|
protected static final String ATTRIBUTE_NAME = "STARTUP_STATUS";
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// static methods
|
// static methods
|
||||||
|
|
|
@ -0,0 +1,314 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.i18n.selection;
|
||||||
|
|
||||||
|
import static edu.cornell.mannlib.vitro.webapp.i18n.selection.LocaleSelectionSetup.PROPERTY_FORCE_LOCALE;
|
||||||
|
import static edu.cornell.mannlib.vitro.webapp.i18n.selection.LocaleSelectionSetup.PROPERTY_SELECTABLE_LOCALES;
|
||||||
|
import static edu.cornell.mannlib.vitro.webapp.i18n.selection.SelectedLocale.ATTRIBUTE_NAME;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContextEvent;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.LocaleUtils;
|
||||||
|
import org.apache.commons.lang.ObjectUtils;
|
||||||
|
import org.apache.log4j.Level;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import stubs.edu.cornell.mannlib.vitro.webapp.config.ConfigurationPropertiesStub;
|
||||||
|
import stubs.edu.cornell.mannlib.vitro.webapp.startup.StartupStatusStub;
|
||||||
|
import stubs.javax.servlet.ServletContextStub;
|
||||||
|
import edu.cornell.mannlib.vitro.testing.AbstractTestClass;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.i18n.selection.SelectedLocale.ContextSelectedLocale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
public class LocaleSelectionSetupTest extends AbstractTestClass {
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// Infrastructure
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
private LocaleSelectionSetup lss;
|
||||||
|
private ServletContextStub ctx;
|
||||||
|
private ServletContextEvent sce;
|
||||||
|
private ConfigurationPropertiesStub props;
|
||||||
|
private StartupStatusStub ss;
|
||||||
|
|
||||||
|
private int[] expectedMessageCounts;
|
||||||
|
private Locale expectedForcedLocale;
|
||||||
|
private List<Locale> expectedSelectableLocales;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
// setLoggerLevel(LocaleSelectionSetup.class, Level.DEBUG);
|
||||||
|
// setLoggerLevel(StartupStatusStub.class, Level.DEBUG);
|
||||||
|
setLoggerLevel(ConfigurationProperties.class, Level.WARN);
|
||||||
|
|
||||||
|
ctx = new ServletContextStub();
|
||||||
|
sce = new ServletContextEvent(ctx);
|
||||||
|
|
||||||
|
props = new ConfigurationPropertiesStub();
|
||||||
|
props.setBean(ctx);
|
||||||
|
|
||||||
|
ss = new StartupStatusStub(ctx);
|
||||||
|
|
||||||
|
lss = new LocaleSelectionSetup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void checkExpectations() {
|
||||||
|
if (expectedMessageCounts == null) {
|
||||||
|
fail("expecteMessages() was not called");
|
||||||
|
}
|
||||||
|
|
||||||
|
String message = compareMessageCount("info", ss.getInfoCount(),
|
||||||
|
expectedMessageCounts[0])
|
||||||
|
+ compareMessageCount("warning", ss.getWarningCount(),
|
||||||
|
expectedMessageCounts[1])
|
||||||
|
+ compareMessageCount("fatal", ss.getFatalCount(),
|
||||||
|
expectedMessageCounts[2])
|
||||||
|
+ checkForced()
|
||||||
|
+ checkSelectable();
|
||||||
|
if (!message.isEmpty()) {
|
||||||
|
fail(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String compareMessageCount(String label, int actual, int expected) {
|
||||||
|
if (expected == actual) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return "expecting " + expected + " " + label
|
||||||
|
+ " messages, but received " + actual + "; ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String checkForced() {
|
||||||
|
Locale actual = null;
|
||||||
|
Object o = ctx.getAttribute(ATTRIBUTE_NAME);
|
||||||
|
if (o instanceof ContextSelectedLocale) {
|
||||||
|
actual = ((ContextSelectedLocale) o).getForcedLocale();
|
||||||
|
}
|
||||||
|
|
||||||
|
Locale expected = expectedForcedLocale;
|
||||||
|
if (ObjectUtils.equals(expected, actual)) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return "expected forced locale of " + expectedForcedLocale
|
||||||
|
+ ", but was " + actual + "; ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String checkSelectable() {
|
||||||
|
List<Locale> actual = Collections.emptyList();
|
||||||
|
Object o = ctx.getAttribute(ATTRIBUTE_NAME);
|
||||||
|
if (o instanceof ContextSelectedLocale) {
|
||||||
|
actual = ((ContextSelectedLocale) o).getSelectableLocales();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Locale> expected = expectedSelectableLocales;
|
||||||
|
if (expected == null) {
|
||||||
|
expected = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ObjectUtils.equals(expected, actual)) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return "expected selectable locales of " + expected + ", but was "
|
||||||
|
+ actual + "; ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// The tests
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// General functionality
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void neitherPropertyIsSpecified() {
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectMessages(1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void forceSuccessL() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "es");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectForced("es");
|
||||||
|
expectMessages(1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void forceSuccessL_C() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "es_ES");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectForced("es_ES");
|
||||||
|
expectMessages(1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void forceSuccessL_C_V() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "no_NO_NY");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectForced("no_NO_NY");
|
||||||
|
expectMessages(1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void oneSelectable() {
|
||||||
|
props.setProperty(PROPERTY_SELECTABLE_LOCALES, "fr_FR");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectSelectable("fr_FR");
|
||||||
|
expectMessages(1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void twoSelectables() {
|
||||||
|
props.setProperty(PROPERTY_SELECTABLE_LOCALES, "fr_FR, es_PE");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectSelectable("fr_FR", "es_PE");
|
||||||
|
expectMessages(1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void bothPropertiesAreSpecified() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "es_ES");
|
||||||
|
props.setProperty(PROPERTY_SELECTABLE_LOCALES, "fr_FR");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectForced("es_ES");
|
||||||
|
expectMessages(1, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locale string syntax (common to both force and selectable)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void langaugeIsEmpty() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "_ES");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectMessages(0, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void languageWrongLength() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "e_ES");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectMessages(0, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void languageNotAlphabetic() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "e4_ES");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectMessages(0, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void languageNotLowerCase() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "eS_ES");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectMessages(0, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void countryIsEmpty() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "es_ _13");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectMessages(0, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void countryWrongLength() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "es_ESS");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectMessages(0, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void countryNotAlphabetic() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "es_E@");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectMessages(0, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void countryNotUpperCase() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "es_es");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectMessages(0, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void variantIsEmpty() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "es_ES_");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectMessages(0, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void funkyVariantIsAcceptable() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "es_ES_123_aa");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectForced("es_ES_123_aa");
|
||||||
|
expectMessages(1, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void localeNotRecognizedProducesWarning() {
|
||||||
|
props.setProperty(PROPERTY_FORCE_LOCALE, "es_FR");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectForced("es_FR");
|
||||||
|
expectMessages(1, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syntax of selectable property
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void emptySelectableLocaleProducesWarning() {
|
||||||
|
props.setProperty(PROPERTY_SELECTABLE_LOCALES, "es_ES, , fr_FR");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectSelectable("es_ES", "fr_FR");
|
||||||
|
expectMessages(1, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void blanksAroundCommasAreIgnored() {
|
||||||
|
props.setProperty(PROPERTY_SELECTABLE_LOCALES, "es_ES,en_US \t , fr_FR");
|
||||||
|
lss.contextInitialized(sce);
|
||||||
|
expectSelectable("es_ES", "en_US", "fr_FR");
|
||||||
|
expectMessages(1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// helper methods
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
private void expectMessages(int infoCount, int warningCount, int fatalCount) {
|
||||||
|
this.expectedMessageCounts = new int[] { infoCount, warningCount,
|
||||||
|
fatalCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectForced(String localeString) {
|
||||||
|
this.expectedForcedLocale = stringToLocale(localeString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectSelectable(String... strings) {
|
||||||
|
List<Locale> list = new ArrayList<Locale>();
|
||||||
|
for (String string : strings) {
|
||||||
|
list.add(stringToLocale(string));
|
||||||
|
}
|
||||||
|
this.expectedSelectableLocales = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Locale stringToLocale(String string) {
|
||||||
|
return LocaleUtils.toLocale(string);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package stubs.edu.cornell.mannlib.vitro.webapp.startup;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletContextListener;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep track of how many messages come in.
|
||||||
|
*/
|
||||||
|
public class StartupStatusStub extends StartupStatus {
|
||||||
|
private static final Log log = LogFactory.getLog(StartupStatusStub.class);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// Stub infrastructure
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
private int infoCount = 0;
|
||||||
|
private int warningCount = 0;
|
||||||
|
private int fatalCount = 0;
|
||||||
|
|
||||||
|
public StartupStatusStub(ServletContext ctx) {
|
||||||
|
ctx.setAttribute(ATTRIBUTE_NAME, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInfoCount() {
|
||||||
|
return infoCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWarningCount() {
|
||||||
|
return warningCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFatalCount() {
|
||||||
|
return fatalCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// Stub methods
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(ServletContextListener listener, String message) {
|
||||||
|
log.debug("INFO: " + message);
|
||||||
|
infoCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(ServletContextListener listener, String message,
|
||||||
|
Throwable cause) {
|
||||||
|
log.debug("INFO: " + message + " " + cause);
|
||||||
|
infoCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warning(ServletContextListener listener, String message) {
|
||||||
|
log.debug("WARNING: " + message);
|
||||||
|
warningCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warning(ServletContextListener listener, String message,
|
||||||
|
Throwable cause) {
|
||||||
|
log.debug("WARNING: " + message + " " + cause);
|
||||||
|
warningCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fatal(ServletContextListener listener, String message) {
|
||||||
|
log.debug("FATAL: " + message);
|
||||||
|
fatalCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fatal(ServletContextListener listener, String message,
|
||||||
|
Throwable cause) {
|
||||||
|
log.debug("FATAL: " + message + " " + cause);
|
||||||
|
fatalCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// Un-implemented methods
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void listenerNotExecuted(ServletContextListener listener) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"StartupStatusStub.listenerNotExecuted() not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void listenerExecuted(ServletContextListener listener) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"StartupStatusStub.listenerExecuted() not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allClear() {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"StartupStatusStub.allClear() not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStartupAborted() {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"StartupStatusStub.isStartupAborted() not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<StatusItem> getStatusItems() {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"StartupStatusStub.getStatusItems() not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<StatusItem> getErrorItems() {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"StartupStatusStub.getErrorItems() not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<StatusItem> getWarningItems() {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"StartupStatusStub.getWarningItems() not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<StatusItem> getItemsForListener(ServletContextListener listener) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"StartupStatusStub.getItemsForListener() not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
<nav role="navigation">
|
<nav role="navigation">
|
||||||
<ul id="header-nav" role="list">
|
<ul id="header-nav" role="list">
|
||||||
|
<#include "languageSelector.ftl">
|
||||||
<li role="listitem"><a href="${urls.index}" title="index">Index</a></li>
|
<li role="listitem"><a href="${urls.index}" title="index">Index</a></li>
|
||||||
<#if user.loggedIn>
|
<#if user.loggedIn>
|
||||||
<#if user.hasSiteAdminAccess>
|
<#if user.hasSiteAdminAccess>
|
||||||
|
|
|
@ -55,6 +55,9 @@ edu.cornell.mannlib.vitro.webapp.services.shortview.ShortViewServiceSetup
|
||||||
|
|
||||||
edu.ucsf.vitro.opensocial.OpenSocialSmokeTests
|
edu.ucsf.vitro.opensocial.OpenSocialSmokeTests
|
||||||
|
|
||||||
|
# For multiple language support
|
||||||
|
edu.cornell.mannlib.vitro.webapp.i18n.selection.LocaleSelectionSetup
|
||||||
|
|
||||||
# The Solr index uses a "public" permission, so the PropertyRestrictionPolicyHelper
|
# The Solr index uses a "public" permission, so the PropertyRestrictionPolicyHelper
|
||||||
# and the PermissionRegistry must already be set up.
|
# and the PermissionRegistry must already be set up.
|
||||||
edu.cornell.mannlib.vitro.webapp.search.solr.SolrSetup
|
edu.cornell.mannlib.vitro.webapp.search.solr.SolrSetup
|
||||||
|
|
|
@ -78,6 +78,16 @@
|
||||||
<url-pattern>/*</url-pattern>
|
<url-pattern>/*</url-pattern>
|
||||||
</filter-mapping>
|
</filter-mapping>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<description>Override the Locale in the HttpRequest, if appropriate.</description>
|
||||||
|
<filter-name>Locale selection filter</filter-name>
|
||||||
|
<filter-class>edu.cornell.mannlib.vitro.webapp.i18n.selection.LocaleSelectionFilter</filter-class>
|
||||||
|
</filter>
|
||||||
|
<filter-mapping>
|
||||||
|
<filter-name>Locale selection filter</filter-name>
|
||||||
|
<url-pattern>/*</url-pattern>
|
||||||
|
</filter-mapping>
|
||||||
|
|
||||||
<filter>
|
<filter>
|
||||||
<filter-name>JSession Strip Filter</filter-name>
|
<filter-name>JSession Strip Filter</filter-name>
|
||||||
<filter-class>edu.cornell.mannlib.vitro.webapp.filters.JSessionStripFilter</filter-class>
|
<filter-class>edu.cornell.mannlib.vitro.webapp.filters.JSessionStripFilter</filter-class>
|
||||||
|
@ -1338,6 +1348,16 @@
|
||||||
<url-pattern>/orng/*</url-pattern>
|
<url-pattern>/orng/*</url-pattern>
|
||||||
</servlet-mapping>
|
</servlet-mapping>
|
||||||
|
|
||||||
|
<servlet>
|
||||||
|
<description>Multiple-language support. Allows user to select his preferred langauge</description>
|
||||||
|
<servlet-name>LocaleSelectionController</servlet-name>
|
||||||
|
<servlet-class>edu.cornell.mannlib.vitro.webapp.i18n.selection.LocaleSelectionController</servlet-class>
|
||||||
|
</servlet>
|
||||||
|
<servlet-mapping>
|
||||||
|
<servlet-name>LocaleSelectionController</servlet-name>
|
||||||
|
<url-pattern>/selectLocale</url-pattern>
|
||||||
|
</servlet-mapping>
|
||||||
|
|
||||||
|
|
||||||
<!-- ==================== tag libraries ============================== -->
|
<!-- ==================== tag libraries ============================== -->
|
||||||
<jsp-config>
|
<jsp-config>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
<nav role="navigation">
|
<nav role="navigation">
|
||||||
<ul id="header-nav" role="list">
|
<ul id="header-nav" role="list">
|
||||||
|
<#include "languageSelector.ftl">
|
||||||
<#if user.loggedIn>
|
<#if user.loggedIn>
|
||||||
<li role="listitem">${user.loginName}</li>
|
<li role="listitem">${user.loginName}</li>
|
||||||
<li role="listitem"><a href="${urls.logout}" title="End your session">Log out</a></li>
|
<li role="listitem"><a href="${urls.logout}" title="End your session">Log out</a></li>
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
|
||||||
|
|
||||||
|
<#--
|
||||||
|
How can this done with images instead of buttons containing images?
|
||||||
|
Why don't the "alt" values show as tooltips?"
|
||||||
|
What was the right way to do this?
|
||||||
|
-->
|
||||||
|
|
||||||
|
<#-- This is included by identity.ftl -->
|
||||||
|
<#if selectLocale??>
|
||||||
|
<li>
|
||||||
|
<form method="get" action="${selectLocale.selectLocaleUrl}" >
|
||||||
|
<#list selectLocale.locales as locale>
|
||||||
|
<button type="submit" name="selection" value="${locale.code}">
|
||||||
|
<img src="${locale.imageUrl}" height="15" align="middle" alt="${locale.label}"/>
|
||||||
|
</button>
|
||||||
|
<#if locale_has_next>|</#if>
|
||||||
|
</#list>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* selectLocale
|
||||||
|
* -- selectLocaleUrl
|
||||||
|
* -- locales [list of maps]
|
||||||
|
* -- [map]
|
||||||
|
* -- code
|
||||||
|
* -- label (tooltip relative to the current Locale)
|
||||||
|
* -- imageUrl
|
||||||
|
-->
|
Loading…
Add table
Reference in a new issue