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

org.glassfish.jersey.server.internal.routing.UriRoutingContext Maven / Gradle / Ivy

Go to download

A bundle project producing JAX-RS RI bundles. The primary artifact is an "all-in-one" OSGi-fied JAX-RS RI bundle (jaxrs-ri.jar). Attached to that are two compressed JAX-RS RI archives. The first archive (jaxrs-ri.zip) consists of binary RI bits and contains the API jar (under "api" directory), RI libraries (under "lib" directory) as well as all external RI dependencies (under "ext" directory). The secondary archive (jaxrs-ri-src.zip) contains buildable JAX-RS RI source bundle and contains the API jar (under "api" directory), RI sources (under "src" directory) as well as all external RI dependencies (under "ext" directory). The second archive also contains "build.xml" ANT script that builds the RI sources. To build the JAX-RS RI simply unzip the archive, cd to the created jaxrs-ri directory and invoke "ant" from the command line.

There is a newer version: 3.1.9
Show newest version
/*
 * Copyright (c) 2011, 2024 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.server.internal.routing;

import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.MatchResult;
import java.util.stream.Collectors;

import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.PathSegment;
import jakarta.ws.rs.core.UriBuilder;

import org.glassfish.jersey.internal.util.collection.ImmutableMultivaluedMap;
import org.glassfish.jersey.internal.util.collection.LazyValue;
import org.glassfish.jersey.internal.util.collection.Value;
import org.glassfish.jersey.internal.util.collection.Values;
import org.glassfish.jersey.message.internal.TracingLogger;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.internal.ServerTraceEvent;
import org.glassfish.jersey.server.internal.process.Endpoint;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.model.ResourceMethod;
import org.glassfish.jersey.server.model.ResourceMethodInvoker;
import org.glassfish.jersey.server.model.RuntimeResource;
import org.glassfish.jersey.uri.UriComponent;
import org.glassfish.jersey.uri.UriTemplate;
import org.glassfish.jersey.uri.internal.JerseyUriBuilder;

/**
 * Default implementation of the routing context as well as URI information provider.
 *
 * @author Marek Potociar
 */
public class UriRoutingContext implements RoutingContext {

    private final LinkedList matchResults = new LinkedList<>();
    private final LinkedList matchedResources = new LinkedList<>();
    private final LinkedList templates = new LinkedList<>();

    private final MultivaluedHashMap encodedTemplateValues = new MultivaluedHashMap<>();
    private final ImmutableMultivaluedMap encodedTemplateValuesView =
            new ImmutableMultivaluedMap<>(encodedTemplateValues);

    private final LinkedList paths = new LinkedList<>();
    private final LinkedList matchedRuntimeResources = new LinkedList<>();
    private final LinkedList matchedLocators = new LinkedList<>();
    private final LinkedList locatorSubResources = new LinkedList<>();

    private final LazyValue tracingLogger;

    private volatile ResourceMethod matchedResourceMethod = null;
    private volatile Throwable mappedThrowable = null;

    private Endpoint endpoint;

    private MultivaluedHashMap decodedTemplateValues;
    private ImmutableMultivaluedMap decodedTemplateValuesView;

    private ImmutableMultivaluedMap encodedQueryParamsView;
    private ImmutableMultivaluedMap decodedQueryParamsView;

    /**
     * Injection constructor.
     *
     * @param requestContext request reference.
     */
    public UriRoutingContext(final ContainerRequest requestContext) {
        this.requestContext = requestContext;
        // Tracing Logger is initialized after UriContext before pushMatchedResource
        this.tracingLogger = Values.lazy((Value) () -> TracingLogger.getInstance(requestContext));
    }

    // RoutingContext
    @Override
    public void pushMatchResult(final MatchResult matchResult) {
        matchResults.push(matchResult);
    }

    @Override
    public void pushMatchedResource(final Object resource) {
        tracingLogger.get().log(ServerTraceEvent.MATCH_RESOURCE, resource);
        matchedResources.push(resource);
    }

    @Override
    public Object peekMatchedResource() {
        return matchedResources.peek();
    }

    @Override
    public void pushMatchedLocator(final ResourceMethod resourceLocator) {
        tracingLogger.get().log(ServerTraceEvent.MATCH_LOCATOR, resourceLocator.getInvocable().getHandlingMethod());
        matchedLocators.push(resourceLocator);
    }

    @Override
    public void pushLeftHandPath() {
        final String rightHandPath = getFinalMatchingGroup();
        final int rhpLength = (rightHandPath != null) ? rightHandPath.length() : 0;

        final String encodedRequestPath = getPath(false);

        final int length = encodedRequestPath.length() - rhpLength;
        if (length <= 0) {
            paths.addFirst("");
        } else {
            paths.addFirst(encodedRequestPath.substring(0, length));
        }
    }

