/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*******************************************************************************/
package org.apache.wink.common.internal.uritemplate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ws.rs.core.MultivaluedMap;
import org.apache.wink.common.internal.i18n.Messages;
import org.apache.wink.common.internal.uri.UriEncoder;
/**
* JAX-RS style template processor for compiling, matching and expanding URI
* templates
*/
public class JaxRsUriTemplateProcessor extends UriTemplateProcessor {
/*
* From the JAX-RS API specification: param = "{"WSP nameWSP [ ":"WSP
* regexWSP ] "}" name = (ALPHA / DIGIT / "_")(ALPHA / DIGIT / "." / "_" /
* "-" ) ; \w[\w\.-] regex =( nonbrace / "{" nonbrace "}" ) ; where nonbrace
* is any char other than "{" and "}"
*/
private static final String JAXRS_VARIABLE_PATTERN_WSP = "[ \\t]*"; //$NON-NLS-1$
private static final String JAXRS_VARIABLE_PATTERN_NAME = "(\\w[\\w\\.-]*)"; //$NON-NLS-1$
private static final String JAXRS_VARIABLE_PATTERN_NONBRACE = "[^{}]"; //$NON-NLS-1$
private static final String JAXRS_VARIABLE_PATTERN_REGEX =
"((?:(?:" + JAXRS_VARIABLE_PATTERN_NONBRACE //$NON-NLS-1$
+ ")|(?:\\{" //$NON-NLS-1$
+ JAXRS_VARIABLE_PATTERN_NONBRACE
+ "*\\}))*)"; //$NON-NLS-1$
private static final String JAXRS_VARIABLE_PATTERN_PARAM =
"\\{" + JAXRS_VARIABLE_PATTERN_WSP //$NON-NLS-1$
+ JAXRS_VARIABLE_PATTERN_NAME
+ JAXRS_VARIABLE_PATTERN_WSP
+ "(?::" //$NON-NLS-1$
+ JAXRS_VARIABLE_PATTERN_WSP
+ JAXRS_VARIABLE_PATTERN_REGEX
+ JAXRS_VARIABLE_PATTERN_WSP
+ ")?\\}"; //$NON-NLS-1$
private static final Pattern JAXRS_VARIABLE_PATTERN =
Pattern
.compile(JAXRS_VARIABLE_PATTERN_PARAM);
protected int numOfNonDefaultRegexes;
/**
* Create a processor without a template
*/
public JaxRsUriTemplateProcessor() {
super();
numOfNonDefaultRegexes = 0;
}
/**
* Create an processor with the provided template. The
* {@link #compile(String)} method is called on the provided template.
*
* @param uriTemplate the template that this processor is associated with
*/
public JaxRsUriTemplateProcessor(String template) {
this();
compile(template);
}
@Override
protected void reset() {
super.reset();
numOfNonDefaultRegexes = 0;
}
@Override
public final void compile(String template) {
compile(template, new JaxRsPatternBuilder(this));
}
/**
* Compile the provided uri template and pass compilation events to the
* provided {@link JaxRsCompilationHandler}.
*
* @param template the template to compile
* @param handler the {@link JaxRsCompilationHandler} that will receive
* compilation events
*/
public static void compile(String template, JaxRsCompilationHandler handler) {
if (template == null) {
throw new NullPointerException("uriTemplate"); //$NON-NLS-1$
}
if (handler == null) {
throw new NullPointerException("handler"); //$NON-NLS-1$
}
int start = 0;
String literal = ""; //$NON-NLS-1$
// fire start
handler.startCompile(template);
// search for JAX-RS style variables
Matcher matcher = JAXRS_VARIABLE_PATTERN.matcher(template);
while (matcher.find()) {
// get the literal part which is the characters up to the variable
literal = template.substring(start, matcher.start());
start = matcher.end();
// fire literal
handler.literal(literal);
// capturing group 1 is the variable name
String variable = matcher.group(1);
// capturing group 2 is the regular expression (will be null if it
// doesn't exist)
String regex = matcher.group(2);
// fire variable
handler.variable(variable, regex);
}
// get the trailing literal part
literal = template.substring(start);
// fire end
handler.endCompile(literal);
}
/**
* Expand the provided template using the provided values. Regular
* expressions of variables in the template are ignored. All variables
* defined in the template must have a value.
*
* @param template the uri template to expand
* @param values a map with the values of the variables
* @return an expanded uri using the supplied variable values
*/
public static String expand(String template, MultivaluedMap<String, String> values) {
if (template == null) {
return null;
}
StringBuilder result = new StringBuilder();
expand(template, values, result);
return result.toString();
}
/**
* Expand the provided template using the provided values. Regular
* expressions of variables in the template are ignored. All variables
* defined in the template must have a value.
*
* @param uriTemplate the uri template to expand
* @param values a map with the values of the variables
* @param out the output for the expansion
*/
public static void expand(String template,
MultivaluedMap<String, String> values,
StringBuilder out) {
if (template == null) {
return;
}
JaxRsTemplateExpander expander = new JaxRsTemplateExpander(values, out);
compile(template, expander);
}
@Override
public int compareTo(UriTemplateProcessor other) {
int result = super.compareTo(other);
if (result != 0) {
return result;
}
if (!(other instanceof JaxRsUriTemplateProcessor)) {
return result;
}
return compareNumOfNonDefaultRegexes((JaxRsUriTemplateProcessor)other);
}
private int compareNumOfNonDefaultRegexes(JaxRsUriTemplateProcessor jaxRsProcessor) {
return (numOfNonDefaultRegexes - jaxRsProcessor.numOfNonDefaultRegexes);
}
/**
* Factory method for normalized uri-templates.
*
* @param uriTemplate uri-template specification
* @return instance representing (normalized) uri-template
* @see UriTemplateProcessor#normalizeUri(String)
*/
public static UriTemplateProcessor newNormalizedInstance(String uriTemplate) {
return new JaxRsUriTemplateProcessor(UriTemplateProcessor.normalizeUri(uriTemplate));
}
/**
* This interface is used for receiving events during the compilation of a
* JAX-RS uri template.
*
* @see {@link JaxRsUriTemplateProcessor#compile(String, JaxRsCompilationHandler)}
*/
public static interface JaxRsCompilationHandler extends BaseCompilationHandler {
/**
* Variable event.
*
* @param name the name of the variable
* @param regex the regex as it appears in the template being compiled,
* or <code>null</code> if no regex appeared in the template.
*/
public void variable(String name, String regex);
}
/**
* This compilation handler handles the compilation events for compiling the
* uri template of a processor instance. It creates the regex pattern to use
* for matching and the variables used for matching and expansion, and then
* sets them on the processor instance.
*/
private static class JaxRsPatternBuilder extends AbstractPatternBuilder implements
JaxRsCompilationHandler {
public JaxRsPatternBuilder(JaxRsUriTemplateProcessor processor) {
super(processor);
}
@Override
public void literal(String literal) {
super.literal(UriEncoder.encodeUriTemplate(literal, true));
}
public void variable(String name, String regex) {
// create a new variable
CapturingGroup variable = createVariable(name, regex, null);
// save it to the map of capturing variables for use during matching
processor.variables.add(name, variable);
// save it for use during expansion
processor.expanders.add(variable);
if (regex != null) {
((JaxRsUriTemplateProcessor)processor).numOfNonDefaultRegexes += 1;
}
}
@Override
protected void createTail() {
// overriding the tail creation to handle the special case
// where the complete path template is empty (i.e. it was either
// @Path("") or @Path("/")).
// we need this to be able to handle resources with @Path("/") that
// have sub-resource methods or locators.
// if we don't do this and there's a resource with @Path("/") and a
// sub-resource @Path("hello"), then a request to "/hello" will not
// be picked up becuase the default tail is "(/.*)?"
if (processor.template.equals("")) { //$NON-NLS-1$
// let the tail catch all characters, whether there is a / or not
processor.tail = createVariable(TEMPLATE_TAIL_NAME, "(.*)?", null); //$NON-NLS-1$
} else {
// normal behavior
super.createTail();
}
}
}
/**
* This compilation handler is used for creating an expansion string by
* replacing all variables with their values from the provided map.
*/
private static class JaxRsTemplateExpander extends AbstractTemplateExpander implements
JaxRsCompilationHandler {
public JaxRsTemplateExpander(MultivaluedMap<String, String> values, StringBuilder out) {
super(values, out);
}
public void variable(String name, String regex) {
if (values == null) {
throw new NullPointerException(Messages.getMessage("variableNotSuppliedAValue", name)); //$NON-NLS-1$
}
String valueStr = values.getFirst(name);
if (valueStr == null) {
throw new NullPointerException(Messages.getMessage("variableNotSuppliedAValue", name)); //$NON-NLS-1$
}
out.append(valueStr);
}
}
}