Page 1 of 1

[Java] RequestMapper package

Posted: Mon Sep 24, 2007 11:30 am
by Chris Corbyn
I know we have one or two Java Developers knocking around here so I figured I may get the odd response. I wanted to extend upon the extremely minimal J2EE spec for <servlet-mapping> abilities to actually affect what's in the request and to be a little more flexible than just a static string. This little package is intended to allow you to specify Mappings of the form "/some/path/:with/:vars/*" where the :with and :vars become named request parameters and the wildcard is evaluated from /key/value pairs.

I wrote this as part of a little framework I'm building and the usage is intended to be from within a filter (to create the request via Mapper.commit()) then inside JavaBean components and Tags to create clean URIs (from a list of parameters via Mapper.createUri()).

Support so far covers:

* Basic Request Mapping (longest pattern is selected)
* Parameter injection
* Prefix/suffix specifications (standard J2EE design)
* Regex pattern requirements on the URI

I'll post the tests in a following post to break things up a bit. There looks like a lot of code to critique but the first 3 files are interfaces and probably more useful to critique compared with their implementation.

Yet to be added:

* A factory which builds a new Mapper from XML or a properties file

Mapping.java

Code: Select all

/*
 Interface for Request Mapping rules.
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 
package org.mayo.requestmapper;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.io.UnsupportedEncodingException;

/**
 * Provides the API for a mapping rule in the RequestMapper system.
 * @author Chris Corbyn <chris@w3style.co.uk>
 */
public interface Mapping {
  
  /**
   * Returns the length of the pattern, excluding wildcards.
   * @return int
   */
  public int getLength();
  
  /**
   * Returns true only if this mapping matches the provided initial request.
   * @param HttpServletRequest request
   * @return boolean
   * @throws UnsupportedEncodingException
   */
  public boolean matchesRequest(HttpServletRequest request, String encoding)
      throws UnsupportedEncodingException;
  
  /**
   * Returns true only if the Map contains the same parameters as this
   * mapping does.
   * Implied parameters are not required to be in the Map.
   * @param Map<String,String> params
   * @return boolean
   */
  public boolean matchesParameters(Map<String,String> params);
  
  /**
   * Commits this Mapping to the HttpServletRequest given.
   * The encoding passed may be used in any URL decoding which is needed.
   * @param HttpServletRequest request
   * @param String encoding
   * @return HttpServletRequest
   * @throws UnsupportedEncodingException
   */
  public HttpServletRequest commit(HttpServletRequest request,
      String encoding) throws UnsupportedEncodingException;
  
  /**
   * Use the given request parameters to generate a URI from this mapping.
   * @param HttpServletRequest request
   * @param Map<String,String> params
   * @param String encoding
   * @return String
   * @throws UnsupportedEncodingException
   */
  public String createUri(HttpServletRequest request, Map<String,String> params,
      String encoding) throws UnsupportedEncodingException;
  
}
Mapper.java

Code: Select all

/*
 Interface for the request Mapper.
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 
package org.mayo.requestmapper;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.io.UnsupportedEncodingException;

/**
 * Provides the API for a Mapper class in the RequestMapper system.
 * @author Chris Corbyn <chris@w3style.co.uk>
 */
public interface Mapper {
  
  /**
   * Adds a new mapping rule to this MappingContainer.
   * @param Mapping mapping
   */
  public void addMapping(Mapping mapping);
  
  /**
   * Commits this Mapping to the HttpServletRequest given.
   * The encoding passed may be used in any URL decoding which is needed.
   * @param HttpServletRequest request
   * @param String encoding
   * @return HttpServletRequest
   * @throws UnsupportedEncodingException
   */
  public HttpServletRequest commit(HttpServletRequest request,
      String encoding) throws UnsupportedEncodingException;
  
  /**
   * Use the given request parameters to generate a URI from this mapping.
   * The encoding passed may be used in any URLEncoding which is needed.
   * @param HttpServletRequest request
   * @param Map<String,String> params
   * @param String encoding
   * @return String
   * @throws UnsupportedEncodingException
   */
  public String createUri(HttpServletRequest request, Map<String,String> params,
      String encoding) throws UnsupportedEncodingException;
  
}
MutableRequest.java

Code: Select all

/*
 This interface extends HttpServletRequest by allowing parameters to be set.
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 
package org.mayo.requestmapper;

import javax.servlet.http.HttpServletRequest;

/**
 * This interface extends HttpServletRequest by allowing parameters to be set.
 * @author Chris Corbyn <chris@w3style.co.uk>
 */
public interface MutableRequest extends HttpServletRequest {
  
  /**
   * Set a single value for a parameter.
   * @param String param
   * @param String value
   */
  public void setParameter(String param, String value);
  
  /**
   * Set a collection of values for a parameter.
   * @param String param
   * @param String[] values
   */
  public void setParameterValues(String param, String[] values);

}
MutableRequestWrapper.java

Code: Select all

/*
 Provides a wrapped up version of the request provided by the container.
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 
package org.mayo.requestmapper;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Enumeration;

/**
 * This class wraps request parameters provided by the mapping system into the
 * default request.
 * @author Chris Corbyn <chris@w3style.co.uk>
 */
@SuppressWarnings("deprecation")
public class MutableRequestWrapper extends HttpServletRequestWrapper
    implements MutableRequest {
  
  /** An immutable Map of parameters */
  private Map<String,String[]> internalParameterMap;
  
  /**
   * Constructs a new wrapper from the given request.
   * @param HttpServletRequest request
   */
  @SuppressWarnings("unchecked")
  public MutableRequestWrapper(HttpServletRequest request) {
    super(request);
    internalParameterMap = super.getParameterMap();
  }
  
  /**
   * Set a single value for a parameter.
   * @param String param
   * @param String value
   */
  public void setParameter(String param, String value) {
    String[] values = { value };
    setParameterValues(param, values);
  }
  
  /**
   * Set a collection of values for a parameter.
   * @param String param
   * @param String[] values
   */
  public void setParameterValues(String param, String[] values) {
    internalParameterMap.put(param, values);
  }
  
  /**
   * Returns an Enumeration over the names of the parameters in this request.
   * @return Enumeration<String>
   */
  @Override
  public Enumeration<String> getParameterNames() {
    final Iterator<String> it = internalParameterMap.keySet().iterator();
    return new Enumeration<String>() {
      public boolean hasMoreElements() {
        return it.hasNext();
      }
      public String nextElement() {
        return it.next();
      }
    };
  }
  
  /**
   * Get the collection of values for the given parameter.
   * This methods returns null if no such parameter exists.
   * @param String param
   * @return String[]
   */
  @Override
  public String[] getParameterValues(String param) {
    return internalParameterMap.get(param);
  }
  
  /**
   * Returns the value of the given request parameter.
   * If no such parameter exists this method returns null.
   * @param String param
   * @return String
   */
  @Override
  public String getParameter(String param) {
    String value = null;
    String[] values = getParameterValues(param);
    if (values != null) {
      value = values[0];
    }
    return value;
  }
  
  /**
   * Returns an immutable Map containing all parameters in this request.
   * @return Map<String,String[]>
   */
  @Override
  public Map<String,String[]> getParameterMap() {
    return new HashMap<String,String[]>(internalParameterMap);
  }

}
BasicMapping.java

Code: Select all

/*
 Provides a basic implementation of the Mapping interface.
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 
package org.mayo.requestmapper;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.io.UnsupportedEncodingException;

/**
 * Provides a basic implementation of the Mapping interface.
 * @author Chris Corbyn <chris@w3style.co.uk>
 */
public class BasicMapping implements Mapping {
  
  /** The pattern which this mapping is for */
  private String pattern;
  
  /** The parameters which are in the request without being in the URL */
  private Map<String,String> impliedParameters;
  
  /**
   * Constructs a new BasicMapping using the given pattern with no
   * impliedParameters.
   * @param String pattern
   */
  public BasicMapping(String pattern) {
    this(pattern, new HashMap<String,String>());
  }
  
  /**
   * Constructs a new BasicMapping using the given pattern with the provided
   * impliedParameters.
   * @param Map<String,String> impliedParameters
   * @param String pattern
   */
  public BasicMapping(String pattern, Map<String,String> impliedParameters) {
    setPattern(pattern);
    setImpliedParameters(impliedParameters);
  }
  
  /**
   * Set the URI pattern this Mapping matches.
   * @param String pattern
   */
  public void setPattern(String pattern) {
    if (!"/".equals(pattern) && pattern.endsWith("/")) {
      pattern = pattern.substring(0, pattern.length() - 1);
    }
    this.pattern = pattern;
  }
  
  /**
   * Get the URI pattern this Mapping matches.
   * @return String
   */
  public String getPattern() {
    return pattern;
  }
  
  /**
   * Set the implied parameters in this Mapping.
   * @param Map<String,String> impliedParameters
   */
  public void setImpliedParameters(Map<String,String> impliedParameters) {
    this.impliedParameters = impliedParameters;
  }
  
  /**
   * Get the implied parameters in this Mapping.
   * @return Map<String,String>
   */
  public Map<String,String> getImpliedParameters() {
    return impliedParameters;
  }
  
  /**
   * Returns the length of the pattern, excluding wildcards.
   * @return int
   */
  public int getLength() {
    return pattern.replaceAll("\\*$|:[a-zA-Z_][a-zA-Z0-9_]*", "").length();
  }
  
  /**
   * Read from the HttpServletRequest to determine the requested path without
   * the context path.
   * @param HttpServletRequest request
   * @return String
   */
  protected String getPathFromRequest(HttpServletRequest request) {
    String uri = request.getRequestURI().trim();
    uri = uri.substring(request.getContextPath().length());
    uri = uri.replaceAll("/{2,}", "/");
    return uri;
  }
  
  /**
   * Get a regular expression equivalent of the pattern.
   * @return String
   */
  protected String getRegex() {
    String regex = "\\Q" + pattern.replaceFirst("/\\*$", "\\\\E((?:/.*)?)\\\\Q").
        replaceFirst("/$", "\\\\E/?\\\\Q").
        replaceAll(":[a-zA-Z_][a-zA-Z0-9_]*", "\\\\E(.+?)\\\\Q") + "\\E";
    return regex;
  }
  
  /**
   * Returns a string which contains :key elements for replacing to create
   * a clean URI.
   * @return String
   */
  protected String getUriTemplate() {
    return pattern.replaceFirst("/\\*$", "/:%WILDCARD%");
  }
  
  /**
   * Returns true only if this initial request matches this Mapping.
   * @param HttpServletRequest request
   * @return boolean
   * @throws UnsupportedEncodingException
   */
  public boolean matchesRequest(HttpServletRequest request, String encoding)
      throws UnsupportedEncodingException {
    String uri = URLDecoder.decode(getPathFromRequest(request), encoding);
    String regex = getRegex();
    return uri.matches(regex);
  }
  
  /**
   * Get the keys which are required to be in a request for this pattern to match.
   * @return Set<String>
   */
  protected Set<String> getRequiredKeys() {
    Set<String> keys = new LinkedHashSet<String>();
    Matcher m = Pattern.compile(":([a-zA-Z_][a-zA-Z0-9_]*)").matcher(pattern);
    while (m.find()) {
      keys.add(m.group(1));
    }
    return keys;
  }
  
  /**
   * Returns true only if the Map contains the same parameters as this
   * mapping does.
   * Implied parameters are not required to be in the Map.
   * @param Map<String,String> params
   * @return boolean
   */
  public boolean matchesParameters(Map<String,String> params) {
    Set<String> keys = new HashSet<String>(params.keySet());
    for (String k : getRequiredKeys()) {
      if (!keys.contains(k)) {
        return false;
      }
      keys.remove(k);
    }
    for (String k : impliedParameters.keySet()) {
      if (!keys.contains(k)
          || !impliedParameters.get(k).equals(params.get(k))) {
        return false;
      }
      keys.remove(k);
    }
    return (keys.isEmpty() || pattern.endsWith("/*"));
  }
  
  /**
   * Explode out the wildcard portion of the URL to produce an array.
   * @param String wildcard
   * @return String[]
   */
  private String[] parseWildcard(String wildcard) {
    if (wildcard.startsWith("/")) {
      wildcard = wildcard.substring(1);
    }
    String[] params = wildcard.split("/");
    return params;
  }
  
  /**
   * Commits this Mapping to the HttpServletRequest given.
   * The encoding passed may be used in any URL decoding which is needed.
   * @param HttpServletRequest request
   * @param String encoding
   * @return HttpServletRequest
   * @throws UnsupportedEncodingException
   */
  public HttpServletRequest commit(HttpServletRequest request,
      String encoding) throws UnsupportedEncodingException {
    if (!matchesRequest(request, encoding)) {
      return request;
    }
    String uri = getPathFromRequest(request);
    String regex = getRegex();
    Matcher m = Pattern.compile(regex).matcher(uri);
    if (!m.matches()) {
      return request;
    }
    
    MutableRequest wrapper = new MutableRequestWrapper(request);
    
    int i = 0;
    for (String k : getRequiredKeys()) {
      wrapper.setParameter(k, URLDecoder.decode(m.group(++i), encoding));
    }
    
    //Compute the wildcard portion as /key/value pairs
    if (pattern.endsWith("/*")) {
      String[] wildcard = parseWildcard(m.group(i + 1));
      for (int j = 0; j < wildcard.length; j += 2) {
        String k = URLDecoder.decode(wildcard[j], encoding);
        String v = "";
        if (wildcard.length > j + 1) {
          v = URLDecoder.decode(wildcard[j + 1], encoding);
        }
        wrapper.setParameter(k, v);
      }
    }
    
    for (String k : impliedParameters.keySet()) {
      wrapper.setParameter(k, impliedParameters.get(k));
    }
    
    return wrapper;
  }
  
  /**
   * Use the given request parameters to generate a URI from this mapping.
   * The encoding passed may be used in any URLEncoding which is needed.
   * @param HttpServletRequest request
   * @param Map<String,String> params
   * @param String encoding
   * @return String
   * @throws UnsupportedEncodingException
   */
  public String createUri(HttpServletRequest request, Map<String,String> params,
      String encoding) throws UnsupportedEncodingException {
    if (!matchesParameters(params)) {
      return request.getContextPath();
    }
    
    String uri = getUriTemplate();
    Set<String> keys = new LinkedHashSet<String>(params.keySet());
    for (String k : getRequiredKeys()) {
      keys.remove(k);
      if (!params.containsKey(k)) {
        continue;
      }
      uri = uri.replace(":" + k, URLEncoder.encode(params.get(k), encoding));
    }
    
    StringBuffer wildcard = new StringBuffer();
    for (String k : keys) {
      String v = params.get(k);
      if (impliedParameters.containsKey(k) &&
          impliedParameters.get(k).equals(v)) {
        continue;
      }
      wildcard.append("/" + URLEncoder.encode(k, encoding));
      wildcard.append("/" + URLEncoder.encode(v, encoding));
    }
    uri = uri.replace("/:%WILDCARD%", wildcard.toString());
    
    return request.getContextPath() + uri;
  }
  
}
ComplexMapping.java

Code: Select all

/*
 Provides a full featured implementation of the Mapping interface supporting
 prefixes, suffixes, implied parameters, wildcards and regex requirements.
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 
package org.mayo.requestmapper;

import java.util.Map;
import java.util.HashMap;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.io.UnsupportedEncodingException;
import javax.servlet.http.HttpServletRequest;

/**
 * Provides a full featured implementation of the Mapping interface supporting
 * prefixes, suffixes, implied parameters, wildcards and regex requirements.
 * @author Chris Corbyn <chris@w3style.co.uk>
 */
public class ComplexMapping extends BasicMapping {
  
  /** The suffix on the URI */
  private String suffix = "";
  
  /** The prefix on the URI */
  private String prefix = "";
  
  /** Pattern requirements on URI parameters */
  private Map<String,String> requirements;
  
  /**
   * Constructs a new ComplexMapping using the given pattern with no
   * impliedParameters.
   * @param String pattern
   */
  public ComplexMapping(String pattern) {
    this(pattern, new HashMap<String,String>());
  }
  
  /**
   * Constructs a new ComplexMapping using the given pattern with the provided
   * impliedParameters.
   * @param Map<String,String> impliedParameters
   * @param String pattern
   */
  public ComplexMapping(String pattern, Map<String,String> impliedParameters) {
    this(pattern, impliedParameters, new HashMap<String,String>());
  }
  
  /**
   * Constructs a new ComplexMapping with the given pattern, impliedParameters
   * and pattern requirements.
   * @param String pattern
   * @param Map<String,String> impliedParameters
   * @param Map<String,String> requirements
   */
  public ComplexMapping(String pattern, Map<String,String> impliedParameters,
      Map<String,String> requirements) {
    this(pattern, impliedParameters, requirements, "", "");
  }
  
  /**
   * Constructs a new ComplexMapping with the given pattern, impliedParameters,
   * pattern requirements, prefix and suffix.
   * @param String pattern
   * @param Map<String,String> impliedParameters
   * @param Map<String,String> requirements
   * @param String prefix
   * @param String suffix
   */
  public ComplexMapping(String pattern, Map<String,String> impliedParameters,
      Map<String,String> requirements, String prefix, String suffix) {
    super(pattern, impliedParameters);
    setRequirements(requirements);
    setPrefix(prefix);
    setSuffix(suffix);
  }
  
  /**
   * Sets the suffix required to be on the URI.
   * @param String suffix
   */
  public void setSuffix(String suffix) {
    this.suffix = suffix;
  }
  
  /**
   * Returns the suffix which is needed on the URI.
   * @return String
   */
  public String getSuffix() {
    return suffix;
  }
  
  /**
   * Sets the prefix required to be on the URI.
   * @param String prefix
   */
  public void setPrefix(String prefix) {
    if (prefix.endsWith("/")) {
      prefix = prefix.substring(0, prefix.length() - 1);
    }
    this.prefix = prefix;
  }
  
  /**
   * Returns the prefix which is needed on the URI.
   * @return String
   */
  public String getPrefix() {
    return prefix;
  }
  
  /**
   * Set the regex requirements for parameters in the request.
   * @param Map<String,String> requirements
   */
  public void setRequirements(Map<String,String> requirements) {
    this.requirements = requirements;
  }
  
  /**
   * Get the regex requirements for parameters in the request.
   * @return Map<String,String>
   */
  public Map<String,String> getRequirements() {
    return new HashMap<String,String>(requirements);
  }
  
