Description
If you have application which require encoding of locale in pages URLs, for instance:
www.domain.com/uk/home
www.domain.com/nl/home
(For other use cases like www.domain.com/username see e.g. http://blog.jteam.nl/2010/02/24/wicket-root-mounts/.)
RequestDecorator
Simple decorator class for a Request object, used in LocaleUrlCodingStrategyDecorator to strip the locale from the original
url
import org.apache.wicket.Request; import org.apache.wicket.Page; import java.util.Locale; import java.util.Map; public class RequestDecorator extends Request { /** * Decorated request. */ private final Request request; /** * Constructor. * * @param request to decorate. */ public RequestDecorator(final Request request) { if (request == null) { throw new IllegalArgumentException("Decorated Request cannot be NULL!"); } this.request = request; } /** * {@inheritDoc} */ @Override public Locale getLocale() { return request.getLocale(); } /** * {@inheritDoc} */ @Override public String getParameter(final String key) { return request.getParameter(key); } /** * {@inheritDoc} */ @Override public Map<String, String[]> getParameterMap() { return request.getParameterMap(); } /** * {@inheritDoc} */ @Override public String[] getParameters(final String key) { return request.getParameters(key); } /** * {@inheritDoc} */ @Override public String getPath() { return request.getPath(); } /** * {@inheritDoc} */ @Override public String getQueryString() { return request.getQueryString(); } /** * {@inheritDoc} */ @Override public String getRelativePathPrefixToContextRoot() { return request.getRelativePathPrefixToContextRoot(); } /** * {@inheritDoc} */ @Override public String getRelativePathPrefixToWicketHandler() { return request.getRelativePathPrefixToWicketHandler(); } /** * {@inheritDoc} */ @Override public String getURL() { return request.getURL(); } @Override public String decodeURL(String url) { return request.decodeURL(url); } @Override public Page getPage() { return request.getPage(); } @Override public void setPage(Page page) { super.setPage(page); } @Override public String getRelativeURL() { return request.getRelativeURL(); } @Override public boolean mergeVersion() { return request.mergeVersion(); } @Override public String toString() { return request.toString(); } }
RequestCodingStrategyDecorator
To do this, you'll need to create your own implementation of IRequestCodingStrategy.
We need to have a decorator of IRequestCodingStrategy, below is the implementation:
import org.apache.wicket.IRequestTarget; import org.apache.wicket.Request; import org.apache.wicket.RequestCycle; import org.apache.wicket.request.IRequestCodingStrategy; import org.apache.wicket.request.RequestParameters; import org.apache.wicket.request.target.coding.IRequestTargetUrlCodingStrategy; /** * Decorator of {@link IRequestCodingStrategy} interface. * * @author Alex Objelean */ public class RequestCodingStrategyDecorator implements IRequestCodingStrategy { /** * Decorated strategy. */ private final IRequestCodingStrategy strategy; /** * Constructor. * @param strategy to decorate. */ public RequestCodingStrategyDecorator(final IRequestCodingStrategy strategy) { if (strategy == null) { throw new IllegalArgumentException("Strategy cannot be null!"); } this.strategy = strategy; } /** * {@inheritDoc} */ public void addIgnoreMountPath(final String path) { this.strategy.addIgnoreMountPath(path); } /** * {@inheritDoc} */ public RequestParameters decode(final Request request) { return this.strategy.decode(request); } /** * {@inheritDoc} */ public CharSequence encode(final RequestCycle requestCycle, final IRequestTarget requestTarget) { return this.strategy.encode(requestCycle, requestTarget); } /** * @return decorated {@link IRequestCodingStrategy}. */ public final IRequestCodingStrategy getDecoratedStrategy() { return this.strategy; } /** * {@inheritDoc} */ public void mount(final IRequestTargetUrlCodingStrategy urlCodingStrategy) { this.strategy.mount(urlCodingStrategy); } /** * {@inheritDoc} */ public CharSequence pathForTarget(final IRequestTarget requestTarget) { return this.strategy.pathForTarget(requestTarget); } /** * {@inheritDoc} */ public String rewriteStaticRelativeUrl(final String string) { return this.strategy.rewriteStaticRelativeUrl(string); } /** * {@inheritDoc} */ public IRequestTarget targetForRequest(final RequestParameters requestParameters) { return this.strategy.targetForRequest(requestParameters); } /** * {@inheritDoc} */ public void unmount(final String path) { this.strategy.unmount(path); } /** * {@inheritDoc} */ public IRequestTargetUrlCodingStrategy urlCodingStrategyForPath(final String path) { return this.strategy.urlCodingStrategyForPath(path); } }
LocaleUrlCodingStrategyDecorator
Next step is to create the coding strategy responsible for adding locale support to your urls.
import java.util.HashSet; import java.util.Locale; import java.util.Set; import org.apache.commons.lang.LocaleUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.apache.wicket.IRequestTarget; import org.apache.wicket.RedirectToUrlException; import org.apache.wicket.Request; import org.apache.wicket.RequestCycle; import org.apache.wicket.Session; import org.apache.wicket.protocol.http.request.WebRequestCodingStrategy; import org.apache.wicket.request.IRequestCodingStrategy; import org.apache.wicket.request.RequestParameters; import org.apache.wicket.request.target.coding.AbstractRequestTargetUrlCodingStrategy; import org.apache.wicket.request.target.coding.IRequestTargetUrlCodingStrategy; import org.apache.wicket.request.target.component.IBookmarkablePageRequestTarget; import org.apache.wicket.request.target.component.IPageRequestTarget; /** * Coding strategy that prepends locale to url, useful for caching localizable applications. * <p> * Changes the locale, depending on found locale in request path. Delegates the encoding/decoding call to original * decorated coding strategy, but with processed request path (strip locale). * * @author Alex Objelean * */ public class LocaleUrlCodingStrategyDecorator extends RequestCodingStrategyDecorator { /** * Simple implementation of {@link IRequestTargetUrlCodingStrategy}, used only to make wicket filter treat original * request as wicket related. */ private static class PassThroughUrlCodingStrategy extends AbstractRequestTargetUrlCodingStrategy { /** * Construct. * * @param path */ public PassThroughUrlCodingStrategy(final String path) { super(path); } /** * {@inheritDoc} */ public IRequestTarget decode(final RequestParameters requestParameters) { return null; } /** * {@inheritDoc} */ public CharSequence encode(final IRequestTarget requestTarget) { return null; } /** * {@inheritDoc} */ public boolean matches(final IRequestTarget requestTarget) { return false; } } /** log. */ private static org.apache.log4j.Logger log = Logger.getLogger(LocaleUrlCodingStrategyDecorator.class); /** * Set of supported locales. */ private final Set<String> locales = new HashSet<String>(); /** * Construct. * * @param defaultStrategy The default strategy most requests are forwarded to */ public LocaleUrlCodingStrategyDecorator(final IRequestCodingStrategy defaultStrategy) { super(defaultStrategy); locales.add("en"); locales.add("ro"); locales.add("pt"); locales.add("es"); locales.add("ru"); locales.add("fr"); } /** * Decode the querystring of the URL. one. * * @see org.apache.wicket.request.IRequestCodingStrategy#decode(org.apache.wicket.Request) */ @Override public RequestParameters decode(final Request request) { // log.debug("<decode>"); // log.debug("\trequestUrl: " + request.getURL()); try { final String requestPathLocale = getRequestPathLocale(request); final Locale locale = LocaleUtils.toLocale(requestPathLocale); if (requestPathLocale != null) { if (!Session.get().getLocale().equals(locale)) { log.debug("Changing locale to: " + locale); Session.get().setLocale(locale); } final String url = request.decodeURL(request.getURL()); // remove locale from request final String urlWithoutLocale = url.replace(getLocaleString(request.getLocale()), ""); // log.debug("DecodedUrl: " + urlWithoutLocale); // use decorator for decoding return getDecoratedStrategy().decode(new RequestDecorator(request) { @Override public String getURL() { return urlWithoutLocale; } }); } else if (!request.getPath().startsWith(WebRequestCodingStrategy.RESOURCES_PATH_PREFIX)){ //redirect to locale aware url for all request which are not resource related //ISSUE: Redirect any url's without i18n to the default /en/ versions of those pages throw new RedirectToUrlException("en/" + request.getPath() + "?" + request.getQueryString()); } return getDecoratedStrategy().decode(request); } finally { // log.debug("</decode>"); } } /** * Encode the querystring of the URL */ @Override public CharSequence encode(final RequestCycle requestCycle, final IRequestTarget requestTarget) { // log.debug("<encode>"); String url = getDecoratedStrategy().encode(requestCycle, requestTarget).toString(); try { // log.debug("\turl: " + url); //rewrite only requests for pages & links (ignore others, like resources) if (requestTarget instanceof IBookmarkablePageRequestTarget || requestTarget instanceof IPageRequestTarget) { final String localeString = getLocaleString(Session.get().getLocale()); if (url.startsWith("../")) { //TODO move this logic to utility method? //last index is incremented by 3, because ../ has 3 characters final int lastIndex = url.lastIndexOf("../") + 3; final String remainingUrl = url.substring(lastIndex); if (StringUtils.isEmpty(remainingUrl)) { return url; } url = url.substring(0, lastIndex) + localeString + remainingUrl; return url; } // if starts with . -> skip if (url.startsWith(".")) { return url; } url = localeString + url; } return url; } finally { // log.debug("\tEncoding: " + url); // log.debug("</encode>"); } } /** * @param locale * @return */ private String getLocaleString(final Locale locale) { return locale.getLanguage() + "/"; } /** * Returns encoded locale in the request path. If none is found, null is returned. * * @param request * @return */ private String getRequestPathLocale(final Request request) { final String path = request.getPath(); for (final String locale : locales) { if (path.startsWith(locale + "/")) { return locale; } } return null; } /** * Remove locale from path. * * @param path to strip. * @return path without locale. */ private String stripLocaleFropPath(final String path) { for (final String locale : locales) { final String toStrip = locale + "/"; if (path.startsWith(toStrip)) { return path.replaceFirst(toStrip, ""); } } return path; } /** * {@inheritDoc} */ @Override public IRequestTarget targetForRequest(final RequestParameters requestParameters) { //delegate call to decorated codingStrategy, but with locale removed final String newPath = stripLocaleFropPath(requestParameters.getPath()); requestParameters.setPath(newPath); return super.targetForRequest(requestParameters); } /** * {@inheritDoc} */ @Override public IRequestTargetUrlCodingStrategy urlCodingStrategyForPath(final String path) { final String newPath = stripLocaleFropPath(path); if (StringUtils.isEmpty(newPath)) { // treat this kind of situation by returing some not null codingStrategy, // to let this kind of request treated by wicket return new PassThroughUrlCodingStrategy(newPath); } return super.urlCodingStrategyForPath(newPath); } }
Usage
In MyApplication.java, override newRequestCycleProcessor method & add the newly created LocaleUrlCodingStrategyDecorator.
/** * {@inheritDoc} */ @Override protected IRequestCycleProcessor newRequestCycleProcessor() { return new WebRequestCycleProcessor() { @Override protected IRequestCodingStrategy newRequestCodingStrategy() { return new LocaleUrlCodingStrategyDecorator(super.newRequestCodingStrategy()); } } }
Conclusion
TODO.