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

org.jboss.resteasy.reactive.server.mapping.RequestMapper Maven / Gradle / Ivy

There is a newer version: 3.17.5
Show newest version
package org.jboss.resteasy.reactive.server.mapping;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;

public class RequestMapper {

    private static final String[] EMPTY_STRING_ARRAY = new String[0];

    private final PathMatcher>> requestPaths;
    private final PathMatcher.Builder>> pathMatcherBuilder;
    private final ArrayList> templates;
    final int maxParams;

    public RequestMapper(ArrayList> templates) {
        pathMatcherBuilder = new PathMatcher.Builder<>();
        this.templates = templates;
        int max = 0;
        Map>> aggregates = new HashMap<>();
        for (RequestPath i : templates) {
            ArrayList> paths = aggregates.get(i.template.stem);
            if (paths == null) {
                aggregates.put(i.template.stem, paths = new ArrayList<>());
            }
            paths.add(i);
            max = Math.max(max, i.template.countPathParamNames());
        }
        aggregates.forEach(this::sortAggregates);
        aggregates.forEach(this::addPrefixPaths);
        maxParams = max;
        requestPaths = pathMatcherBuilder.build();
    }

    private void sortAggregates(String stem, List> list) {
        list.sort(new Comparator>() {
            @Override
            public int compare(RequestPath t1, RequestPath t2) {
                return t2.template.compareTo(t1.template);
            }
        });
    }

    private void addPrefixPaths(String stem, ArrayList> list) {
        pathMatcherBuilder.addPrefixPath(stem, list);
    }

    public RequestMatch map(String path) {
        var result = mapFromPathMatcher(path, requestPaths.match(path));
        if (result != null) {
            return result;
        }

        // the following code is meant to handle cases like https://github.com/quarkusio/quarkus/issues/30667
        return mapFromPathMatcher(path, requestPaths.defaultMatch(path));
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private RequestMatch mapFromPathMatcher(String path, PathMatcher.PathMatch>> initialMatch) {
        var value = initialMatch.getValue();
        if (initialMatch.getValue() == null) {
            return null;
        }
        int pathLength = path.length();
        for (int index = 0; index < ((List>) value).size(); index++) {
            RequestPath potentialMatch = ((List>) value).get(index);
            String[] params = (maxParams > 0) ? new String[maxParams] : EMPTY_STRING_ARRAY;
            int paramCount = 0;
            boolean matched = true;
            boolean prefixAllowed = potentialMatch.prefixTemplate;
            int matchPos = initialMatch.getMatched().length();
            for (int i = 1; i < potentialMatch.template.components.length; ++i) {
                URITemplate.TemplateComponent segment = potentialMatch.template.components[i];
                if (segment.type == URITemplate.Type.CUSTOM_REGEX) {
                    Matcher matcher = segment.pattern.matcher(path);
                    matched = matcher.find(matchPos);
                    if (!matched || matcher.start() != matchPos) {
                        break;
                    }
                    matchPos = matcher.end();
                    for (String group : segment.groups) {
                        params[paramCount++] = matcher.group(group);
                    }
                } else if (segment.type == URITemplate.Type.LITERAL) {
                    //make sure the literal text is the same
                    if (matchPos + segment.literalText.length() > pathLength) {
                        matched = false;
                        break; //too long
                    }
                    for (int pos = 0; pos < segment.literalText.length(); ++pos) {
                        if (path.charAt(matchPos++) != segment.literalText.charAt(pos)) {
                            matched = false;
                            break;
                        }
                    }
                    if (!matched) {
                        break;
                    }
                } else if (segment.type == URITemplate.Type.DEFAULT_REGEX) {
                    if (matchPos == pathLength) {
                        matched = false;
                        break;
                    }
                    int start = matchPos;
                    while (matchPos < pathLength && path.charAt(matchPos) != '/') {
                        matchPos++;
                    }
                    params[paramCount++] = path.substring(start, matchPos);
                }
            }
            if (!matched) {
                continue;
            }
            if (paramCount < params.length) {
                params[paramCount] = null;
            }
            boolean fullMatch = matchPos == pathLength;
            boolean doPrefixMatch = false;
            if (!fullMatch) {
                //according to the spec every template ends with (/.*)?
                if (matchPos == 1) { //matchPos == 1 corresponds to '/' as a root level match
                    doPrefixMatch = prefixAllowed || pathLength == 1; //if prefix is allowed, or we've matched the whole thing
                } else if (path.charAt(matchPos) == '/') {
                    doPrefixMatch = prefixAllowed || matchPos == pathLength - 1; //if prefix is allowed, or the remainder is only a trailing /
                }
            }
            if (fullMatch || doPrefixMatch) {
                String remaining;
                if (fullMatch) {
                    remaining = "";
                } else {
                    if (matchPos == 1) {
                        remaining = path;
                    } else {
                        remaining = path.substring(matchPos);
                    }
                }
                return new RequestMatch(potentialMatch.template, potentialMatch.value, params, remaining);
            }
        }
        return null;
    }

    public static class RequestPath implements Dumpable {
        public final boolean prefixTemplate;
        public final URITemplate template;
        public final T value;

        public RequestPath(boolean prefixTemplate, URITemplate template, T value) {
            this.prefixTemplate = prefixTemplate;
            this.template = template;
            this.value = value;
        }

        @Override
        public String toString() {
            return "RequestPath{ value: " + value + ", template: " + template + " }";
        }

        @Override
        public void dump(int level) {
            indent(level);
            System.err.println("RequestPath:");
            indent(level + 1);
            System.err.println("value: " + value);
            indent(level + 1);
            System.err.println("template: ");
            template.dump(level + 2);
        }
    }

    public static class RequestMatch {
        public final URITemplate template;
        public final T value;
        /**
         * The matched parameters in order.
         * 

* Note that this array may be larger than required, and padded with null values at the end */ public final String[] pathParamValues; public final String remaining; public RequestMatch(URITemplate template, T value, String[] pathParamValues, String remaining) { this.template = template; this.value = value; this.pathParamValues = pathParamValues; this.remaining = remaining; } @Override public String toString() { return "RequestMatch{ value: " + value + ", template: " + template + ", pathParamValues: " + Arrays.toString(pathParamValues) + " }"; } } public void dump() { this.requestPaths.dump(0); } public PathMatcher>> getRequestPaths() { return requestPaths; } public ArrayList> getTemplates() { return templates; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy