All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cxf.jaxrs.model.URITemplate Maven / Gradle / Ivy

There is a newer version: 3.0.0-milestone2
Show newest version
/**
 * 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.cxf.jaxrs.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.ws.rs.Path;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.PathSegment;

import org.apache.cxf.jaxrs.utils.HttpUtils;
import org.apache.cxf.jaxrs.utils.JAXRSUtils;

public final class URITemplate {

    public static final String TEMPLATE_PARAMETERS = "jaxrs.template.parameters";
    public static final String LIMITED_REGEX_SUFFIX = "(/.*)?";
    public static final String UNLIMITED_REGEX_SUFFIX = "(/)?";
    public static final String FINAL_MATCH_GROUP = "FINAL_MATCH_GROUP";

    /**
     * The regular expression for matching URI templates and names.
     */
    private static final Pattern TEMPLATE_NAMES_PATTERN = Pattern.compile("\\{(\\w[-\\w\\.]*)(\\:(.+?))?\\}");

    private static final String DEFAULT_PATH_VARIABLE_REGEX = "([^/]+?)";
    private static final String PATH_UNLIMITED_VARIABLE_REGEX = "(.*?)";
        
    private static final String CHARACTERS_TO_ESCAPE = ".";

    private final String template;
    private final List templateVariables = new ArrayList();
    private final List customTemplateVariables = new ArrayList();
    private final Pattern templateRegexPattern;
    private final String literals;

    public URITemplate(String theTemplate) {
        this(theTemplate, true);
    }

    public URITemplate(String theTemplate, boolean limited) {
        
        this.template = theTemplate;

        StringBuilder literalChars = new StringBuilder();
        StringBuilder patternBuilder = new StringBuilder();

        // compute a regular expression from URI template
        Matcher matcher = TEMPLATE_NAMES_PATTERN.matcher(template);
        int i = 0;
        while (matcher.find()) {
            templateVariables.add(matcher.group(1).trim());

            String substr = escapeCharacters(template.substring(i, matcher.start()));
            literalChars.append(substr);
            patternBuilder.append(substr);
            i = matcher.end();
            if (matcher.group(2) != null && matcher.group(3) != null) {
                patternBuilder.append('(');
                patternBuilder.append(matcher.group(3).trim());
                patternBuilder.append(')');
                customTemplateVariables.add(matcher.group(1).trim());
            } else {
                if (!limited && i == template.length()) {
                    patternBuilder.append(PATH_UNLIMITED_VARIABLE_REGEX);
                } else {
                    patternBuilder.append(DEFAULT_PATH_VARIABLE_REGEX);
                }
            }
        }
        String substr = escapeCharacters(template.substring(i, template.length()));
        literalChars.append(substr);
        patternBuilder.append(substr);

        literals = literalChars.toString();

        int endPos = patternBuilder.length() - 1;
        boolean endsWithSlash = (endPos >= 0) ? patternBuilder.charAt(endPos) == '/' : false;
        if (endsWithSlash) {
            patternBuilder.deleteCharAt(endPos);
        }
        patternBuilder.append(limited ? LIMITED_REGEX_SUFFIX : UNLIMITED_REGEX_SUFFIX);

        templateRegexPattern = Pattern.compile(patternBuilder.toString());
    }

    public String getLiteralChars() {
        return literals;
    }

    public String getValue() {
        return template;
    }

    public List getVariables() {
        return Collections.unmodifiableList(templateVariables);
    }

    public List getCustomVariables() {
        return Collections.unmodifiableList(customTemplateVariables);
    }

    private static String escapeCharacters(String expression) {

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < expression.length(); i++) {
            char ch = expression.charAt(i);
            sb.append(isReservedCharacter(ch) ? "\\" + ch : ch);
        }
        return sb.toString();
    }

    private static boolean isReservedCharacter(char ch) {
        return CHARACTERS_TO_ESCAPE.indexOf(ch) != -1;
    }

    public boolean match(String uri, MultivaluedMap templateVariableToValue) {

        if (uri == null) {
            return (templateRegexPattern == null) ? true : false;
        }

        if (templateRegexPattern == null) {
            return false;
        }

        Matcher m = templateRegexPattern.matcher(uri);
        if (!m.matches()) {
            if (uri.contains(";")) {
                // we might be trying to match one or few path segments containing matrix
                // parameters against a clear path segment as in @Path("base").
                List pList = JAXRSUtils.getPathSegments(template, false);
                List uList = JAXRSUtils.getPathSegments(uri, false);
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < uList.size(); i++) {
                    sb.append('/');
                    if (pList.size() > i && pList.get(i).getPath().indexOf('{') == -1) {
                        sb.append(uList.get(i).getPath());
                    } else {
                        sb.append(HttpUtils.fromPathSegment(uList.get(i)));
                    }
                }
                uri = sb.toString();
                m = templateRegexPattern.matcher(uri);
                if (!m.matches()) {
                    return false;
                }
            } else {
                return false;
            }
        }

        // Assign the matched template values to template variables
        int i = 1;
        for (String name : templateVariables) {
            String value = m.group(i++);
            templateVariableToValue.add(name, value);
        }

        // The right hand side value, might be used to further resolve sub-resources.

        String finalGroup = m.group(i);
        templateVariableToValue.putSingle(FINAL_MATCH_GROUP, finalGroup == null ? "/" : finalGroup);

        return true;
    }

    /**
     * Substitutes template variables with listed values. List of values is counterpart for
     * {@link #getVariables() list of variables}. When list of value is shorter than variables substitution
     * is partial. When variable has pattern, value must fit to pattern, otherwise
     * {@link IllegalArgumentException} is thrown.
     * 

* Example1: for template "/{a}/{b}/{a}" {@link #getVariables()} returns "[a, b, a]"; providing here list * of value "[foo, bar, baz]" results with "/foo/bar/baz". *

* Example2: for template "/{a}/{b}/{a}" providing list of values "[foo]" results with "/foo/{b}/{a}". * * @param values values for variables * @return template with bound variables. * @throws IllegalArgumentException when values is null, any value does not match pattern etc. */ public String substitute(List values) throws IllegalArgumentException { if (values == null) { throw new IllegalArgumentException("values is null"); } Matcher m = TEMPLATE_NAMES_PATTERN.matcher(template); Iterator valIter = values.iterator(); StringBuffer sb = new StringBuffer(); while (m.find() && valIter.hasNext()) { String value = valIter.next(); String varPattern = m.group(2); if (varPattern != null) { // variable has pattern, matching formats e.g. // for "{a:\d\d}" variable value must have two digits etc. Pattern p = Pattern.compile(varPattern); if (!p.matcher(":" + value).matches()) { throw new IllegalArgumentException("Value '" + value + "' does not match variable " + m.group()); } } m.appendReplacement(sb, value); } m.appendTail(sb); return sb.toString(); } /** * Substitutes template variables with mapped values. Variables are mapped to values; if not all variables * are bound result will still contain variables. Note that all variables with the same name are replaced * by one value. *

* Example: for template "/{a}/{b}/{a}" {@link #getVariables()} returns "[a, b, a]"; providing here * mapping "[a: foo, b: bar]" results with "/foo/bar/foo" (full substitution) and for mapping "[b: baz]" * result is "{a}/baz/{a}" (partial substitution). * * @param valuesMap map variables to their values; on each value Object.toString() is called. * @return template with bound variables. * @throws IllegalArgumentException when size of list of values differs from list of variables or list * contains nulls. */ public String substitute(Map valuesMap) throws IllegalArgumentException { if (valuesMap == null) { throw new IllegalArgumentException("valuesMap is null"); } Matcher m = TEMPLATE_NAMES_PATTERN.matcher(template); StringBuffer sb = new StringBuffer(); while (m.find()) { Object value = valuesMap.get(m.group(1)); if (value == null) { continue; } String sval = value.toString(); String varPattern = m.group(2); if (varPattern != null) { Pattern p = Pattern.compile(varPattern); if (!p.matcher(":" + sval).matches()) { throw new IllegalArgumentException("Value '" + sval + "' does not match variable " + m.group()); } } m.appendReplacement(sb, sval); } m.appendTail(sb); return sb.toString(); } /** * Encoded literal characters surrounding template variables, * ex. "a {id} b" will be encoded to "a%20{id}%20b" * @return encoded value */ public String encodeLiteralCharacters() { StringBuilder sb = new StringBuilder(); Matcher matcher = TEMPLATE_NAMES_PATTERN.matcher(template); int i = 0; while (matcher.find()) { sb.append(HttpUtils.encodePartiallyEncoded(template.substring(i, matcher.start()), false)); sb.append('{').append(matcher.group(1)).append('}'); i = matcher.end(); } sb.append(HttpUtils.encodePartiallyEncoded(template.substring(i, template.length()), false)); return sb.toString(); } public static URITemplate createTemplate(ClassResourceInfo cri, Path path) { if (path == null) { return new URITemplate("/"); } String pathValue = path.value(); if (!pathValue.startsWith("/")) { pathValue = "/" + pathValue; } return new URITemplate(pathValue, path.limited()); } public static int compareTemplates(URITemplate t1, URITemplate t2) { String l1 = t1.getLiteralChars(); String l2 = t2.getLiteralChars(); if (!l1.equals(l2)) { // descending order return l1.length() < l2.length() ? 1 : -1; } int g1 = t1.templateVariables.size(); int g2 = t2.templateVariables.size(); // descending order int result = g1 < g2 ? 1 : g1 > g2 ? -1 : 0; if (result == 0) { int gCustom1 = t1.customTemplateVariables.size(); int gCustom2 = t2.customTemplateVariables.size(); if (gCustom1 != gCustom2) { // descending order return gCustom1 < gCustom2 ? 1 : -1; } } return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy