/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed 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.springframework.web.servlet.mvc.method;

import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;

import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestConditionHolder;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.util.UrlPathHelper;

Request mapping information. Encapsulates the following request mapping conditions:
  1. PatternsRequestCondition
  2. RequestMethodsRequestCondition
  3. ParamsRequestCondition
  4. HeadersRequestCondition
  5. ConsumesRequestCondition
  6. ProducesRequestCondition
  7. RequestCondition (optional, custom request condition)
Author:Arjen Poutsma, Rossen Stoyanchev
Since:3.1
/** * Request mapping information. Encapsulates the following request mapping conditions: * <ol> * <li>{@link PatternsRequestCondition} * <li>{@link RequestMethodsRequestCondition} * <li>{@link ParamsRequestCondition} * <li>{@link HeadersRequestCondition} * <li>{@link ConsumesRequestCondition} * <li>{@link ProducesRequestCondition} * <li>{@code RequestCondition} (optional, custom request condition) * </ol> * * @author Arjen Poutsma * @author Rossen Stoyanchev * @since 3.1 */
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> { @Nullable private final String name; private final PatternsRequestCondition patternsCondition; private final RequestMethodsRequestCondition methodsCondition; private final ParamsRequestCondition paramsCondition; private final HeadersRequestCondition headersCondition; private final ConsumesRequestCondition consumesCondition; private final ProducesRequestCondition producesCondition; private final RequestConditionHolder customConditionHolder; public RequestMappingInfo(@Nullable String name, @Nullable PatternsRequestCondition patterns, @Nullable RequestMethodsRequestCondition methods, @Nullable ParamsRequestCondition params, @Nullable HeadersRequestCondition headers, @Nullable ConsumesRequestCondition consumes, @Nullable ProducesRequestCondition produces, @Nullable RequestCondition<?> custom) { this.name = (StringUtils.hasText(name) ? name : null); this.patternsCondition = (patterns != null ? patterns : new PatternsRequestCondition()); this.methodsCondition = (methods != null ? methods : new RequestMethodsRequestCondition()); this.paramsCondition = (params != null ? params : new ParamsRequestCondition()); this.headersCondition = (headers != null ? headers : new HeadersRequestCondition()); this.consumesCondition = (consumes != null ? consumes : new ConsumesRequestCondition()); this.producesCondition = (produces != null ? produces : new ProducesRequestCondition()); this.customConditionHolder = new RequestConditionHolder(custom); }
Creates a new instance with the given request conditions.
/** * Creates a new instance with the given request conditions. */
public RequestMappingInfo(@Nullable PatternsRequestCondition patterns, @Nullable RequestMethodsRequestCondition methods, @Nullable ParamsRequestCondition params, @Nullable HeadersRequestCondition headers, @Nullable ConsumesRequestCondition consumes, @Nullable ProducesRequestCondition produces, @Nullable RequestCondition<?> custom) { this(null, patterns, methods, params, headers, consumes, produces, custom); }
Re-create a RequestMappingInfo with the given custom request condition.
/** * Re-create a RequestMappingInfo with the given custom request condition. */
public RequestMappingInfo(RequestMappingInfo info, @Nullable RequestCondition<?> customRequestCondition) { this(info.name, info.patternsCondition, info.methodsCondition, info.paramsCondition, info.headersCondition, info.consumesCondition, info.producesCondition, customRequestCondition); }
Return the name for this mapping, or null.
/** * Return the name for this mapping, or {@code null}. */
@Nullable public String getName() { return this.name; }
Return the URL patterns of this RequestMappingInfo; or instance with 0 patterns (never null).
/** * Return the URL patterns of this {@link RequestMappingInfo}; * or instance with 0 patterns (never {@code null}). */
public PatternsRequestCondition getPatternsCondition() { return this.patternsCondition; }
Return the HTTP request methods of this RequestMappingInfo; or instance with 0 request methods (never null).
/** * Return the HTTP request methods of this {@link RequestMappingInfo}; * or instance with 0 request methods (never {@code null}). */
public RequestMethodsRequestCondition getMethodsCondition() { return this.methodsCondition; }
Return the "parameters" condition of this RequestMappingInfo; or instance with 0 parameter expressions (never null).
/** * Return the "parameters" condition of this {@link RequestMappingInfo}; * or instance with 0 parameter expressions (never {@code null}). */
public ParamsRequestCondition getParamsCondition() { return this.paramsCondition; }
Return the "headers" condition of this RequestMappingInfo; or instance with 0 header expressions (never null).
/** * Return the "headers" condition of this {@link RequestMappingInfo}; * or instance with 0 header expressions (never {@code null}). */
public HeadersRequestCondition getHeadersCondition() { return this.headersCondition; }
Return the "consumes" condition of this RequestMappingInfo; or instance with 0 consumes expressions (never null).
/** * Return the "consumes" condition of this {@link RequestMappingInfo}; * or instance with 0 consumes expressions (never {@code null}). */
public ConsumesRequestCondition getConsumesCondition() { return this.consumesCondition; }
Return the "produces" condition of this RequestMappingInfo; or instance with 0 produces expressions (never null).
/** * Return the "produces" condition of this {@link RequestMappingInfo}; * or instance with 0 produces expressions (never {@code null}). */
public ProducesRequestCondition getProducesCondition() { return this.producesCondition; }
Return the "custom" condition of this RequestMappingInfo, or null.
/** * Return the "custom" condition of this {@link RequestMappingInfo}, or {@code null}. */
@Nullable public RequestCondition<?> getCustomCondition() { return this.customConditionHolder.getCondition(); }
Combine "this" request mapping info (i.e. the current instance) with another request mapping info instance.

Example: combine type- and method-level request mappings.

Returns:a new request mapping info instance; never null
/** * Combine "this" request mapping info (i.e. the current instance) with another request mapping info instance. * <p>Example: combine type- and method-level request mappings. * @return a new request mapping info instance; never {@code null} */
@Override public RequestMappingInfo combine(RequestMappingInfo other) { String name = combineNames(other); PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition); RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition); ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition); HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition); ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition); ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition); RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder); return new RequestMappingInfo(name, patterns, methods, params, headers, consumes, produces, custom.getCondition()); } @Nullable private String combineNames(RequestMappingInfo other) { if (this.name != null && other.name != null) { String separator = RequestMappingInfoHandlerMethodMappingNamingStrategy.SEPARATOR; return this.name + separator + other.name; } else if (this.name != null) { return this.name; } else { return other.name; } }
Checks if all conditions in this request mapping info match the provided request and returns a potentially new request mapping info with conditions tailored to the current request.

