Apache Wicket > Framework Documentation > Best Practices and Gotchas > Wicket and localized URLs
Added by Alex Objelean, last edited by Jo Geraerts on Nov 07, 2009  (view change)

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

RequestDecorator

Simple decorator class for a Request object, used in LocaleUrlCodingStrategyDecorator to strip the locale from the original
url

RequestDecorator.java
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:

RequestCodingStrategyDecorator.java
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.

LocaleUrlCodingStrategyDecorator.java
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;
					}

                                         @Override
                                         public String getPath() {
                                                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());
			}
			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.

MyApplication.java
/** 
   * {@inheritDoc} 
   */ 
  @Override 
  protected IRequestCycleProcessor newRequestCycleProcessor() { 
    return new WebRequestCycleProcessor() { 
    @Override 
      protected IRequestCodingStrategy newRequestCodingStrategy() { 
        return new LocaleUrlCodingStrategyDecorator(super.newRequestCodingStrategy()); 
      } 
    } 
  } 

Conclusion

TODO.