  /**
   * Get a regular expression equivalent of the pattern.
   * @return String
   */
  @Override
  protected String getRegex() { //Shorten this?
    StringBuffer buf = new StringBuffer(Pattern.quote(prefix));
    String regex = getPattern();
    regex = regex.replaceFirst("/\\*$", "\\\\E((?:/.*)?)\\\\Q");
    regex = regex.replaceFirst("/$", "\\\\E/?\\\\Q");
    Matcher m = Pattern.compile(":([a-zA-Z_][a-zA-Z0-9_]*)").matcher(regex);
    while (m.find()) {
      String k = m.group(1);
      String requirement;
      if (requirements.containsKey(k)) {
        requirement = "\\\\E(" + requirements.get(k).replace("\\", "\\\\") +
            ")\\\\Q";
      } else {
        requirement = "\\\\E(.+?)\\\\Q";
      }
      regex = regex.replaceFirst(":" + k, requirement);
    }
    buf.append("\\Q");
    buf.append(regex);
    buf.append("\\E");
    buf.append(Pattern.quote(suffix));
    return buf.toString();
  }
  
  /**
   * Get a template from which to build this Mapping into a clean URI.
   * @return String
   */
  @Override
  protected String getUriTemplate() {
    return prefix + super.getUriTemplate() + suffix;
  }
  
  /**
   * Returns true only if the parameters passed match this Mapping.
   * @param Map<String,String> params
   * @return boolean
   */
  @Override
  public boolean matchesParameters(Map<String,String> params) {
    for (String k : getRequiredKeys()) {
      if (!params.containsKey(k)) {
        return false;
      }
      String v = params.get(k);
      if (requirements.containsKey(k) && !v.matches(requirements.get(k))) {
        return false;
      }
    }
    return super.matchesParameters(params);
  }
  
}
RequestMapper.java

Code: Select all

/*
 Interface for the Request Mapper.
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 
package org.mayo.requestmapper;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Map;
import java.util.Set;
import java.util.LinkedHashSet;
import java.net.URLEncoder;
import java.io.UnsupportedEncodingException;

/**
 * Uses Mapping strategies to provide request rewriting.
 * @author Chris Corbyn <chris@w3style.co.uk>
 */
public class RequestMapper implements Mapper {
  
  /** Mapping objects */
  private Set<Mapping> mappings;
  
  /**
   * Constructs a new RequestMapper with no Mapping objects.
   */
  public RequestMapper() {
    mappings = new LinkedHashSet<Mapping>();
  }
  
  /**
   * Adds a new mapping rule to this RequestMapper.
   * @param Mapping mapping
   */
  public void addMapping(Mapping mapping) {
    mappings.add(mapping);
  }
  
  /**
   * Commits this Mapping to the HttpServletRequest given.
   * The encoding passed may be used in any URL decoding which is needed.
   * @param HttpServletRequest request
   * @param String encoding
   * @return HttpServletRequest
   * @throws UnsupportedEncodingException
   */
  public HttpServletRequest commit(HttpServletRequest request,
      String encoding) throws UnsupportedEncodingException {
    Mapping bestMapping = null;
    for (Mapping m : mappings) {
      if (m.matchesRequest(request, encoding)) {
        if (bestMapping == null || bestMapping.getLength() < m.getLength()) {
          bestMapping = m;
        }
      }
    }
    
    if (bestMapping != null) {
      return bestMapping.commit(request, encoding);
    } else {
      return request;
    }
  }
  
  /**
   * Create an unclean URI with the provided request parameters in it.
   * This is a fallback solution when a clean URI cannot be built.
   * @param HttpServletRequest request
   * @param Map<String,String> params
   * @param String encoding
   * @return String
   * @throws UnsupportedEncodingException
   */
  protected String createQueryStringUri(HttpServletRequest request,
      Map<String,String> params, String encoding)
      throws UnsupportedEncodingException {
    StringBuffer uri = new StringBuffer("/");
    String separator = "?";
    for (String k : params.keySet()) {
      String v = URLEncoder.encode(params.get(k), encoding);
      uri.append(separator);
      uri.append(URLEncoder.encode(k, encoding));
      uri.append("=");
      uri.append(v);
      separator = "&";
    }
    return request.getContextPath() + uri.toString();
  }
  
  /**
   * Use the given request parameters to generate a URI from this mapping.
   * The encoding passed may be used in any URLEncoding which is needed.
   * @param HttpServletRequest request
   * @param Map<String,String> params
   * @param String encoding
   * @return String
   * @throws UnsupportedEncodingException
   */
  public String createUri(HttpServletRequest request, Map<String,String> params,
      String encoding) throws UnsupportedEncodingException {
    Mapping bestMapping = null;
    for (Mapping m : mappings) {
      if (m.matchesParameters(params)) {
        if (bestMapping == null || bestMapping.getLength() < m.getLength()) {
          bestMapping = m;
        }
      }
    }
    
    if (bestMapping != null) {
      return bestMapping.createUri(request, params, encoding);
    } else {
      return createQueryStringUri(request, params, encoding);
    }
  }
  
}

Posted: Mon Sep 24, 2007 11:44 am
by Chris Corbyn
Test cases (depends on jMock):

AbstractMappingTest.java

Code: Select all

/*
 An abstract test case for Mapping classes.
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 */

package org.mayo.requestmapper;

import junit.framework.TestCase;
import org.jmock.Mockery;
import org.jmock.Expectations;
import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import org.mayo.jmock.MockRequestFactory;


/**
 * An abstract test case for Mapping classes.
 * @author Chris Corbyn <chris@w3style.co.uk>
 */
abstract public class AbstractMappingTest extends TestCase {
  
  /**
   * Get the Mapping instance with the given pattern.
   * @param String pattern
   * @return Mapping
   */
  abstract public Mapping getMapping(String pattern);
  
  /**
   * Get the Mapping instance with the given pattern and implied parameters.
   * @param Stirng pattern
   * @param Map<String,String> impliedParameters
   * @return Mapping
   */
  abstract public Mapping getMapping(String pattern,
      Map<String,String> impliedParameters);
  
  //Change this to return number of slashes
  // or something else maybe.  It's a tough one to think about
  public void testLengthComputation() {
    Mapping m1 = getMapping("/a/:url/:with/arguments/:in");
    assertEquals(15, m1.getLength());
    Mapping m2 = getMapping("/just/a/static/one");
    assertEquals(18, m2.getLength());
    Mapping m3 = getMapping("/*");
    assertEquals(1, m3.getLength());
    Mapping m4 = getMapping("/foo/bar/*");
    assertEquals(9, m4.getLength());
  }
  
  public void testMatchesBasicRequest() throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    
    Mapping m1 = getMapping("/");
    
