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

LocaleURLFilter detects locale at the start of the requested URL

parent 022bb62b
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package org.genesys2.server.servlet.controller; package org.genesys2.server.servlet.controller;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.genesys2.server.model.UserRole; import org.genesys2.server.model.UserRole;
...@@ -27,6 +29,7 @@ import org.genesys2.server.service.EMailVerificationService; ...@@ -27,6 +29,7 @@ import org.genesys2.server.service.EMailVerificationService;
import org.genesys2.server.service.OrganizationService; import org.genesys2.server.service.OrganizationService;
import org.genesys2.server.service.StatisticsService; import org.genesys2.server.service.StatisticsService;
import org.genesys2.server.service.UserService; import org.genesys2.server.service.UserService;
import org.genesys2.server.servlet.filter.LocaleURLFilter;
import org.genesys2.util.ReCaptchaUtil; import org.genesys2.util.ReCaptchaUtil;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
...@@ -78,7 +81,17 @@ public class HtmlController extends BaseController { ...@@ -78,7 +81,17 @@ public class HtmlController extends BaseController {
private OrganizationService organizationService; private OrganizationService organizationService;
@RequestMapping("/") @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"; 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 @@ ...@@ -32,72 +32,54 @@
package org.genesys2.spring; package org.genesys2.spring;
import java.util.HashSet;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import javax.servlet.http.HttpServletRequest; 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 * 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 * session in case of a custom setting, with a fallback to the specified default
* locale or the request's accept-header locale. * locale or the request's accept-header locale.
* *
* <p> * <p>
* This is most appropriate if the application needs user sessions anyway, that * 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. * is, when the HttpSession does not have to be created for the locale.
* *
* <p> * <p>
* Custom controllers can override the user's locale by calling * Custom controllers can override the user's locale by calling
* {@code setLocale}, e.g. responding to a locale change request. * {@code setLocale}, e.g. responding to a locale change request.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Matija Obreza * @author Matija Obreza
* *
* @see #setDefaultLocale * @see #setDefaultLocale
* @see #setLocale * @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 @Override
protected Locale determineDefaultLocale(HttpServletRequest request) { public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = request.getLocale(); Locale requestAttributeLocale = (Locale) request.getAttribute(LocaleURLFilter.REQUEST_LOCALE_ATTR);
if (defaultLocale != null && !supportedLocales.contains(defaultLocale.getLanguage())) { if (requestAttributeLocale == null) {
// reset request locale if not on the list of supported locales return this.defaultLocale;
defaultLocale = null;
} }
if (defaultLocale == null) { return requestAttributeLocale;
defaultLocale = getDefaultLocale(); }
}
return defaultLocale; @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; ...@@ -22,7 +22,7 @@ import java.util.Properties;
import java.util.Set; import java.util.Set;
import org.genesys2.spring.AddStuffInterceptor; 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.genesys2.spring.validation.oval.spring.SpringOvalValidator;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
...@@ -41,7 +41,6 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry; ...@@ -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.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; 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.CookieThemeResolver;
import org.springframework.web.servlet.theme.ThemeChangeInterceptor; import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
...@@ -83,13 +82,9 @@ public class SpringServletConfig extends WebMvcConfigurerAdapter { ...@@ -83,13 +82,9 @@ public class SpringServletConfig extends WebMvcConfigurerAdapter {
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
final LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
final ThemeChangeInterceptor themeChangeInterceptor = new ThemeChangeInterceptor(); final ThemeChangeInterceptor themeChangeInterceptor = new ThemeChangeInterceptor();
themeChangeInterceptor.setParamName("theme"); themeChangeInterceptor.setParamName("theme");
registry.addInterceptor(localeChangeInterceptor);
registry.addInterceptor(themeChangeInterceptor); registry.addInterceptor(themeChangeInterceptor);
registry.addInterceptor(addStuffInterceptor); registry.addInterceptor(addStuffInterceptor);
} }
...@@ -123,7 +118,7 @@ public class SpringServletConfig extends WebMvcConfigurerAdapter { ...@@ -123,7 +118,7 @@ public class SpringServletConfig extends WebMvcConfigurerAdapter {
} }
@Bean @Bean
public BetterSessionLocaleResolver localeResolver() { public RequestAttributeLocaleResolver localeResolver() {
final Set<String> supportedLocales = new HashSet<String>(); final Set<String> supportedLocales = new HashSet<String>();
supportedLocales.add("en"); supportedLocales.add("en");
...@@ -136,9 +131,9 @@ public class SpringServletConfig extends WebMvcConfigurerAdapter { ...@@ -136,9 +131,9 @@ public class SpringServletConfig extends WebMvcConfigurerAdapter {
supportedLocales.add("zh"); supportedLocales.add("zh");
// supportedLocales.add("sl"); // supportedLocales.add("sl");
final BetterSessionLocaleResolver resolver = new BetterSessionLocaleResolver(); final RequestAttributeLocaleResolver resolver = new RequestAttributeLocaleResolver();
resolver.setDefaultLocale(Locale.forLanguageTag("en")); resolver.setDefaultLocale(new Locale("en"));
resolver.setSupportedLocales(supportedLocales); // resolver.setSupportedLocales(supportedLocales);
return resolver; return resolver;
} }
......
...@@ -25,6 +25,7 @@ log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %t %5p %c{1}:%L - %m ...@@ -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' ### ### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=info, stdout log4j.rootLogger=info, stdout