For example the returned instance may contain the subset of URL patterns that match to the current request, sorted with best matching patterns on top.

Returns:a new instance in case all conditions match; or null otherwise
/** * Checks if all conditions in this request mapping info match the provided request and returns * a potentially new request mapping info with conditions tailored to the current request. * <p>For example the returned instance may contain the subset of URL patterns that match to * the current request, sorted with best matching patterns on top. * @return a new instance in case all conditions match; or {@code null} otherwise */
@Override @Nullable public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request); ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request); HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request); ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request); ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request); if (methods == null || params == null || headers == null || consumes == null || produces == null) { return null; } PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request); if (patterns == null) { return null; } RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request); if (custom == null) { return null; } return new RequestMappingInfo(this.name, patterns, methods, params, headers, consumes, produces, custom.getCondition()); }
Compares "this" info (i.e. the current instance) with another info in the context of a request.

Note: It is assumed both instances have been obtained via getMatchingCondition(HttpServletRequest) to ensure they have conditions with content relevant to current request.

/** * Compares "this" info (i.e. the current instance) with another info in the context of a request. * <p>Note: It is assumed both instances have been obtained via * {@link #getMatchingCondition(HttpServletRequest)} to ensure they have conditions with * content relevant to current request. */
@Override public int compareTo(RequestMappingInfo other, HttpServletRequest request) { int result; // Automatic vs explicit HTTP HEAD mapping if (HttpMethod.HEAD.matches(request.getMethod())) { result = this.methodsCondition.compareTo(other.getMethodsCondition(), request); if (result != 0) { return result; } } result = this.patternsCondition.compareTo(other.getPatternsCondition(), request); if (result != 0) { return result; } result = this.paramsCondition.compareTo(other.getParamsCondition(), request); if (result != 0) { return result; } result = this.headersCondition.compareTo(other.getHeadersCondition(), request); if (result != 0) { return result; } result = this.consumesCondition.compareTo(other.getConsumesCondition(), request); if (result != 0) { return result; } result = this.producesCondition.compareTo(other.getProducesCondition(), request); if (result != 0) { return result; } // Implicit (no method) vs explicit HTTP method mappings result = this.methodsCondition.compareTo(other.getMethodsCondition(), request); if (result != 0) { return result; } result = this.customConditionHolder.compareTo(other.customConditionHolder, request); if (result != 0) { return result; } return 0; } @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof RequestMappingInfo)) { return false; } RequestMappingInfo otherInfo = (RequestMappingInfo) other; return (this.patternsCondition.equals(otherInfo.patternsCondition) && this.methodsCondition.equals(otherInfo.methodsCondition) && this.paramsCondition.equals(otherInfo.paramsCondition) && this.headersCondition.equals(otherInfo.headersCondition) && this.consumesCondition.equals(otherInfo.consumesCondition) && this.producesCondition.equals(otherInfo.producesCondition) && this.customConditionHolder.equals(otherInfo.customConditionHolder)); } @Override public int hashCode() { return (this.patternsCondition.hashCode() * 31 + // primary differentiation this.methodsCondition.hashCode() + this.paramsCondition.hashCode() + this.headersCondition.hashCode() + this.consumesCondition.hashCode() + this.producesCondition.hashCode() + this.customConditionHolder.hashCode()); } @Override public String toString() { StringBuilder builder = new StringBuilder("{"); if (!this.methodsCondition.isEmpty()) { Set<RequestMethod> httpMethods = this.methodsCondition.getMethods(); builder.append(httpMethods.size() == 1 ? httpMethods.iterator().next() : httpMethods); } if (!this.patternsCondition.isEmpty()) { Set<String> patterns = this.patternsCondition.getPatterns(); builder.append(" ").append(patterns.size() == 1 ? patterns.iterator().next() : patterns); } if (!this.paramsCondition.isEmpty()) { builder.append(", params ").append(this.paramsCondition); } if (!this.headersCondition.isEmpty()) { builder.append(", headers ").append(this.headersCondition); } if (!this.consumesCondition.isEmpty()) { builder.append(", consumes ").append(this.consumesCondition); } if (!this.producesCondition.isEmpty()) { builder.append(", produces ").append(this.producesCondition); } if (!this.customConditionHolder.isEmpty()) { builder.append(", and ").append(this.customConditionHolder); } builder.append('}'); return builder.toString(); }
Create a new RequestMappingInfo.Builder with the given paths.
Params:
  • paths – the paths to use
Since:4.2
/** * Create a new {@code RequestMappingInfo.Builder} with the given paths. * @param paths the paths to use * @since 4.2 */
public static Builder paths(String... paths) { return new DefaultBuilder(paths); }
Defines a builder for creating a RequestMappingInfo.
Since:4.2
/** * Defines a builder for creating a RequestMappingInfo. * @since 4.2 */
public interface Builder {
Set the path patterns.
/** * Set the path patterns. */
Builder paths(String... paths);
Set the request method conditions.
/** * Set the request method conditions. */
Builder methods(RequestMethod... methods);
Set the request param conditions.
/** * Set the request param conditions. */
Builder params(String... params);
Set the header conditions.

By default this is not set.

/** * Set the header conditions. * <p>By default this is not set. */
Builder headers(String... headers);
Set the consumes conditions.
/** * Set the consumes conditions. */
Builder consumes(String... consumes);
Set the produces conditions.
/** * Set the produces conditions. */
Builder produces(String... produces);
Set the mapping name.
/** * Set the mapping name. */
Builder mappingName(String name);
Set a custom condition to use.
/** * Set a custom condition to use. */
Builder customCondition(RequestCondition<?> condition);
Provide additional configuration needed for request mapping purposes.
/** * Provide additional configuration needed for request mapping purposes. */
Builder options(BuilderConfiguration options);
Build the RequestMappingInfo.
/** * Build the RequestMappingInfo. */
RequestMappingInfo build(); } private static class DefaultBuilder implements Builder { private String[] paths = new String[0]; private RequestMethod[] methods = new RequestMethod[0]; private String[] params = new String[0]; private String[] headers = new String[0]; private String[] consumes = new String[0]; private String[] produces = new String[0]; @Nullable private String mappingName; @Nullable private RequestCondition<?> customCondition; private BuilderConfiguration options = new BuilderConfiguration(); public DefaultBuilder(String... paths) { this.paths = paths; } @Override public Builder paths(String... paths) { this.paths = paths; return this; } @Override public DefaultBuilder methods(RequestMethod... methods) { this.methods = methods; return this; } @Override public DefaultBuilder params(String... params) { this.params = params; return this; } @Override public DefaultBuilder headers(String... headers) { this.headers = headers; return this; } @Override public DefaultBuilder consumes(String... consumes) { this.consumes = consumes; return this; } @Override public DefaultBuilder produces(String... produces) { this.produces = produces; return this; } @Override public DefaultBuilder mappingName(String name) { this.mappingName = name; return this; } @Override public DefaultBuilder customCondition(RequestCondition<?> condition) { this.customCondition = condition; return this; } @Override public Builder options(BuilderConfiguration options) { this.options = options; return this; } @Override public RequestMappingInfo build() { ContentNegotiationManager manager = this.options.getContentNegotiationManager(); PatternsRequestCondition patternsCondition = new PatternsRequestCondition( this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(), this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(), this.options.getFileExtensions()); return new RequestMappingInfo(this.mappingName, patternsCondition, new RequestMethodsRequestCondition(this.methods), new ParamsRequestCondition(this.params), new HeadersRequestCondition(this.headers), new ConsumesRequestCondition(this.consumes, this.headers), new ProducesRequestCondition(this.produces, this.headers, manager), this.customCondition); } }
Container for configuration options used for request mapping purposes. Such configuration is required to create RequestMappingInfo instances but is typically used across all RequestMappingInfo instances.
See Also:
Since:4.2
/** * Container for configuration options used for request mapping purposes. * Such configuration is required to create RequestMappingInfo instances but * is typically used across all RequestMappingInfo instances. * @since 4.2 * @see Builder#options */
public static class BuilderConfiguration { @Nullable private UrlPathHelper urlPathHelper; @Nullable private PathMatcher pathMatcher; private boolean trailingSlashMatch = true; private boolean suffixPatternMatch = true; private boolean registeredSuffixPatternMatch = false; @Nullable private ContentNegotiationManager contentNegotiationManager;
Set a custom UrlPathHelper to use for the PatternsRequestCondition.

By default this is not set.

Since:4.2.8
/** * Set a custom UrlPathHelper to use for the PatternsRequestCondition. * <p>By default this is not set. * @since 4.2.8 */
public void setUrlPathHelper(@Nullable UrlPathHelper urlPathHelper) { this.urlPathHelper = urlPathHelper; }
Return a custom UrlPathHelper to use for the PatternsRequestCondition, if any.
/** * Return a custom UrlPathHelper to use for the PatternsRequestCondition, if any. */
@Nullable public UrlPathHelper getUrlPathHelper() { return this.urlPathHelper; }
Set a custom PathMatcher to use for the PatternsRequestCondition.

By default this is not set.

/** * Set a custom PathMatcher to use for the PatternsRequestCondition. * <p>By default this is not set. */
public void setPathMatcher(@Nullable PathMatcher pathMatcher) { this.pathMatcher = pathMatcher; }
Return a custom PathMatcher to use for the PatternsRequestCondition, if any.
/** * Return a custom PathMatcher to use for the PatternsRequestCondition, if any. */
@Nullable public PathMatcher getPathMatcher() { return this.pathMatcher; }
Set whether to apply trailing slash matching in PatternsRequestCondition.

By default this is set to 'true'.

/** * Set whether to apply trailing slash matching in PatternsRequestCondition. * <p>By default this is set to 'true'. */
public void setTrailingSlashMatch(boolean trailingSlashMatch) { this.trailingSlashMatch = trailingSlashMatch; }
Return whether to apply trailing slash matching in PatternsRequestCondition.
/** * Return whether to apply trailing slash matching in PatternsRequestCondition. */
public boolean useTrailingSlashMatch() { return this.trailingSlashMatch; }
Set whether to apply suffix pattern matching in PatternsRequestCondition.

By default this is set to 'true'.

See Also:
  • setRegisteredSuffixPatternMatch(boolean)
/** * Set whether to apply suffix pattern matching in PatternsRequestCondition. * <p>By default this is set to 'true'. * @see #setRegisteredSuffixPatternMatch(boolean) */
public void setSuffixPatternMatch(boolean suffixPatternMatch) { this.suffixPatternMatch = suffixPatternMatch; }
Return whether to apply suffix pattern matching in PatternsRequestCondition.
/** * Return whether to apply suffix pattern matching in PatternsRequestCondition. */
public boolean useSuffixPatternMatch() { return this.suffixPatternMatch; }
Set whether suffix pattern matching should be restricted to registered file extensions only. Setting this property also sets suffixPatternMatch=true and requires that a setContentNegotiationManager is also configured in order to obtain the registered file extensions.
/** * Set whether suffix pattern matching should be restricted to registered * file extensions only. Setting this property also sets * {@code suffixPatternMatch=true} and requires that a * {@link #setContentNegotiationManager} is also configured in order to * obtain the registered file extensions. */
public void setRegisteredSuffixPatternMatch(boolean registeredSuffixPatternMatch) { this.registeredSuffixPatternMatch = registeredSuffixPatternMatch; this.suffixPatternMatch = (registeredSuffixPatternMatch || this.suffixPatternMatch); }
Return whether suffix pattern matching should be restricted to registered file extensions only.
/** * Return whether suffix pattern matching should be restricted to registered * file extensions only. */
public boolean useRegisteredSuffixPatternMatch() { return this.registeredSuffixPatternMatch; }
Return the file extensions to use for suffix pattern matching. If registeredSuffixPatternMatch=true, the extensions are obtained from the configured contentNegotiationManager.
/** * Return the file extensions to use for suffix pattern matching. If * {@code registeredSuffixPatternMatch=true}, the extensions are obtained * from the configured {@code contentNegotiationManager}. */
@Nullable public List<String> getFileExtensions() { if (useRegisteredSuffixPatternMatch() && this.contentNegotiationManager != null) { return this.contentNegotiationManager.getAllFileExtensions(); } return null; }
Set the ContentNegotiationManager to use for the ProducesRequestCondition.

By default this is not set.

/** * Set the ContentNegotiationManager to use for the ProducesRequestCondition. * <p>By default this is not set. */
public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) { this.contentNegotiationManager = contentNegotiationManager; }
Return the ContentNegotiationManager to use for the ProducesRequestCondition, if any.
/** * Return the ContentNegotiationManager to use for the ProducesRequestCondition, * if any. */
@Nullable public ContentNegotiationManager getContentNegotiationManager() { return this.contentNegotiationManager; } } }