    final HttpServletRequest req1 = MockRequestFactory.createRequest(mockery,
        "/somewhere/", "/somewhere");
    assertTrue("/ should match /", m1.matchesRequest(req1, "utf-8"));
    final HttpServletRequest req2 = MockRequestFactory.createRequest(mockery,
        "/somewhere/foobar", "/somewhere");
    assertFalse("/foobar should not match /", m1.matchesRequest(req2, "utf-8"));
  }
  
  public void testMatchesPatternRequest() throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    
    Mapping m1 = getMapping("/:foo/:bar");
    
    final HttpServletRequest req1 = MockRequestFactory.createRequest(mockery,
        "/x/y/", "");
    assertTrue("/x/y should match /:foo/:bar", m1.matchesRequest(req1, "utf-8"));
    final HttpServletRequest req2 = MockRequestFactory.createRequest(mockery,
        "/place/foo/bar/", "/place");
    assertTrue("/foo/bar should match /:foo/:bar", m1.matchesRequest(req2, "utf-8"));
    final HttpServletRequest req3 = MockRequestFactory.createRequest(mockery,
        "/", "");
    assertFalse("/ should not match /:foo/:bar", m1.matchesRequest(req3, "utf-8"));
    final HttpServletRequest req4 = MockRequestFactory.createRequest(mockery,
        "/context/x", "/context");
    assertFalse("/x should not match /:foo/:bar", m1.matchesRequest(req4, "utf-8"));
  }
  
  public void testMatchesWildcardRequest() throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    Mapping m1 = getMapping("/foo bar/*");
    
    final HttpServletRequest req1 = MockRequestFactory.createRequest(mockery,
        "/foo%20bar", "");
    assertTrue("/foo%20bar should match /foo bar/* (wildcard ignored)",
        m1.matchesRequest(req1, "utf-8"));
    final HttpServletRequest req2 = MockRequestFactory.createRequest(mockery,
        "/user/foo%20bar/a/b/c", "/user");
    assertTrue("/foo%20bar/a/b/c should match /foo bar/*",
        m1.matchesRequest(req2, "utf-8"));
    final HttpServletRequest req3 = MockRequestFactory.createRequest(mockery,
        "/user/notfoo%20bar", "/user");
    assertFalse("/notfoo%20bar should not match /foo bar/*",
        m1.matchesRequest(req3, "utf-8"));
    final HttpServletRequest req4 = MockRequestFactory.createRequest(mockery,
        "/a/b/c", "");
    assertFalse("/a/b/c should not match /foo bar/*",
        m1.matchesRequest(req4, "utf-8"));
  }
  
  public void testMatchesParametersWithImpliedParameters() {
    Map<String,String> map1 = new HashMap<String,String>();
    map1.put("foo", "bar");
    map1.put("zip", "button");
    Mapping m1 = getMapping("/", map1);
    
    Map<String,String> p1 = new HashMap<String,String>();
    p1.put("zip", "button");
    p1.put("foo", "bar");
    assertTrue("foo and zip are implied so should match",
        m1.matchesParameters(p1));
    assertFalse("foo and zip are needed before match",
        m1.matchesParameters(new HashMap<String,String>()));
    Map<String,String> p2 = new HashMap<String,String>();
    p2.put("foo", "bar");
    assertFalse("zip is needed before match",
        m1.matchesParameters(p2));
    Map<String,String> p3 = new HashMap<String,String>();
    p3.put("thing", "nothing");
    assertFalse("There is no parameter 'thing' in the mapping so should not match",
        m1.matchesParameters(p3));
    
    Map<String,String> p4 = new HashMap<String,String>();
    p4.put("foo", "not-bar");
    p4.put("zip", "button");
    assertFalse("Implied foo parameter value must match to success",
        m1.matchesParameters(p4));
    Map<String,String> p5 = new HashMap<String,String>();
    p5.put("zip", "not-button");
    assertFalse("Value of zip parameter must match to succeed",
        m1.matchesParameters(p5));
  }
  
  public void testMatchesParametersForPattern() {
    Mapping m1 = getMapping("/:foo");
    Map<String,String> p1 = new HashMap<String,String>();
    p1.put("foo", "bar");
    assertTrue("foo is in URL so should match",
        m1.matchesParameters(p1));
    Map<String,String> p2 = new HashMap<String,String>();
    p2.put("foo", "123456");
    assertTrue("foo is in URL so should match",
        m1.matchesParameters(p2));
    Map<String,String> p3 = new HashMap<String,String>();
    p3.put("foo", "bar");
    p3.put("zip", "button");
    assertFalse("zip is not in URL so should not match",
        m1.matchesParameters(p3));
    Map<String,String> p4 = new HashMap<String,String>();
    assertFalse("foo must be in URL so this shouldn't match",
        m1.matchesParameters(p4));
    
    Mapping m2 = getMapping("/test/:abc/zyz/:something");
    Map<String,String> p5 = new HashMap<String,String>();
    p5.put("abc", "test");
    p5.put("something", "blah");
    assertTrue("abc and something are in URL so should match",
        m2.matchesParameters(p5));
    Map<String,String> p6 = new HashMap<String,String>();
    p6.put("abc", "test");
    p6.put("something", "whatever");
    p6.put("nothing", "here");
    assertFalse("nothing is NOT in the URL so shouldn't match",
        m2.matchesParameters(p6));
    Map<String,String> p7 = new HashMap<String,String>();
    p7.put("abc", "test");
    assertFalse("something is in URL but not in parameters",
        m2.matchesParameters(p7));
  }
  
  public void testMatchesParametersWithWildcard() {
    Mapping m1 = getMapping("/:foo/*");
    Map<String,String> p1 = new HashMap<String,String>();
    p1.put("foo", "bar");
    assertTrue("The wildcard should be optional, foo should match",
        m1.matchesParameters(p1));
    Map<String,String> p2 = new HashMap<String,String>();
    p2.put("foo", "test");
    p2.put("zip", "button");
    assertTrue("The wildcard should accept any extra parameters",
        m1.matchesParameters(p2));
    Map<String,String> p3 = new HashMap<String,String>();
    p3.put("foo", "example");
    p3.put("test", "no");
    p3.put("whatever", "something");
    assertTrue("The wildcard should accept any extra parameters",
        m1.matchesParameters(p3));
    Map<String,String> p4 = new HashMap<String,String>();
    p4.put("not-foo", "bar");
    p4.put("test", "thing");
    assertFalse("foo is not in parameters so should not be matched",
        m1.matchesParameters(p4));
    
    //This matches *anything at all*
    Mapping m2 = getMapping("/test/*");
    Map<String,String> p5 = new HashMap<String,String>();
    p5.put("test", "test");
    assertTrue("There's no required parameters so anything should match",
        m2.matchesParameters(p5));
    Map<String,String> p6 = new HashMap<String,String>();
    p6.put("x", "y");
    p6.put("zip", "");
    assertTrue("There's no required parameters so anything should match",
        m2.matchesParameters(p6));
    Map<String,String> p7 = new HashMap<String,String>();
    assertTrue("There's no required parameters so anything should match",
        m2.matchesParameters(p7));
  }
  
  public void testCommitWithImpliedParameters() throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    final HttpServletRequest mockRequest = MockRequestFactory.createRequest(
        mockery, new HashMap<String,String[]>());
    mockery.checking(new Expectations() {{
      ignoring(mockRequest);
    }});
    
    Map<String,String> map1 = new HashMap<String,String>();
    map1.put("foo", "bar");
    map1.put("x", "y");
    Mapping m1 = getMapping("/", map1);
    
    HttpServletRequest request = m1.commit(mockRequest, "utf-8");
    assertEquals("bar", request.getParameter("foo"));
    assertEquals("y", request.getParameter("x"));
  }
  
  public void testCommitWithPattern() throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    
    Mapping m1 = getMapping("/:foo/:zip");
    
    Map<String,String[]> origMap = new HashMap<String,String[]>();
    origMap.put("test", new String[] { "thing" });
    final HttpServletRequest mockRequest1 = MockRequestFactory.createRequest(
        mockery, origMap, "/chris/corbyn", "");
    mockery.checking(new Expectations() {{
      ignoring(mockRequest1);
    }});
    HttpServletRequest request1 = m1.commit(mockRequest1, "utf-8");
    assertEquals("thing", request1.getParameter("test"));
    assertEquals("chris", request1.getParameter("foo"));
    assertEquals("corbyn", request1.getParameter("zip"));
    
    final HttpServletRequest mockRequest2 = MockRequestFactory.createRequest(
        mockery, "/ctx/posts/123", "/ctx");
    mockery.checking(new Expectations() {{
      ignoring(mockRequest2);
    }});
    HttpServletRequest request2 = m1.commit(mockRequest2, "utf-8");
    assertEquals("posts", request2.getParameter("foo"));
    assertEquals("123", request2.getParameter("zip"));
    
    Mapping m2 = getMapping("/base/:action");
    
    final HttpServletRequest mockRequest4 = MockRequestFactory.createRequest(
        mockery, "/location/base/close%2Fnow", "/location");
    mockery.checking(new Expectations() {{
      ignoring(mockRequest4);
    }});
    HttpServletRequest request4 = m2.commit(mockRequest4, "utf-8");
    assertEquals("close/now", request4.getParameter("action"));
  }
  
  public void testCommitWithWildcard() throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    
    Mapping m1 = getMapping("/*");
    
    final HttpServletRequest mockRequest1 = MockRequestFactory.createRequest(
        mockery, "/ctx/foo/bar/zip/button", "/ctx");
    mockery.checking(new Expectations() {{
      ignoring(mockRequest1);
    }});
    HttpServletRequest request1 = m1.commit(mockRequest1, "utf-8");
    assertEquals("bar", request1.getParameter("foo"));
    assertEquals("button", request1.getParameter("zip"));
    
    final HttpServletRequest mockRequest2 = MockRequestFactory.createRequest(
        mockery, "/a/b%20c/d", "");
    mockery.checking(new Expectations() {{
      ignoring(mockRequest2);
    }});
    HttpServletRequest request2 = m1.commit(mockRequest2, "utf-8");
    assertEquals("b c", request2.getParameter("a"));
    assertEquals("", request2.getParameter("d"));
  }
  
  public void testCommitWithCombinedParameters()
      throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    
    Map<String,String> p = new HashMap<String,String>();
    p.put("foo", "bar");
    Mapping m = getMapping("/base/:module/:action/*", p);
    
    final HttpServletRequest mockRequest = MockRequestFactory.createRequest(
        mockery, "/ctxPath/base/forum/createPost/threadId/23/preview/true",
        "/ctxPath");
    mockery.checking(new Expectations() {{
      ignoring(mockRequest);
    }});
    HttpServletRequest request = m.commit(mockRequest, "utf-8");
    assertEquals("bar", request.getParameter("foo"));
    assertEquals("forum", request.getParameter("module"));
    assertEquals("createPost", request.getParameter("action"));
    assertEquals("23", request.getParameter("threadId"));
    assertEquals("true", request.getParameter("preview"));
  }
  
  public void testCreateUriWithImpliedParameters()
      throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    
    Map<String,String> map1 = new HashMap<String,String>();
    map1.put("module", "documentation");
    map1.put("action", "showContents");
    Mapping m1 = getMapping("/docs", map1);
    
    HttpServletRequest mockRequest1 = MockRequestFactory.createRequest(
        mockery, "/ctxPath/foo", "/ctxPath");
    
    Map<String,String> p1 = new HashMap<String,String>();
    p1.put("module", "documentation");
    p1.put("action", "showContents");
    assertEquals("/ctxPath/docs", m1.createUri(mockRequest1, p1, "utf-8"));
    
    Map<String,String> map2 = new HashMap<String,String>();
    map2.put("page", "index");
    Mapping m2 = getMapping("/", map2);
    
    HttpServletRequest mockRequest2 = MockRequestFactory.createRequest(
        mockery, "/", "");
    
    Map<String,String> p2 = new HashMap<String,String>();
    p2.put("page", "index");
    assertEquals("/", m2.createUri(mockRequest2, p2, "utf-8"));
  }
  
  public void testCreateUriWithPattern() throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    
    Mapping m1 = getMapping("/:module/:action");
    HttpServletRequest mockRequest1 = MockRequestFactory.createRequest(
        mockery, "/context/wherever/whatever", "/context");
    Map<String,String> p1 = new HashMap<String,String>();
    p1.put("action", "delete");
    p1.put("module", "users");
    assertEquals("/context/users/delete", m1.createUri(mockRequest1, p1, "utf-8"));
    
    Mapping m2 = getMapping("/docs/:chapter/:page");
    Map<String,String> p2 = new HashMap<String,String>();
    p2.put("chapter", "2");
    p2.put("page", "19");
    assertEquals("/context/docs/2/19", m2.createUri(mockRequest1, p2, "utf-8"));
  }
  
  public void testCreateUriWithWildcard() throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    
    Mapping m1 = getMapping("/help/*");
    HttpServletRequest mockRequest1 = MockRequestFactory.createRequest(
        mockery, "/", "");
    Map<String,String> p1 = new LinkedHashMap<String,String>();
    p1.put("a", "b");
    p1.put("zip", "button");
    p1.put("test", "foo bar");
    assertEquals("/help/a/b/zip/button/test/foo+bar",
        m1.createUri(mockRequest1, p1, "utf-8"));
    
    Mapping m2 = getMapping("/*");
    HttpServletRequest mockRequest2 = MockRequestFactory.createRequest(
        mockery, "/userName/foo", "/userName");
    Map<String,String> p2 = new LinkedHashMap<String,String>();
    p2.put("page", "contact");
    assertEquals("/userName/page/contact",
        m2.createUri(mockRequest2, p2, "utf-8"));
  }
  
  public void testCreateUriWithCombinedParameters()
      throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    
    Map<String,String> map1 = new HashMap<String,String>();
    map1.put("needed", "x");
    Mapping m1 = getMapping("/place/:foo/:bar/*", map1);
    HttpServletRequest mockRequest1 = MockRequestFactory.createRequest(
        mockery, "/user/", "/user");
    
    Map<String,String> p1 = new HashMap<String,String>();
    p1.put("foo", "param1");
    p1.put("bar", "param2");
    p1.put("needed", "x");
    assertEquals("/user/place/param1/param2",
        m1.createUri(mockRequest1, p1, "utf-8"));
    
    Map<String,String> p2 = new LinkedHashMap<String,String>();
    p2.put("foo", "param1");
    p2.put("bar", "param2");
    p2.put("needed", "x");
    p2.put("extra", "stuff");
    assertEquals("/user/place/param1/param2/extra/stuff",
        m1.createUri(mockRequest1, p2, "utf-8"));
  }
  
}
TestBasicMapping.java

Code: Select all

/*
 Tests for the BasicMapping class.
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 */

package org.mayo.requestmapper;

import java.util.Map;


/**
 * Tests for the BasicMapping class.
 * @author Chris Corbyn <chris@w3style.co.uk>
 */
public class TestBasicMapping extends AbstractMappingTest {
  
  /**
   * Get the Mapping instance with the given pattern.
   * @param String pattern
   * @return Mapping
   */
  public Mapping getMapping(String pattern) {
    return new BasicMapping(pattern);
  }
  
  /**
   * Get the Mapping instance with the given pattern and implied parameters.
   * @param Stirng pattern
   * @param Map<String,String> impliedParameters
   * @return Mapping
   */
  public Mapping getMapping(String pattern,
      Map<String,String> impliedParameters) {
    return new BasicMapping(pattern, impliedParameters);
  }
  
}
TestComplexMapping.java

Code: Select all

/*
 Tests for the ComplexMapping class.
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 */

package org.mayo.requestmapper;

import org.jmock.Mockery;
import org.jmock.Expectations;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.io.UnsupportedEncodingException;
import org.mayo.jmock.MockRequestFactory;


/**
 * Tests for the ComplexMapping class.
 * @author Chris Corbyn <chris@w3style.co.uk>
 */
public class TestComplexMapping extends AbstractMappingTest {
  
  /**
   * Get the Mapping instance with the given pattern.
   * @param String pattern
   * @return Mapping
   */
  public Mapping getMapping(String pattern) {
    return new ComplexMapping(pattern);
  }
  
  /**
   * Get the Mapping instance with the given pattern and implied parameters.
   * @param Stirng pattern
   * @param Map<String,String> impliedParameters
   * @return Mapping
   */
  public Mapping getMapping(String pattern,
      Map<String,String> impliedParameters) {
    return new ComplexMapping(pattern, impliedParameters);
  }
  
  public void testMatchesRequestWithSuffix()
      throws UnsupportedEncodingException {
    final String encoding = "utf-8";
    Mockery mockery = new Mockery();
    HttpServletRequest mockRequest1 = MockRequestFactory.createRequest(
        mockery, "/ctx/foo.html", "/ctx");
    ComplexMapping m1 = new ComplexMapping("/foo");
    m1.setSuffix(".html");
    assertTrue("Suffix is .html so full pattern is /foo.html.  Match expected.",
        ((Mapping) m1).matchesRequest(mockRequest1, encoding));
    m1.setSuffix(".php");
    assertFalse("Suffix is .php but request has .html so shouldn't match",
        ((Mapping) m1).matchesRequest(mockRequest1, encoding));
    
    HttpServletRequest mockRequest2 = MockRequestFactory.createRequest(
        mockery, "/docs/introduction.do", "");
    ComplexMapping m2 = new ComplexMapping("/docs/:page");
    m2.setSuffix(".do");
    assertTrue("Suffix is .do so should match",
        ((Mapping) m2).matchesRequest(mockRequest2, encoding));
    
    HttpServletRequest mockRequest3 = MockRequestFactory.createRequest(
        mockery, "/docs/introduction", "");
    assertFalse(".do suffix is missing so should not match",
        ((Mapping) m2).matchesRequest(mockRequest3, encoding));
  }
  
  public void testCommitWithSuffix() throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    
    ComplexMapping m1 = new ComplexMapping("/:x");
    m1.setSuffix(".do");
    
    final HttpServletRequest mockRequest1 = MockRequestFactory.createRequest(
        mockery, "/ctx/foo.do", "/ctx");
    mockery.checking(new Expectations() {{
      ignoring(mockRequest1);
    }});
    HttpServletRequest request1 = m1.commit(mockRequest1, "utf-8");
    assertEquals("foo", request1.getParameter("x"));
    
    final HttpServletRequest mockRequest2 = MockRequestFactory.createRequest(
        mockery, "/a%20b.do", "");
    mockery.checking(new Expectations() {{
      ignoring(mockRequest2);
    }});
    HttpServletRequest request2 = m1.commit(mockRequest2, "utf-8");
    assertEquals("a b", request2.getParameter("x"));
  }
  
  public void testMatchesParametersWithSuffix() {
    Mockery mockery = new Mockery();
    ComplexMapping m1 = new ComplexMapping("/:x");
    m1.setSuffix(".html");
    Map<String,String> p1 = new HashMap<String,String>();
    p1.put("x", "whatever");
    assertTrue("x is in params so should match",
        ((Mapping) m1).matchesParameters(p1));
    Map<String,String> p2 = new HashMap<String,String>();
    p2.put("x", "test");
    p2.put("blah", "blah");
    assertFalse("blah is not in the request so should not match",
        ((Mapping) m1).matchesParameters(p2));
  }
  
  public void testCreateUriWithSuffix() throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    final String encoding = "iso-8859-1";
    final HttpServletRequest mockRequest1 = MockRequestFactory.createRequest(
        mockery, "/foo/bar.html", "/foo");
    ComplexMapping m1 = new ComplexMapping("/test");
    m1.setSuffix(".html");
    Map<String,String> p1 = new HashMap<String,String>();
    assertEquals("/foo/test.html",
        ((Mapping) m1).createUri(mockRequest1, p1, encoding));
    
    ComplexMapping m2 = new ComplexMapping("/:foo/*");
    m2.setSuffix(".do");
    Map<String,String> p2 = new LinkedHashMap<String,String>();
    p2.put("foo", "bar");
    p2.put("zip", "button");
    p2.put("item", "1234");
    assertEquals("/foo/bar/zip/button/item/1234.do",
        ((Mapping) m2).createUri(mockRequest1, p2, encoding));
  }
  
  public void testMatchesRequestWithPrefix()
      throws UnsupportedEncodingException {
    final String encoding = "utf-8";
    Mockery mockery = new Mockery();
    HttpServletRequest mockRequest1 = MockRequestFactory.createRequest(
        mockery, "/ctx/pre/location", "/ctx");
    ComplexMapping m1 = new ComplexMapping("/location");
    m1.setPrefix("/pre");
    assertTrue("Prefix is /pre so full pattern is /pre/location.  Match expected.",
        ((Mapping) m1).matchesRequest(mockRequest1, encoding));
    m1.setSuffix("/path");
    assertFalse("Prefix is /path but request has /pre so shouldn't match",
        ((Mapping) m1).matchesRequest(mockRequest1, encoding));
    
    HttpServletRequest mockRequest2 = MockRequestFactory.createRequest(
        mockery, "/docs/introduction", "");
    ComplexMapping m2 = new ComplexMapping("/:page");
    m2.setPrefix("/docs");
    assertTrue("Prefix is /docs so should match",
        ((Mapping) m2).matchesRequest(mockRequest2, encoding));
    
    HttpServletRequest mockRequest3 = MockRequestFactory.createRequest(
        mockery, "/introduction", "");
    assertFalse("/docs prefix is missing so should not match",
        ((Mapping) m2).matchesRequest(mockRequest3, encoding));
  }
  
  public void testCommitWithPrefix() throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    
    ComplexMapping m1 = new ComplexMapping("/:x");
    m1.setPrefix("/test");
    
    final HttpServletRequest mockRequest1 = MockRequestFactory.createRequest(
        mockery, "/ctx/test/foo", "/ctx");
    mockery.checking(new Expectations() {{
      ignoring(mockRequest1);
    }});
    HttpServletRequest request1 = m1.commit(mockRequest1, "utf-8");
    assertEquals("foo", request1.getParameter("x"));
    
    final HttpServletRequest mockRequest2 = MockRequestFactory.createRequest(
        mockery, "/test/a%20b", "");
    mockery.checking(new Expectations() {{
      ignoring(mockRequest2);
    }});
    HttpServletRequest request2 = m1.commit(mockRequest2, "utf-8");
    assertEquals("a b", request2.getParameter("x"));
  }
  
  public void testMatchesParametersWithPrefix() {
    Mockery mockery = new Mockery();
    ComplexMapping m1 = new ComplexMapping("/:x");
    m1.setPrefix("/bar");
    Map<String,String> p1 = new HashMap<String,String>();
    p1.put("x", "whatever");
    assertTrue("x is in params so should match", m1.matchesParameters(p1));
    Map<String,String> p2 = new HashMap<String,String>();
    p2.put("x", "test");
    p2.put("blah", "blah");
    assertFalse("blah is not in the request so should not match",
        m1.matchesParameters(p2));
  }
  
  public void testCreateUriWithPrefix() throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    final String encoding = "iso-8859-1";
    final HttpServletRequest mockRequest1 = MockRequestFactory.createRequest(
        mockery, "/foo/bar.html", "/foo");
    ComplexMapping m1 = new ComplexMapping("/test");
    m1.setPrefix("/egg");
    Map<String,String> p1 = new HashMap<String,String>();
    assertEquals("/foo/egg/test",
        ((Mapping) m1).createUri(mockRequest1, p1, encoding));
    
    ComplexMapping m2 = new ComplexMapping("/:foo/*");
    m2.setPrefix("/bacon/eggs");
    Map<String,String> p2 = new LinkedHashMap<String,String>();
    p2.put("foo", "bar");
    p2.put("zip", "button");
    p2.put("item", "1234");
    assertEquals("/foo/bacon/eggs/bar/zip/button/item/1234",
        ((Mapping) m2).createUri(mockRequest1, p2, encoding));
  }
  
  public void testMatchesRequestWithRequirements()
      throws UnsupportedEncodingException {
    final String encoding = "utf-8";
    Mockery mockery = new Mockery();
    HttpServletRequest mockRequest1 = MockRequestFactory.createRequest(
        mockery, "/ctx/page/23", "/ctx");
    ComplexMapping m1 = new ComplexMapping("/page/:num");
    Map<String,String> req1 = new HashMap<String,String>();
    req1.put("num", "\\d+");
    m1.setRequirements(req1);
    assertTrue("Requirement is \\d+ and :num is a number.  Match expected.",
        ((Mapping) m1).matchesRequest(mockRequest1, encoding));
    Map<String,String> req2 = new HashMap<String,String>();
    req2.put("num", "[a-z]+");
    m1.setRequirements(req2);
    assertFalse("Requirement is [a-z]+ but :num is 23 so shouldn't match",
        ((Mapping) m1).matchesRequest(mockRequest1, encoding));
    
    HttpServletRequest mockRequest2 = MockRequestFactory.createRequest(
        mockery, "/introduction/123", "");
    ComplexMapping m2 = new ComplexMapping("/:chapter/:page");
    Map<String,String> req3 = new HashMap<String,String>();
    req3.put("page", "\\d+");
    req3.put("chapter", "[a-z]+");
    m2.setRequirements(req3);
    assertTrue("Requirements are satisfied so should match",
        ((Mapping) m2).matchesRequest(mockRequest2, encoding));
    
    HttpServletRequest mockRequest3 = MockRequestFactory.createRequest(
        mockery, "/introduction/one", "");
    assertFalse(":page requirement is wrong so should not match",
        ((Mapping) m2).matchesRequest(mockRequest3, encoding));
  }
  
  public void testMatchesParametersWithRequirements() {
    Mockery mockery = new Mockery();
    ComplexMapping m1 = new ComplexMapping("/:foo/:bar");
    Map<String,String> req1 = new HashMap<String,String>();
    req1.put("bar", "\\d+");
    m1.setRequirements(req1);
    
    Map<String,String> p1 = new HashMap<String,String>();
    p1.put("foo", "thing");
    p1.put("bar", "2");
    assertTrue("bar is a number so should match",
        ((Mapping) m1).matchesParameters(p1));
    Map<String,String> p2 = new HashMap<String,String>();
    p2.put("foo", "thing");
    p2.put("bar", "nothing");
    assertFalse("bar is not a number so shouldn't match",
        ((Mapping) m1).matchesParameters(p2));
    
    Map<String,String> p3 = new HashMap<String,String>();
    p3.put("bar", "2");
    assertFalse("foo is missing so shouldn't match",
        ((Mapping) m1).matchesParameters(p3));
  }
  
}
TestMutableRequestWrapper.java

Code: Select all

/*
 Tests for the RequestWrapper class.
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 */

package org.mayo.requestmapper;

import junit.framework.TestCase;
import org.jmock.Mockery;
import org.jmock.Expectations;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import org.mayo.jmock.MockRequestFactory;


/**
 * Tests for the MutableRequestWrapper class.
 * @author Chris Corbyn <chris@w3style.co.uk>
 */
public class TestMutableRequestWrapper extends TestCase {
  
  public void testOriginalParameterNames() {
    Mockery mockery = new Mockery();
    
    Map<String,String[]> map = new HashMap<String,String[]>();
    map.put("foo", new String[] { "bar" });
    map.put("zip", new String[] { "button" });
    
    final HttpServletRequest mockRequest = MockRequestFactory.createRequest(
        mockery, map);
    mockery.checking(new Expectations() {{
      ignoring(mockRequest);
    }});
    Enumeration<String> mockEnum = mockRequest.getParameterNames();
    assertTrue("Mock must have foo param name", mockEnum.hasMoreElements());
    assertEquals("foo", mockEnum.nextElement());
    assertTrue("Mock must have zip param name", mockEnum.hasMoreElements());
    assertEquals("zip", mockEnum.nextElement());
    
    MutableRequest request = new MutableRequestWrapper(mockRequest);
    Enumeration<String> testEnum = request.getParameterNames();
    assertTrue("No foo param name?", testEnum.hasMoreElements());
    assertEquals("foo", testEnum.nextElement());
    assertTrue("No zip param name?", testEnum.hasMoreElements());
    assertEquals("zip", testEnum.nextElement());
  }
  
  public void testOriginalParameterValues() {
    Mockery mockery = new Mockery();
    
    Map<String,String[]> map = new HashMap<String,String[]>();
    map.put("foo", new String[] { "bar" });
    map.put("zip", new String[] { "button" });
    
    final HttpServletRequest mockRequest = MockRequestFactory.createRequest(
        mockery, map);
    mockery.checking(new Expectations() {{
      ignoring(mockRequest);
    }});
    String[] mockFooValues = mockRequest.getParameterValues("foo");
    String[] mockZipValues = mockRequest.getParameterValues("zip");
    assertEquals(1, mockFooValues.length);
    assertEquals(1, mockZipValues.length);
    assertEquals("bar", mockFooValues[0]);
    assertEquals("button", mockZipValues[0]);
    
    MutableRequest request = new MutableRequestWrapper(mockRequest);
    String[] testFooValues = mockRequest.getParameterValues("foo");
    String[] testZipValues = mockRequest.getParameterValues("zip");
    assertEquals(mockFooValues.length, testFooValues.length);
    assertEquals(mockZipValues.length, testZipValues.length);
    assertEquals(mockFooValues[0], testFooValues[0]);
    assertEquals(mockZipValues[0], testZipValues[0]);
  }
  
  public void testOriginalParameters() {
    Mockery mockery = new Mockery();
    
    Map<String,String[]> map = new HashMap<String,String[]>();
    map.put("foo", new String[] { "bar" });
    map.put("zip", new String[] { "button" });
    
    final HttpServletRequest mockRequest = MockRequestFactory.createRequest(
        mockery, map);
    mockery.checking(new Expectations() {{
      ignoring(mockRequest);
    }});
    assertEquals("bar", mockRequest.getParameter("foo"));
    assertEquals("button", mockRequest.getParameter("zip"));
    
    MutableRequest request = new MutableRequestWrapper(mockRequest);
    assertEquals(mockRequest.getParameter("foo"), request.getParameter("foo"));
    assertEquals(mockRequest.getParameter("zip"), request.getParameter("zip"));
  }
  
  public void testParameterMap() {
    Mockery mockery = new Mockery();
    
    Map<String,String[]> map = new HashMap<String,String[]>();
    map.put("foo", new String[] { "bar" });
    map.put("zip", new String[] { "button" });
    
    final HttpServletRequest mockRequest = MockRequestFactory.createRequest(
        mockery, map);
    mockery.checking(new Expectations() {{
      ignoring(mockRequest);
    }});
    assertEquals(map, mockRequest.getParameterMap());
    
    MutableRequest request = new MutableRequestWrapper(mockRequest);
    assertEquals(mockRequest.getParameterMap(), request.getParameterMap());
  }
  
  public void testParameterMapIsImmutable() {
    Mockery mockery = new Mockery();
    
    Map<String,String[]> map = new HashMap<String,String[]>();
    map.put("foo", new String[] { "bar" });
    map.put("zip", new String[] { "button" });
    
    final HttpServletRequest mockRequest = MockRequestFactory.createRequest(
        mockery, map);
    mockery.checking(new Expectations() {{
      ignoring(mockRequest);
    }});
    
    MutableRequest request = new MutableRequestWrapper(mockRequest);
    request.getParameterMap().put("wasntThereBefore",
        new String[] { "isThereNow" });
    assertFalse("Should not be possible to inject into the Map",
        request.getParameterMap().containsKey("wasntThereBefore"));
  }
  
  public void testSetParameterValues() {
    Mockery mockery = new Mockery();
    
    Map<String,String[]> map = new HashMap<String,String[]>();
    map.put("test", new String[] { "thing" });
    
    final HttpServletRequest mockRequest = MockRequestFactory.createRequest(
        mockery, map);
    mockery.checking(new Expectations() {{
      ignoring(mockRequest);
    }});
    
    MutableRequest request = new MutableRequestWrapper(mockRequest);
    String[] barValues = { "x" };
    
    request.setParameterValues("bar", barValues);
    Set<String> wanted1 = new HashSet<String>();
    wanted1.add("test");
    wanted1.add("bar");
    Set<String> got1 = new HashSet<String>();
    Enumeration<String> e1 = request.getParameterNames();
    while (e1.hasMoreElements()) {
      got1.add(e1.nextElement());
    }
    assertEquals(wanted1, got1);
    
    assertEquals("thing", request.getParameter("test"));
    assertEquals("x", request.getParameter("bar"));
    
    String[] testBarValues = request.getParameterValues("bar");
    assertEquals(1, testBarValues.length);
    assertEquals("x", testBarValues[0]);
    
    String[] testTestValues = request.getParameterValues("test");
    assertEquals(1, testTestValues.length);
    assertEquals("thing", testTestValues[0]);
    
    Map<String,String[]> testMap = request.getParameterMap();
    assertEquals(2, testMap.size());
    assertTrue(testMap.containsKey("test"));
    assertTrue(testMap.containsKey("bar"));
    
    String[] testMapTestValues = testMap.get("test");
    assertEquals(1, testMapTestValues.length);
    assertEquals("thing", testMapTestValues[0]);
    
    String[] testMapBarValues = testMap.get("bar");
    assertEquals(1, testMapBarValues.length);
    assertEquals("x", testMapBarValues[0]);
  }
  
  public void testSetParameterValuesMultiCall() {
    Mockery mockery = new Mockery();
    
    Map<String,String[]> map = new HashMap<String,String[]>();
    map.put("test", new String[] { "thing" });
    
    final HttpServletRequest mockRequest = MockRequestFactory.createRequest(
        mockery, map);
    mockery.checking(new Expectations() {{
      ignoring(mockRequest);
    }});
    
    MutableRequest request = new MutableRequestWrapper(mockRequest);
    String[] barValues = { "x" };
    request.setParameterValues("bar", barValues);
    
    String[] fooValues = { "y" };
    request.setParameterValues("foo", fooValues);
    
    Set<String> wanted1 = new HashSet<String>();
    wanted1.add("test");
    wanted1.add("bar");
    wanted1.add("foo");
    Set<String> got1 = new HashSet<String>();
    Enumeration<String> e1 = request.getParameterNames();
    while (e1.hasMoreElements()) {
      got1.add(e1.nextElement());
    }
    assertEquals(wanted1, got1);
    
    assertEquals("thing", request.getParameter("test"));
    assertEquals("x", request.getParameter("bar"));
    assertEquals("y", request.getParameter("foo"));
    
    String[] testBarValues = request.getParameterValues("bar");
    assertEquals(1, testBarValues.length);
    assertEquals("x", testBarValues[0]);
    
    String[] testFooValues = request.getParameterValues("foo");
    assertEquals(1, testFooValues.length);
    assertEquals("y", testFooValues[0]);
    
    String[] testTestValues = request.getParameterValues("test");
    assertEquals(1, testTestValues.length);
    assertEquals("thing", testTestValues[0]);
    
    Map<String,String[]> testMap = request.getParameterMap();
    assertEquals(3, testMap.size());
    assertTrue(testMap.containsKey("test"));
    assertTrue(testMap.containsKey("bar"));
    assertTrue(testMap.containsKey("foo"));
    
    String[] testMapTestValues = testMap.get("test");
    assertEquals(1, testMapTestValues.length);
    assertEquals("thing", testMapTestValues[0]);
    
    String[] testMapBarValues = testMap.get("bar");
    assertEquals(1, testMapBarValues.length);
    assertEquals("x", testMapBarValues[0]);
    
    String[] testMapFooValues = testMap.get("foo");
    assertEquals(1, testMapFooValues.length);
    assertEquals("y", testMapFooValues[0]);
  }
  
  public void testSetParmameter() {
    Mockery mockery = new Mockery();
    
    Map<String,String[]> map = new HashMap<String,String[]>();
    map.put("test", new String[] { "thing" });
    
    final HttpServletRequest mockRequest = MockRequestFactory.createRequest(
        mockery, map);
    mockery.checking(new Expectations() {{
      ignoring(mockRequest);
    }});
    
    MutableRequest request = new MutableRequestWrapper(mockRequest);
    
    request.setParameter("bar", "x");
    Set<String> wanted1 = new HashSet<String>();
    wanted1.add("test");
    wanted1.add("bar");
    Set<String> got1 = new HashSet<String>();
    Enumeration<String> e1 = request.getParameterNames();
    while (e1.hasMoreElements()) {
      got1.add(e1.nextElement());
    }
    assertEquals(wanted1, got1);
    
    assertEquals("thing", request.getParameter("test"));
    assertEquals("x", request.getParameter("bar"));
    
    String[] testBarValues = request.getParameterValues("bar");
    assertEquals(1, testBarValues.length);
    assertEquals("x", testBarValues[0]);
    
    String[] testTestValues = request.getParameterValues("test");
    assertEquals(1, testTestValues.length);
    assertEquals("thing", testTestValues[0]);
    
    Map<String,String[]> testMap = request.getParameterMap();
    assertEquals(2, testMap.size());
    assertTrue(testMap.containsKey("test"));
    assertTrue(testMap.containsKey("bar"));
    
    String[] testMapTestValues = testMap.get("test");
    assertEquals(1, testMapTestValues.length);
    assertEquals("thing", testMapTestValues[0]);
    
    String[] testMapBarValues = testMap.get("bar");
    assertEquals(1, testMapBarValues.length);
    assertEquals("x", testMapBarValues[0]);
  }
  
  public void testSetParameterMultiCall() {
    Mockery mockery = new Mockery();
    
    Map<String,String[]> map = new HashMap<String,String[]>();
    map.put("test", new String[] { "thing" });
    
    final HttpServletRequest mockRequest = MockRequestFactory.createRequest(
        mockery, map);
    mockery.checking(new Expectations() {{
      ignoring(mockRequest);
    }});
    
    MutableRequest request = new MutableRequestWrapper(mockRequest);
    request.setParameter("bar", "x");
    request.setParameter("foo", "y");
    
    Set<String> wanted1 = new HashSet<String>();
    wanted1.add("test");
    wanted1.add("bar");
    wanted1.add("foo");
    Set<String> got1 = new HashSet<String>();
    Enumeration<String> e1 = request.getParameterNames();
    while (e1.hasMoreElements()) {
      got1.add(e1.nextElement());
    }
    assertEquals(wanted1, got1);
    
    assertEquals("thing", request.getParameter("test"));
    assertEquals("x", request.getParameter("bar"));
    assertEquals("y", request.getParameter("foo"));
    
    String[] testBarValues = request.getParameterValues("bar");
    assertEquals(1, testBarValues.length);
    assertEquals("x", testBarValues[0]);
    
    String[] testFooValues = request.getParameterValues("foo");
    assertEquals(1, testFooValues.length);
    assertEquals("y", testFooValues[0]);
    
    String[] testTestValues = request.getParameterValues("test");
    assertEquals(1, testTestValues.length);
    assertEquals("thing", testTestValues[0]);
    
    Map<String,String[]> testMap = request.getParameterMap();
    assertEquals(3, testMap.size());
    assertTrue(testMap.containsKey("test"));
    assertTrue(testMap.containsKey("bar"));
    assertTrue(testMap.containsKey("foo"));
    
    String[] testMapTestValues = testMap.get("test");
    assertEquals(1, testMapTestValues.length);
    assertEquals("thing", testMapTestValues[0]);
    
    String[] testMapBarValues = testMap.get("bar");
    assertEquals(1, testMapBarValues.length);
    assertEquals("x", testMapBarValues[0]);
    
    String[] testMapFooValues = testMap.get("foo");
    assertEquals(1, testMapFooValues.length);
    assertEquals("y", testMapFooValues[0]);
  }
  
}
TestRequestMapper.java

Code: Select all

/*
 Tests for the RequestMapper class.
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 */

package org.mayo.requestmapper;

import junit.framework.TestCase;
import org.jmock.Mockery;
import org.jmock.Expectations;
import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import org.mayo.jmock.MockRequestFactory;


/**
 * Tests for the RequestMapper class.
 * @author Chris Corbyn <chris@w3style.co.uk>
 */
public class TestRequestMapper extends TestCase {
  
  public void testMappingSelectionForCommit()
      throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    final String encoding = "utf-8";
    
    final HttpServletRequest req1 = mockery.mock(HttpServletRequest.class);
    mockery.checking(new Expectations() {{
      ignoring(req1);
    }});
    
    final Mapping mapping1 = mockery.mock(Mapping.class);
    mockery.checking(new Expectations() {{
      atLeast(1).of(mapping1).matchesRequest(req1, encoding);
          will(returnValue(false));
      never(mapping1).commit(req1, encoding);
      ignoring(mapping1);
    }});
    
