/* * 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.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; /** * Request mapping information. Encapsulates the following request mapping conditions: *
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(this.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 By default this is not set.
*/
public void setContentTypeResolver(RequestedContentTypeResolver resolver) {
this.contentTypeResolver = resolver;
}
@Nullable
public RequestedContentTypeResolver getContentTypeResolver() {
return this.contentTypeResolver;
}
}
}