    @Override
    public void pushTemplates(final UriTemplate resourceTemplate, final UriTemplate methodTemplate) {
        final Iterator matchResultIterator = matchResults.iterator();
        templates.push(resourceTemplate);
        if (methodTemplate != null) {
            templates.push(methodTemplate);
            // fast-forward the match result iterator to second element in the stack
            matchResultIterator.next();
        }

        pushMatchedTemplateValues(resourceTemplate, matchResultIterator.next());
        if (methodTemplate != null) {
            // use the match result from the top of the stack
            pushMatchedTemplateValues(methodTemplate, matchResults.peek());
        }
    }

    private void pushMatchedTemplateValues(final UriTemplate template, final MatchResult matchResult) {
        int i = 1;
        for (final String templateVariable : template.getTemplateVariables()) {
            final String value = matchResult.group(i++);
            encodedTemplateValues.addFirst(templateVariable, value);
            if (decodedTemplateValues != null) {
                decodedTemplateValues.addFirst(
                        UriComponent.decode(templateVariable, UriComponent.Type.PATH_SEGMENT),
                        UriComponent.decode(value, UriComponent.Type.PATH));
            }
        }
    }

    @Override
    public String getFinalMatchingGroup() {
        final MatchResult mr = matchResults.peek();
        if (mr == null) {
            return null;
        }

        final String finalGroup = mr.group(mr.groupCount());
        // We have found a match but the right hand path pattern did not match anything
        // so just returning an empty string as a final matching group result.
        // Otherwise a non-empty patterns would fail to match the right-hand-path properly.
        // See also PatternWithGroups.match(CharSequence) implementation.
        return finalGroup == null ? "" : finalGroup;
    }

    @Override
    public LinkedList getMatchedResults() {
        return matchResults;
    }

    @Override
    public void setEndpoint(final Endpoint endpoint) {
        this.endpoint = endpoint;
    }

    @Override
    public Endpoint getEndpoint() {
        return endpoint;
    }

    @Override
    public void setMatchedResourceMethod(final ResourceMethod resourceMethod) {
        tracingLogger.get().log(ServerTraceEvent.MATCH_RESOURCE_METHOD, resourceMethod.getInvocable().getHandlingMethod());
        this.matchedResourceMethod = resourceMethod;
    }

    @Override
    public void pushMatchedRuntimeResource(final RuntimeResource runtimeResource) {
        if (tracingLogger.get().isLogEnabled(ServerTraceEvent.MATCH_RUNTIME_RESOURCE)) {
            tracingLogger.get().log(ServerTraceEvent.MATCH_RUNTIME_RESOURCE,
                    runtimeResource.getResources().get(0).getPath(),
                    runtimeResource.getResources().get(0).getPathPattern().getRegex(),
                    matchResults.peek().group()
                            .substring(0, matchResults.peek().group().length() - getFinalMatchingGroup().length()),
                    matchResults.peek().group());
        }

        this.matchedRuntimeResources.push(runtimeResource);
    }

    @Override
    public void pushLocatorSubResource(final Resource subResourceFromLocator) {
        this.locatorSubResources.push(subResourceFromLocator);
    }

    // UriInfo
    private final ContainerRequest requestContext;

    @Override
    public URI getAbsolutePath() {
        return requestContext.getAbsolutePath();
    }

    @Override
    public UriBuilder getAbsolutePathBuilder() {
        return new JerseyUriBuilder().uri(getAbsolutePath());
    }

    @Override
    public URI getBaseUri() {
        return requestContext.getBaseUri();
    }

    @Override
    public UriBuilder getBaseUriBuilder() {
        return new JerseyUriBuilder().uri(getBaseUri());
    }

    @Override
    public List getMatchedResources() {
        return Collections.unmodifiableList(matchedResources);
    }

    @Override
    public List getMatchedURIs() {
        return getMatchedURIs(true);
    }

    private static final Function PATH_DECODER = input -> UriComponent.decode(input, UriComponent.Type.PATH);

    @Override
    public List getMatchedURIs(final boolean decode) {
        final List result;
        if (decode) {
            result = paths.stream().map(PATH_DECODER).collect(Collectors.toList());
        } else {
            result = paths;
        }
        return Collections.unmodifiableList(result);
    }

    @Override
    public String getPath() {
        return requestContext.getPath(true);
    }

    @Override
    public String getPath(final boolean decode) {
        return requestContext.getPath(decode);
    }

    @Override
    public MultivaluedMap getPathParameters() {
        return getPathParameters(true);
    }

    @Override
    public MultivaluedMap getPathParameters(final boolean decode) {
        if (decode) {
            if (decodedTemplateValuesView != null) {
                return decodedTemplateValuesView;
            } else if (decodedTemplateValues == null) {
                decodedTemplateValues = new MultivaluedHashMap<>();
                for (final Map.Entry> e : encodedTemplateValues.entrySet()) {
                    decodedTemplateValues.put(
                            UriComponent.decode(e.getKey(), UriComponent.Type.PATH_SEGMENT),
                            // we need to keep the ability to add new entries
                            e.getValue().stream().map(s -> UriComponent.decode(s, UriComponent.Type.PATH))
                             .collect(Collectors.toCollection(ArrayList::new)));
                }
            }
            decodedTemplateValuesView = new ImmutableMultivaluedMap<>(decodedTemplateValues);

            return decodedTemplateValuesView;
        } else {
            return encodedTemplateValuesView;
        }
    }

    @Override
    public List getPathSegments() {
        return getPathSegments(true);
    }

    @Override
    public List getPathSegments(final boolean decode) {
        final String requestPath = requestContext.getPath(false);
        return Collections.unmodifiableList(UriComponent.decodePath(requestPath, decode));
    }

    @Override
    public MultivaluedMap getQueryParameters() {
        return getQueryParameters(true);
    }

    @Override
    public MultivaluedMap getQueryParameters(final boolean decode) {
        if (decode) {
            if (decodedQueryParamsView != null) {
                return decodedQueryParamsView;
            }

            decodedQueryParamsView =
                    new ImmutableMultivaluedMap<>(UriComponent.decodeQuery(getRequestUri(), true));

            return decodedQueryParamsView;
        } else {
            if (encodedQueryParamsView != null) {
                return encodedQueryParamsView;
            }

            encodedQueryParamsView =
                    new ImmutableMultivaluedMap<>(UriComponent.decodeQuery(getRequestUri(), false));

            return encodedQueryParamsView;

        }
    }

    /**
     * Invalidate internal URI component cache views.
     * 

* This method needs to be called if request URI information changes. *

*/ public void invalidateUriComponentViews() { this.decodedQueryParamsView = null; this.encodedQueryParamsView = null; } @Override public URI getRequestUri() { return requestContext.getRequestUri(); } @Override public UriBuilder getRequestUriBuilder() { return UriBuilder.fromUri(getRequestUri()); } // ExtendedUriInfo @Override public Throwable getMappedThrowable() { return mappedThrowable; } @Override public void setMappedThrowable(final Throwable mappedThrowable) { this.mappedThrowable = mappedThrowable; } @Override public List getMatchedTemplates() { return Collections.unmodifiableList(templates); } @Override public List getPathSegments(final String name) { return getPathSegments(name, true); } @Override public List getPathSegments(final String name, final boolean decode) { final int[] bounds = getPathParameterBounds(name); if (bounds != null) { final String path = matchResults.getLast().group(); // Work out how many path segments are up to the start // and end position of the matching path parameter value // This assumes that the path always starts with a '/' int segmentsStart = 0; for (int x = 0; x < bounds[0]; x++) { if (path.charAt(x) == '/') { segmentsStart++; } } int segmentsEnd = segmentsStart; for (int x = bounds[0]; x < bounds[1]; x++) { if (path.charAt(x) == '/') { segmentsEnd++; } } return getPathSegments(decode).subList(segmentsStart - 1, segmentsEnd); } else { return Collections.emptyList(); } } private int[] getPathParameterBounds(final String name) { final Iterator templatesIterator = templates.iterator(); final Iterator matchResultsIterator = matchResults.iterator(); while (templatesIterator.hasNext()) { MatchResult mr = matchResultsIterator.next(); // Find the index of path parameter final int pIndex = getLastPathParameterIndex(name, templatesIterator.next()); if (pIndex != -1) { int pathLength = mr.group().length(); int segmentIndex = mr.end(pIndex + 1); final int groupLength = segmentIndex - mr.start(pIndex + 1); // Find the absolute position of the end of the // capturing group in the request path while (matchResultsIterator.hasNext()) { mr = matchResultsIterator.next(); segmentIndex += mr.group().length() - pathLength; pathLength = mr.group().length(); } return new int[] {segmentIndex - groupLength, segmentIndex}; } } return null; } private int getLastPathParameterIndex(final String name, final UriTemplate t) { int i = 0; int pIndex = -1; for (final String parameterName : t.getTemplateVariables()) { if (parameterName.equals(name)) { pIndex = i; } i++; } return pIndex; } @Override public Method getResourceMethod() { return endpoint instanceof ResourceMethodInvoker ? ((ResourceMethodInvoker) endpoint).getResourceMethod() : null; } @Override public Class getResourceClass() { return endpoint instanceof ResourceMethodInvoker ? ((ResourceMethodInvoker) endpoint).getResourceClass() : null; } @Override public List getMatchedRuntimeResources() { return this.matchedRuntimeResources; } @Override public ResourceMethod getMatchedResourceMethod() { return matchedResourceMethod; } @Override public List getMatchedResourceLocators() { return matchedLocators; } @Override public List getLocatorSubResources() { return locatorSubResources; } @Override public Resource getMatchedModelResource() { return matchedResourceMethod == null ? null : matchedResourceMethod.getParent(); } @Override public URI resolve(final URI uri) { return UriTemplate.resolve(getBaseUri(), uri); } @Override public URI relativize(URI uri) { if (!uri.isAbsolute()) { uri = resolve(uri); } return UriTemplate.relativize(getRequestUri(), uri); } }