    final Mapping mapping2 = mockery.mock(Mapping.class);
    mockery.checking(new Expectations() {{
      atLeast(1).of(mapping2).matchesRequest(req1, encoding);
          will(returnValue(true));
      one(mapping2).commit(req1, encoding);
      ignoring(mapping2);
    }});
    
    Mapper mapper = new RequestMapper();
    mapper.addMapping(mapping1);
    mapper.addMapping(mapping2);
    mapper.commit(req1, encoding);
    
    mockery.assertIsSatisfied();
  }
  
  public void testMappingSelectionUsesLengthAsAFactorInCommit()
      throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    final String encoding = "iso-8859-1";
    
    final HttpServletRequest req1 = mockery.mock(HttpServletRequest.class);
    mockery.checking(new Expectations() {{
      ignoring(req1);
    }});
    
    final Mapping mapping1 = mockery.mock(Mapping.class);
    mockery.checking(new Expectations() {{
      atLeast(1).of(mapping1).matchesRequest(req1, encoding);
          will(returnValue(false));
      allowing(mapping1).getLength(); will(returnValue(1));
      never(mapping1).commit(req1, encoding);
      ignoring(mapping1);
    }});
    
    //Both mapping1 and mapping2 match, but mapping3 is longer
    final Mapping mapping2 = mockery.mock(Mapping.class);
    mockery.checking(new Expectations() {{
      atLeast(1).of(mapping2).matchesRequest(req1, encoding);
          will(returnValue(true));
      atLeast(1).of(mapping2).getLength(); will(returnValue(5));
      never(mapping2).commit(req1, encoding);
      ignoring(mapping2);
    }});
    
    final Mapping mapping3 = mockery.mock(Mapping.class);
    mockery.checking(new Expectations() {{
      atLeast(1).of(mapping3).matchesRequest(req1, encoding);
          will(returnValue(true));
      atLeast(1).of(mapping3).getLength(); will(returnValue(8));
      one(mapping3).commit(req1, encoding);
      ignoring(mapping3);
    }});
    
    final Mapping mapping4 = mockery.mock(Mapping.class);
    mockery.checking(new Expectations() {{
      atLeast(1).of(mapping4).matchesRequest(req1, encoding);
          will(returnValue(false));
      allowing(mapping4).getLength(); will(returnValue(0));
      never(mapping4).commit(req1, encoding);
      ignoring(mapping4);
    }});
    
    Mapper mapper = new RequestMapper();
    mapper.addMapping(mapping1);
    mapper.addMapping(mapping2);
    mapper.addMapping(mapping3);
    mapper.addMapping(mapping4);
    mapper.commit(req1, encoding);
    
    mockery.assertIsSatisfied();
  }
  
  public void testMappingSelectionForCreateUri()
      throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    final String encoding = "utf-8";
    final HttpServletRequest req1 = mockery.mock(HttpServletRequest.class);
    mockery.checking(new Expectations() {{
      ignoring(req1);
    }});
    final Map<String,String> map1 = new HashMap<String,String>();
    final Mapping mapping1 = mockery.mock(Mapping.class);
    mockery.checking(new Expectations() {{
      atLeast(1).of(mapping1).matchesParameters(map1);
          will(returnValue(false));
      never(mapping1).createUri(req1, map1, encoding);
      ignoring(mapping1);
    }});
    final Mapping mapping2 = mockery.mock(Mapping.class);
    mockery.checking(new Expectations() {{
      atLeast(1).of(mapping2).matchesParameters(map1);
          will(returnValue(true));
      one(mapping2).createUri(req1, map1, encoding);
      ignoring(mapping2);
    }});
    Mapper mapper = new RequestMapper();
    mapper.addMapping(mapping1);
    mapper.addMapping(mapping2);
    mapper.createUri(req1, map1, encoding);
    mockery.assertIsSatisfied();
  }
  
  public void testMappingSelectionUsesLengthAsAFactorInCreateUri()
      throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    final String encoding = "us-ascii";
    final HttpServletRequest req1 = mockery.mock(HttpServletRequest.class);
    mockery.checking(new Expectations() {{
      ignoring(req1);
    }});
    final Map<String,String> map1 = new HashMap<String,String>();
    final Mapping mapping1 = mockery.mock(Mapping.class);
    mockery.checking(new Expectations() {{
      atLeast(1).of(mapping1).matchesParameters(map1);
          will(returnValue(true));
      atLeast(1).of(mapping1).getLength(); will(returnValue(11));
      one(mapping1).createUri(req1, map1, encoding);
      ignoring(mapping1);
    }});
    //10 is less than 11 but both match
    final Mapping mapping2 = mockery.mock(Mapping.class);
    mockery.checking(new Expectations() {{
      atLeast(1).of(mapping2).matchesParameters(map1);
          will(returnValue(true));
      atLeast(1).of(mapping2).getLength(); will(returnValue(10));
      never(mapping2).createUri(req1, map1, encoding);
      ignoring(mapping2);
    }});
    
    Mapper mapper = new RequestMapper();
    mapper.addMapping(mapping1);
    mapper.addMapping(mapping2);
    mapper.createUri(req1, map1, encoding);
    
    mockery.assertIsSatisfied();
  }
  
  public void testMapperProxiesReturnsOnCommit()
      throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    final String encoding = "thai-874";
    final HttpServletRequest req1 = mockery.mock(HttpServletRequest.class);
    mockery.checking(new Expectations() {{
      allowing(req1).getParameter("test"); will(returnValue("request1"));
      ignoring(req1);
    }});
    final HttpServletRequest req2 = mockery.mock(HttpServletRequest.class);
    mockery.checking(new Expectations() {{
      allowing(req2).getParameter("test"); will(returnValue("request2"));
      ignoring(req2);
    }});
    final Mapping mapping = mockery.mock(Mapping.class);
    mockery.checking(new Expectations() {{
      atLeast(1).of(mapping).matchesRequest(req1, encoding);
          will(returnValue(true));
      one(mapping).commit(req1, encoding);
          will(returnValue(req2));
      ignoring(mapping);
    }});
    Mapper mapper = new RequestMapper();
    mapper.addMapping(mapping);
    assertEquals("request1", req1.getParameter("test"));
    HttpServletRequest testReq = mapper.commit(req1, encoding);
    assertEquals("request2", testReq.getParameter("test"));
    mockery.assertIsSatisfied();
  }
  
  public void testMapperProxiesReturnsOnCreateUri()
      throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    final String encoding = "utf-8";
    final HttpServletRequest req1 = mockery.mock(HttpServletRequest.class);
    mockery.checking(new Expectations() {{
      ignoring(req1);
    }});
    final Map<String,String> params = new HashMap<String,String>();
    final Mapping mapping = mockery.mock(Mapping.class);
    mockery.checking(new Expectations() {{
      atLeast(1).of(mapping).matchesParameters(params);
          will(returnValue(true));
      one(mapping).createUri(req1, params, encoding);
          will(returnValue("/foo/bar"));
      ignoring(mapping);
    }});
    Mapper mapper = new RequestMapper();
    mapper.addMapping(mapping);
    String uri = mapper.createUri(req1, params, encoding);
    assertEquals("/foo/bar", uri);
    mockery.assertIsSatisfied();
  }
  
  public void testOriginalRequestIsReturnedOnNoMatch()
      throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    final String encoding = "thai-874";
    final HttpServletRequest req1 = mockery.mock(HttpServletRequest.class);
    mockery.checking(new Expectations() {{
      allowing(req1).getParameter("test"); will(returnValue("request1"));
      ignoring(req1);
    }});
    final HttpServletRequest req2 = mockery.mock(HttpServletRequest.class);
    mockery.checking(new Expectations() {{
      allowing(req2).getParameter("test"); will(returnValue("request2"));
      ignoring(req2);
    }});
    final Mapping mapping = mockery.mock(Mapping.class);
    mockery.checking(new Expectations() {{
      atLeast(1).of(mapping).matchesRequest(req1, encoding);
          will(returnValue(false));
      never(mapping).commit(req1, encoding);
      ignoring(mapping);
    }});
    Mapper mapper = new RequestMapper();
    mapper.addMapping(mapping);
    assertEquals("request1", req1.getParameter("test"));
    HttpServletRequest testReq = mapper.commit(req1, encoding);
    assertEquals("request1", testReq.getParameter("test"));
    mockery.assertIsSatisfied();
  }
  
  public void testUncleanUriIsReturnedOnNoMatch()
      throws UnsupportedEncodingException {
    Mockery mockery = new Mockery();
    final String encoding = "utf-8";
    final HttpServletRequest req1 = MockRequestFactory.createRequest(mockery,
        "/test/foo", "/test");
    mockery.checking(new Expectations() {{
      ignoring(req1);
    }});
    final Map<String,String> params = new LinkedHashMap<String,String>();
    params.put("foo", "bar");
    params.put("zip", "button");
    final Mapping mapping = mockery.mock(Mapping.class);
    mockery.checking(new Expectations() {{
      atLeast(1).of(mapping).matchesParameters(params);
          will(returnValue(false));
      never(mapping).createUri(req1, params, encoding);
      ignoring(mapping);
    }});
    Mapper mapper = new RequestMapper();
    mapper.addMapping(mapping);
    String uri = mapper.createUri(req1, params, encoding);
    assertEquals("/test/?foo=bar&zip=button", uri);
    mockery.assertIsSatisfied();
  }
  
}