Commit 5b572ebc authored by Matija Obreza's avatar Matija Obreza

LocaleURLFilter detects locale at the start of the requested URL

parent 022bb62b
......@@ -16,6 +16,8 @@
package org.genesys2.server.servlet.controller;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import org.genesys2.server.model.UserRole;
......@@ -27,6 +29,7 @@ import org.genesys2.server.service.EMailVerificationService;
import org.genesys2.server.service.OrganizationService;
import org.genesys2.server.service.StatisticsService;
import org.genesys2.server.service.UserService;
import org.genesys2.server.servlet.filter.LocaleURLFilter;
import org.genesys2.util.ReCaptchaUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
......@@ -78,7 +81,17 @@ public class HtmlController extends BaseController {
private OrganizationService organizationService;
@RequestMapping("/")
public String index() {
public String index(HttpServletRequest request) {
Locale requestLocale = request.getLocale();
Locale requestedLocale = (Locale) request.getAttribute(LocaleURLFilter.REQUEST_LOCALE_ATTR);
System.err.println("Request locale: " + requestLocale + " detected: " + requestedLocale);
if (requestedLocale == null && requestLocale != null) {
System.err.println("redirecting!");
return "redirect:/" + requestLocale.getLanguage() + "/welcome";
}
return "redirect:/welcome";
}
......
package org.genesys2.server.servlet.filter;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.regex.Matcher;
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.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
public class LocaleURLFilter implements Filter {
private static final Logger LOG = Logger.getLogger(LocaleURLFilter.class);
private static final LocaleURLMatcher localeUrlMatcher = new LocaleURLMatcher();
public static final String REQUEST_LOCALE_ATTR = LocaleURLFilter.class.getName() + ".LOCALE";
private static final String REQUEST_INTERNAL_URL = LocaleURLFilter.class.getName() + ".INTERNALURL";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String excludePaths = filterConfig.getInitParameter("excludePaths");
if (StringUtils.isNotBlank(excludePaths)) {
LOG.info("Excluding paths: " + excludePaths);
String[] ex = excludePaths.split("\\s+");
for (String e : ex) {
LOG.info("Excluding path: " + e);
}
localeUrlMatcher.setExcludedPaths(ex);
}
}
@Override
public void destroy() {
if (LOG.isDebugEnabled()) {
LOG.debug("Destroying LocaleURLFilter");
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final String url = request.getRequestURI().substring(request.getContextPath().length());
if (localeUrlMatcher.isExcludedPath(url)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
if (LOG.isDebugEnabled()) {
LOG.debug("Incoming URL: " + url);
}
final Matcher matcher = localeUrlMatcher.matcher(url);
if (matcher.matches()) {
String urlLanguage = matcher.group(1);
String remainingUrl = matcher.group(2);
Locale urlLocale = Locale.forLanguageTag(urlLanguage);
request.setAttribute(REQUEST_LOCALE_ATTR, urlLocale);
request.setAttribute(REQUEST_INTERNAL_URL, getInternalUrl(remainingUrl, request.getQueryString()));
if (LOG.isDebugEnabled()) {
LOG.debug("URL matches! lang=" + urlLanguage + " remaining=" + remainingUrl);
LOG.debug("Country: " + urlLocale.getCountry() + " Lang: " + urlLocale.getLanguage() + " locale=" + urlLocale);
}
Enumeration<String> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = attrNames.nextElement();
LOG.info("Request attr " + attrName + " = " + request.getAttribute(attrName));
}
LocaleWrappedServletResponse localeResponse = new LocaleWrappedServletResponse((HttpServletResponse) servletResponse, localeUrlMatcher, urlLanguage);
LocaleWrappedServletRequest localeRequest = new LocaleWrappedServletRequest((HttpServletRequest) servletRequest, url, remainingUrl);
LOG.info("Proxying request to remaining URL " + remainingUrl);
// request.getRequestDispatcher(remainingUrl == null ? "/" :
// remainingUrl).forward(servletRequest, localeResponse);
filterChain.doFilter(localeRequest, localeResponse);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("No match on url " + url);
}
request.setAttribute(REQUEST_INTERNAL_URL, getInternalUrl(url, request.getQueryString()));
filterChain.doFilter(servletRequest, servletResponse);
}
}
private String getInternalUrl(String url, String queryString) {
if (StringUtils.isBlank(queryString))
return url;
else
return url + "?" + queryString;
}
}
package org.genesys2.server.servlet.filter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
public class LocaleURLMatcher {
private static final Logger LOG = Logger.getLogger(LocaleURLMatcher.class);
private static final Pattern localePattern = Pattern.compile("^/([a-z]{2}(?:\\-[a-z]{2})?)(/.*)");
private String[] excludedPaths;
public void setExcludedPaths(String[] excludedPaths) {
this.excludedPaths = excludedPaths;
}
public boolean isExcludedPath(String url) {
for (String excludedPath : excludedPaths) {
if (url.startsWith(excludedPath)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Excluded: " + excludedPath + " matches " + url);
}
return true;
}
}
return false;
}
public boolean isExcluded(String url) {
for (String excludedPath : excludedPaths) {
if (url.startsWith(excludedPath)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Excluded: " + excludedPath + " matches " + url);
}
return true;
}
}
// if (localePattern.matcher(url).matches()) {
// LOG.info("Excluding locale-URL " + url);
// return true;
// }
if (fastCheck(url)) {
return true;
}
return false;
}
private boolean fastCheck(String url) {
char[] dst = new char[7];
url.getChars(0, Math.min(url.length(), 7), dst, 0);
// Check language
if (dst[0] != '/')
return false;
if (dst[1] < 'a' || dst[1] > 'z')
return false;
if (dst[2] < 'a' || dst[2] > 'z')
return false;
// end processing
if (dst[3] == '/')
return true;
// Check country
if (dst[3] != '-')
return false;
if (dst[4] < 'a' || dst[4] > 'z')
return false;
if (dst[5] < 'a' || dst[5] > 'z')
return false;
if (dst[6] != '/')
return false;
return true;
}
public Matcher matcher(String url) {
return localePattern.matcher(url);
}
}
package org.genesys2.server.servlet.filter;
import java.io.IOException;
import java.util.Collection;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.Part;
import org.apache.log4j.Logger;
public class LocaleWrappedServletRequest extends HttpServletRequestWrapper {
private static final Logger LOG = Logger.getLogger(LocaleWrappedServletRequest.class);
private final String remainingUrl;
private String originalUrl;
public LocaleWrappedServletRequest(HttpServletRequest request, String originalUrl, String remainingUrl) {
super(request);
this.originalUrl = originalUrl;
this.remainingUrl = remainingUrl;
}
@Override
public String getServletPath() {
String servletPath = super.getServletPath();
if (this.originalUrl.equals(servletPath)) {
if (LOG.isDebugEnabled())
LOG.debug("servletPath: " + servletPath + " remaining: " + remainingUrl);
return remainingUrl;
}
return servletPath;
}
@Override
public String getRequestURI() {
String requestURI = super.getRequestURI();
if (this.originalUrl.equals(requestURI)) {
if (LOG.isDebugEnabled())
LOG.debug("requestURI: " + requestURI + " remaining=" + remainingUrl);
return remainingUrl;
}
return requestURI;
}
@Override
public StringBuffer getRequestURL() {
StringBuffer requestURL = super.getRequestURL();
// LOG.info("requestURL: " + requestURL);
return requestURL;
}
}
package org.genesys2.server.servlet.filter;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.apache.log4j.Logger;
public class LocaleWrappedServletResponse extends HttpServletResponseWrapper {
private static final Logger LOG = Logger.getLogger(LocaleWrappedServletResponse.class);
private String prefix;
private LocaleURLMatcher localeUrlMatcher;
public LocaleWrappedServletResponse(HttpServletResponse response, LocaleURLMatcher localeUrlMatcher, String urlLanguage) {
super(response);
this.localeUrlMatcher = localeUrlMatcher;
this.prefix = updatePrefix(urlLanguage);
}
private boolean isExcluded(String url) {
// Exclude querystring-only urls
return url.startsWith("?") || localeUrlMatcher.isExcluded(url);
}
@Override
public String encodeURL(String url) {
if (isExcluded(url)) {
if (url.startsWith("/en/")) {
return super.encodeURL(url.substring(3));
}
return super.encodeURL(url);
} else {
String encodedURL = prefix + super.encodeURL(url);
LOG.info("encodeURL " + url + " to " + encodedURL);
return encodedURL;
}
}
@Override
@Deprecated
public String encodeUrl(String url) {
if (isExcluded(url)) {
return super.encodeUrl(url);
} else {
String encodedURL = prefix + super.encodeUrl(url);
LOG.info("encodeUrl " + url + " to " + encodedURL);
return encodedURL;
}
}
@Override
public String encodeRedirectURL(String url) {
if (isExcluded(url)) {
return super.encodeRedirectURL(url);
} else {
String encodedURL = prefix + super.encodeRedirectURL(url);
LOG.info("encodeRedirectURL " + url + " to " + encodedURL);
return encodedURL;
}
}
@Override
@Deprecated
public String encodeRedirectUrl(String url) {
if (isExcluded(url)) {
return super.encodeRedirectUrl(url);
} else {
String encodedURL = prefix + super.encodeRedirectUrl(url);
LOG.info("encodeRedirectUrl " + url + " to " + encodedURL);
return encodedURL;
}
}
private String updatePrefix(String language) {
if (language == null || language.length() != 2) {
language = null;
}
if (language == null) {
return "";
} else {
return "/" + language;
}
}
}
......@@ -32,72 +32,54 @@
package org.genesys2.spring;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import org.genesys2.server.servlet.filter.LocaleURLFilter;
import org.springframework.web.servlet.LocaleResolver;
/**
* Implementation of LocaleResolver that uses a locale attribute in the user's
* session in case of a custom setting, with a fallback to the specified default
* locale or the request's accept-header locale.
*
*
* <p>
* This is most appropriate if the application needs user sessions anyway, that
* is, when the HttpSession does not have to be created for the locale.
*
*
* <p>
* Custom controllers can override the user's locale by calling
* {@code setLocale}, e.g. responding to a locale change request.
*
*
* @author Juergen Hoeller
* @author Matija Obreza
*
*
* @see #setDefaultLocale
* @see #setLocale
*/
public class BetterSessionLocaleResolver extends SessionLocaleResolver {
public class RequestAttributeLocaleResolver implements LocaleResolver {
private Set<String> supportedLocales = new HashSet<String>();
private Locale defaultLocale;
public void setSupportedLocales(Set<String> supportedLocales) {
this.supportedLocales = supportedLocales;
}
public Set<String> getSupportedLocales() {
return supportedLocales;
}
/**
* Determine the default locale for the given request, Called if no locale
* session attribute has been found.
* <p>
* This implementation returns the request's accept-header locale if listed
* in the {@link #supportedLocales} , else falls back to the specified
* default locale.
*
* @param request
* the request to resolve the locale for
* @return the default locale (never {@code null})
* @see #setDefaultLocale
* @see javax.servlet.http.HttpServletRequest#getLocale()
*/
@Override
protected Locale determineDefaultLocale(HttpServletRequest request) {
Locale defaultLocale = request.getLocale();
public Locale resolveLocale(HttpServletRequest request) {
Locale requestAttributeLocale = (Locale) request.getAttribute(LocaleURLFilter.REQUEST_LOCALE_ATTR);
if (defaultLocale != null && !supportedLocales.contains(defaultLocale.getLanguage())) {
// reset request locale if not on the list of supported locales
defaultLocale = null;
if (requestAttributeLocale == null) {
return this.defaultLocale;
}
if (defaultLocale == null) {
defaultLocale = getDefaultLocale();
}
return defaultLocale;
return requestAttributeLocale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
throw new UnsupportedOperationException("Cannot set locale!");
}
public void setDefaultLocale(Locale defaultLocale) {
this.defaultLocale=defaultLocale;
}
}
......@@ -22,7 +22,7 @@ import java.util.Properties;
import java.util.Set;
import org.genesys2.spring.AddStuffInterceptor;
import org.genesys2.spring.BetterSessionLocaleResolver;
import org.genesys2.spring.RequestAttributeLocaleResolver;
import org.genesys2.spring.validation.oval.spring.SpringOvalValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
......@@ -41,7 +41,6 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.theme.CookieThemeResolver;
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
......@@ -83,13 +82,9 @@ public class SpringServletConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
final LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
final ThemeChangeInterceptor themeChangeInterceptor = new ThemeChangeInterceptor();
themeChangeInterceptor.setParamName("theme");
registry.addInterceptor(localeChangeInterceptor);
registry.addInterceptor(themeChangeInterceptor);
registry.addInterceptor(addStuffInterceptor);
}
......@@ -123,7 +118,7 @@ public class SpringServletConfig extends WebMvcConfigurerAdapter {
}
@Bean
public BetterSessionLocaleResolver localeResolver() {
public RequestAttributeLocaleResolver localeResolver() {
final Set<String> supportedLocales = new HashSet<String>();
supportedLocales.add("en");
......@@ -136,9 +131,9 @@ public class SpringServletConfig extends WebMvcConfigurerAdapter {
supportedLocales.add("zh");
// supportedLocales.add("sl");
final BetterSessionLocaleResolver resolver = new BetterSessionLocaleResolver();
resolver.setDefaultLocale(Locale.forLanguageTag("en"));
resolver.setSupportedLocales(supportedLocales);
final RequestAttributeLocaleResolver resolver = new RequestAttributeLocaleResolver();
resolver.setDefaultLocale(new Locale("en"));
// resolver.setSupportedLocales(supportedLocales);
return resolver;
}
......
......@@ -25,6 +25,7 @@ log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %t %5p %c{1}:%L - %m
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=info, stdout
log4j.category.org.genesys2.server.servlet.filter=debug
#log4j.category.org.genesys2=debug
#log4j.category.org.hibernate.search=debug
#log4j.category.org.apache.tomcat.jdbc.pool=debug
......
......@@ -16,18 +16,19 @@
<meta name="_csrf_header" content="${_csrf.headerName}"/>
<!-- Links -->
<link rel="shortcut icon" href="<c:url value="/genesys.png" />" />
<link rel="shortcut icon" href="<c:url value="/html/images/genesys.png" />" />
<!-- l10n -->
<link rel="alternate" hreflang="en" href="<c:url value="?${pageContext.request.queryString}"><c:param name="lang" value="en" /></c:url>" />
<link rel="alternate" hreflang="ar" href="<c:url value="?${pageContext.request.queryString}"><c:param name="lang" value="ar" /></c:url>" />
<link rel="alternate" hreflang="de" href="<c:url value="?${pageContext.request.queryString}"><c:param name="lang" value="de" /></c:url>" />
<link rel="alternate" hreflang="es" href="<c:url value="?${pageContext.request.queryString}"><c:param name="lang" value="es" /></c:url>" />
<link rel="alternate" hreflang="fa" href="<c:url value="?${pageContext.request.queryString}"><c:param name="lang" value="fa" /></c:url>" />
<link rel="alternate" hreflang="fr" href="<c:url value="?${pageContext.request.queryString}"><c:param name="lang" value="fr" /></c:url>" />
<link rel="alternate" hreflang="pt" href="<c:url value="?${pageContext.request.queryString}"><c:param name="lang" value="pt" /></c:url>" />
<link rel="alternate" hreflang="ru" href="<c:url value="?${pageContext.request.queryString}"><c:param name="lang" value="ru" /></c:url>" />
<link rel="alternate" hreflang="zh" href="<c:url value="?${pageContext.request.queryString}"><c:param name="lang" value="zh" /></c:url>" />
<link rel="alternate" hreflang="en" href="<c:url value="/en${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="ar" href="<c:url value="/ar${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="de" href="<c:url value="/de${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="es" href="<c:url value="/es${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="fa" href="<c:url value="/fa${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="fr" href="<c:url value="/fr${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="pt" href="<c:url value="/pt${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="ru" href="<c:url value="/ru${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<link rel="alternate" hreflang="zh" href="<c:url value="/zh${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />" />
<title><sitemesh:write property="title" /></title>
......
......@@ -4,7 +4,7 @@
<div class="container">
<div class="clearfix">
<div class="pull-left">
<a href="<c:url value="/" />"><img src="<c:url value="/html/images/logo_genesys.png" />" alt="Genesys - Gateway to Genetic Resources" title="Genesys - Gateway to Genetic Resources" /></a>
<a href="<c:url value="/welcome" />"><img src="<c:url value="/html/images/logo_genesys.png" />" alt="Genesys - Gateway to Genetic Resources" title="Genesys - Gateway to Genetic Resources" /></a>
</div>
<form class="navbar-form navbar-left" role="search" id="search" method="get" action="<c:url value="/acn/search" />">
......@@ -67,19 +67,15 @@
<li class="dropdown" id="lang">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><c:out value="${pageContext.response.locale.displayLanguage}" /> <b class="caret"></b></a>
<ul class="dropdown-menu" role="menu">
<li><a href="?lang=en">English</a></li>
<li><a href="?lang=ar">Arabic</a></li>
<li><a href="?lang=de">German</a></li>
<li><a href="?lang=es">Spanish</a></li>
<li><a href="?lang=fa">Persian</a></li>
<li><a href="?lang=fr">French</a></li>
<li><a href="?lang=pt">Portugese</a></li>
<li><a href="?lang=ru">Russian</a></li>
<li><a href="?lang=zh">Chinese</a></li>
<%-- Only show fully translated languages --%>
<security:authorize access="isAuthenticated()">
<li><a href="?lang=sl">Slovene</a></li>
</security:authorize>
<li><a href="<c:url value="/en${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />">English</a></li>
<li><a href="<c:url value="/ar${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />">Arabic</a></li>
<li><a href="<c:url value="/de${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />">German</a></li>
<li><a href="<c:url value="/es${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />">Spanish</a></li>
<li><a href="<c:url value="/fa${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />">Persian</a></li>
<li><a href="<c:url value="/fr${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />">French</a></li>
<li><a href="<c:url value="/pt${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />">Portugese</a></li>
<li><a href="<c:url value="/ru${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />">Russian</a></li>
<li><a href="<c:url value="/zh${pageContext.request.getAttribute('org.genesys2.server.servlet.filter.LocaleURLFilter.INTERNALURL')}" />">Chinese</a></li>
<li><a target="_blank" href="https://www.transifex.com/projects/p/genesys/">Translate Genesys</a></li>
</ul>
</li>
......@@ -147,19 +143,15 @@
<li class="dropdown" id="lang">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><c:out value="${pageContext.response.locale.displayLanguage}" /> <b class="caret"></b></a>
<ul class="dropdown-menu" role="menu">
<li><a href="?lang=en">English</a></li>
<li><a href="?lang=ar">Arabic</a></li>
<li><a href="?lang=de">German</a></li>
<li><a href="?lang=es">Spanish</a></li>
<li><a href="?lang=fa">Persian</a></li>