This Confluence has been LDAP enabled, if you are an ASF Committer, please use your LDAP Credentials to login. Any problems file an INFRA jira ticket please.

Child pages
  • Wicket and localized URLs
Skip to end of metadata
Go to start of metadata

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

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;
					}

                                });
			} 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.

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

Conclusion

TODO.

  • No labels