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

org.springframework.web.reactive.result.method.RequestMappingInfo Maven / Gradle / Ivy

/*
 * Copyright 2002-2017 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.reactive.result.method;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.result.condition.ConsumesRequestCondition;
import org.springframework.web.reactive.result.condition.HeadersRequestCondition;
import org.springframework.web.reactive.result.condition.ParamsRequestCondition;
import org.springframework.web.reactive.result.condition.PatternsRequestCondition;
import org.springframework.web.reactive.result.condition.ProducesRequestCondition;
import org.springframework.web.reactive.result.condition.RequestCondition;
import org.springframework.web.reactive.result.condition.RequestConditionHolder;
import org.springframework.web.reactive.result.condition.RequestMethodsRequestCondition;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;

/**
 * Encapsulates the following request mapping conditions:
 * 
    *
  1. {@link PatternsRequestCondition} *
  2. {@link RequestMethodsRequestCondition} *
  3. {@link ParamsRequestCondition} *
  4. {@link HeadersRequestCondition} *
  5. {@link ConsumesRequestCondition} *
  6. {@link ProducesRequestCondition} *
  7. {@code RequestCondition} (optional, custom request condition) *
* * @author Rossen Stoyanchev * @since 5.0 */ public final class RequestMappingInfo implements RequestCondition { @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. */ 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. */ 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 {@code null}. */ @Nullable public String getName() { return this.name; } /** * Returns the URL patterns of this {@link RequestMappingInfo}; * or instance with 0 patterns, never {@code null}. */ public PatternsRequestCondition getPatternsCondition() { return this.patternsCondition; } /** * Returns the HTTP request methods of this {@link RequestMappingInfo}; * or instance with 0 request methods, never {@code null}. */ public RequestMethodsRequestCondition getMethodsCondition() { return this.methodsCondition; } /** * Returns the "parameters" condition of this {@link RequestMappingInfo}; * or instance with 0 parameter expressions, never {@code null}. */ public ParamsRequestCondition getParamsCondition() { return this.paramsCondition; } /** * Returns the "headers" condition of this {@link RequestMappingInfo}; * or instance with 0 header expressions, never {@code null}. */ public HeadersRequestCondition getHeadersCondition() { return this.headersCondition; } /** * Returns the "consumes" condition of this {@link RequestMappingInfo}; * or instance with 0 consumes expressions, never {@code null}. */ public ConsumesRequestCondition getConsumesCondition() { return this.consumesCondition; } /** * Returns the "produces" condition of this {@link RequestMappingInfo}; * or instance with 0 produces expressions, never {@code null}. */ public ProducesRequestCondition getProducesCondition() { return this.producesCondition; } /** * Returns the "custom" condition of this {@link RequestMappingInfo}; or {@code null}. */ @Nullable public RequestCondition getCustomCondition() { return this.customConditionHolder.getCondition(); } /** * Combines "this" request mapping info (i.e. the current instance) with another request mapping info instance. *

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 = "#"; return this.name + separator + other.name; } else if (this.name != null) { return this.name; } else { return (other.name != null ? other.name : null); } } /** * 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. * @return a new instance in case all conditions match; or {@code null} otherwise */ @Override @Nullable public RequestMappingInfo getMatchingCondition(ServerWebExchange exchange) { RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(exchange); ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(exchange); HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(exchange); ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(exchange); ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(exchange); if (methods == null || params == null || headers == null || consumes == null || produces == null) { return null; } PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(exchange); if (patterns == null) { return null; } RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(exchange); 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 * {@link #getMatchingCondition(ServerWebExchange)} to ensure they have conditions with * content relevant to current request. */ @Override public int compareTo(RequestMappingInfo other, ServerWebExchange exchange) { int result = this.patternsCondition.compareTo(other.getPatternsCondition(), exchange); if (result != 0) { return result; } result = this.paramsCondition.compareTo(other.getParamsCondition(), exchange); if (result != 0) { return result; } result = this.headersCondition.compareTo(other.getHeadersCondition(), exchange); if (result != 0) { return result; } result = this.consumesCondition.compareTo(other.getConsumesCondition(), exchange); if (result != 0) { return result; } result = this.producesCondition.compareTo(other.getProducesCondition(), exchange); if (result != 0) { return result; } result = this.methodsCondition.compareTo(other.getMethodsCondition(), exchange); if (result != 0) { return result; } result = this.customConditionHolder.compareTo(other.customConditionHolder, exchange); 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("{"); builder.append(this.patternsCondition); if (!this.methodsCondition.isEmpty()) { builder.append(",methods=").append(this.methodsCondition); } 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(",custom=").append(this.customConditionHolder); } builder.append('}'); return builder.toString(); } /** * Create a new {@code RequestMappingInfo.Builder} with the given paths. * @param paths the paths to use */ public static Builder paths(String... paths) { return new DefaultBuilder(paths); } /** * Defines a builder for creating a RequestMappingInfo. */ public interface Builder { /** * Set the path patterns. */ Builder paths(String... paths); /** * Set the request method conditions. */ Builder methods(RequestMethod... methods); /** * Set the request param conditions. */ Builder params(String... params); /** * Set the header conditions. *

By default this is not set. */ Builder headers(String... headers); /** * Set the consumes conditions. */ Builder consumes(String... consumes); /** * Set the produces conditions. */ Builder produces(String... produces); /** * Set the mapping name. */ Builder mappingName(String name); /** * Set a custom condition to use. */ Builder customCondition(RequestCondition condition); /** * Provide additional configuration needed for request mapping purposes. */ Builder options(BuilderConfiguration options); /** * Build the RequestMappingInfo. */ RequestMappingInfo build(); } private static class DefaultBuilder implements Builder { private String[] paths; @Nullable private RequestMethod[] methods; @Nullable private String[] params; @Nullable private String[] headers; @Nullable private String[] consumes; @Nullable private String[] produces; @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() { RequestedContentTypeResolver contentTypeResolver = this.options.getContentTypeResolver(); PathPatternParser parser = this.options.getPatternParser() != null ? this.options.getPatternParser() : new PathPatternParser(); PatternsRequestCondition patternsCondition = new PatternsRequestCondition(parse(this.paths, parser)); return new RequestMappingInfo(this.mappingName, patternsCondition, new RequestMethodsRequestCondition(methods), new ParamsRequestCondition(this.params), new HeadersRequestCondition(this.headers), new ConsumesRequestCondition(this.consumes, this.headers), new ProducesRequestCondition(this.produces, this.headers, contentTypeResolver), this.customCondition); } private static List parse(String[] paths, PathPatternParser parser) { return Arrays .stream(paths) .map(path -> { if (StringUtils.hasText(path) && !path.startsWith("/")) { path = "/" + path; } return parser.parse(path); }) .collect(Collectors.toList()); } } /** * 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 Builder#options */ public static class BuilderConfiguration { @Nullable private PathPatternParser patternParser; @Nullable private RequestedContentTypeResolver contentTypeResolver; public void setPatternParser(PathPatternParser patternParser) { this.patternParser = patternParser; } @Nullable public PathPatternParser getPatternParser() { return this.patternParser; } /** * Set the ContentNegotiationManager to use for the ProducesRequestCondition. *

By default this is not set. */ public void setContentTypeResolver(RequestedContentTypeResolver resolver) { this.contentTypeResolver = resolver; } @Nullable public RequestedContentTypeResolver getContentTypeResolver() { return this.contentTypeResolver; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy