/**
* This file Copyright (c) 2010-2012 Magnolia International
* Ltd. (http://www.magnolia-cms.com). All rights reserved.
*
*
* This file is dual-licensed under both the Magnolia
* Network Agreement and the GNU General Public License.
* You may elect to use one or the other of these licenses.
*
* This file is distributed in the hope that it will be
* useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
* Redistribution, except as permitted by whichever of the GPL
* or MNA you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or
* modify this file under the terms of the GNU General
* Public License, Version 3, as published by the Free Software
* Foundation. You should have received a copy of the GNU
* General Public License, Version 3 along with this program;
* if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 2. For the Magnolia Network Agreement (MNA), this file
* and the accompanying materials are made available under the
* terms of the MNA which accompanies this distribution, and
* is available at http://www.magnolia-cms.com/mna.html
*
* Any modifications to this file must keep this entire header
* intact.
*
*/
package info.magnolia.cms.filters;
import info.magnolia.cms.util.SimpleUrlPattern;
import info.magnolia.context.MgnlContext;
import info.magnolia.context.WebContext;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A URI mapping as configured for filters and servlets.
* @version $Id$
*
*/
public class Mapping {
private static Logger log = LoggerFactory.getLogger(Mapping.class);
private static final String METACHARACTERS = "([\\^\\(\\)\\{\\}\\[\\]*$+])";
protected static String escapeMetaCharacters(String str) {
return str.replaceAll(METACHARACTERS, "\\\\$1");
}
protected Collection<Pattern> mappings = new ArrayList<Pattern>();
public MatchingResult match(HttpServletRequest request) {
Matcher matcher = findMatcher(request);
boolean matches = matcher != null;
int matchingEndPosition = determineMatchingEnd(matcher);
return new MatchingResult(matcher, matches, matchingEndPosition);
}
/**
* Determines the index of the first pathInfo character. If the uri does not match any mapping
* this method returns -1.
*/
private int determineMatchingEnd(Matcher matcher) {
if (matcher == null) {
return -1;
}
if (matcher.groupCount() > 0) {
return matcher.end(1);
}
return matcher.end();
}
private Matcher findMatcher(HttpServletRequest request) {
String uri = null;
WebContext ctx = MgnlContext.getWebContextOrNull();
if (ctx != null) {
uri = ctx.getAggregationState().getCurrentURI();
}
if (uri == null) {
// the web context is not available during installation
uri = StringUtils.substringAfter(request.getRequestURI(), request.getContextPath());
}
return findMatcher(uri);
}
private Matcher findMatcher(String uri) {
for (Iterator iter = mappings.iterator(); iter.hasNext();) {
final Matcher matcher = ((Pattern) iter.next()).matcher(uri);
if (matcher.find()) {
return matcher;
}
}
return null;
}
public Collection<Pattern> getMappings() {
return mappings;
}
public void setMappings(Collection<String> mappings) {
for (String mapping : mappings) {
this.addMapping(mapping);
}
}
/**
* See SRV.11.2 Specification of Mappings in the Servlet Specification for the syntax of
* mappings. Additionally, you can also use plain regular expressions to map your servlets, by
* prefix the mapping by "regex:". (in which case anything in the request url following the
* expression's match will be the pathInfo - if your pattern ends with a $, extra pathInfo won't
* match)
*/
public void addMapping(final String mapping) {
final String pattern;
// we're building a Pattern with 3 groups: (1) servletPath (2) ignored (3) pathInfo
if (isDefaultMapping(mapping)) {
// the mapping is exactly '/*', the servlet path should be
// an empty string and everything else should be the path info
pattern = "^()(/)(" + SimpleUrlPattern.MULTIPLE_CHAR_PATTERN + ")";
}
else if (isPathMapping(mapping)) {
// the pattern ends with /*, escape out metacharacters for
// use in a regex, and replace the ending * with MULTIPLE_CHAR_PATTERN
final String mappingWithoutSuffix = StringUtils.removeEnd(mapping, "/*");
pattern = "^(" + escapeMetaCharacters(mappingWithoutSuffix) + ")(/)(" + SimpleUrlPattern.MULTIPLE_CHAR_PATTERN + ")";
}
else if (isExtensionMapping(mapping)) {
// something like '*.jsp', everything should be the servlet path
// and the path info should be null
final String regexedMapping = StringUtils.replace(mapping, "*.", SimpleUrlPattern.MULTIPLE_CHAR_PATTERN + "\\.");
pattern = "^(" + regexedMapping + ")$";
}
else if (isRegexpMapping(mapping)) {
final String mappingWithoutPrefix = StringUtils.removeStart(mapping, "regex:");
pattern = "^(" + mappingWithoutPrefix + ")($|/)(" + SimpleUrlPattern.MULTIPLE_CHAR_PATTERN + ")";
}
else {
// just literal text, ensure metacharacters are escaped, and that only
// the exact string is matched.
pattern = "^(" + escapeMetaCharacters(mapping) + ")$";
}
log.debug("Adding new mapping for {}", mapping);
mappings.add(Pattern.compile(pattern));
}
/**
* This is order specific, this method should not be called until after the isDefaultMapping()
* method else it will return true for a default mapping.
*/
private boolean isPathMapping(String mapping) {
return mapping.startsWith("/") && mapping.endsWith("/*");
}
private boolean isExtensionMapping(String mapping) {
return mapping.startsWith("*.");
}
private boolean isDefaultMapping(String mapping) {
// TODO : default mapping per spec is "/" - do we really want to support this? is there a
// point ?
return mapping.equals("/");
}
private boolean isRegexpMapping(String mapping) {
return mapping.startsWith("regex:");
}
/**
* Result of {@link ThemeReader} {@link Mapping#match(HttpServletRequest)} method.
* @version $Id$
*/
public static class MatchingResult {
Matcher matcher;
boolean matches;
int matchingEndPosition;
public MatchingResult(Matcher matcher, boolean matches, int matchingEndPosition) {
this.matcher = matcher;
this.matches = matches;
this.matchingEndPosition = matchingEndPosition;
}
public Matcher getMatcher() {
return matcher;
}
public boolean isMatching() {
return matches;
}
public int getMatchingEndPosition() {
return matchingEndPosition;
}
}
}