Polishing
This commit is contained in:
parent
e59a5993f3
commit
382a931e7d
|
@ -13,6 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.core;
|
package org.springframework.core;
|
||||||
|
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
|
@ -22,8 +23,8 @@ import reactor.core.publisher.Mono;
|
||||||
/**
|
/**
|
||||||
* Contract for adapting to and from {@link Flux} and {@link Mono}.
|
* Contract for adapting to and from {@link Flux} and {@link Mono}.
|
||||||
*
|
*
|
||||||
* <p>An adapter supports a specific adaptee type whose stream semantics can be
|
* <p>An adapter supports a specific adaptee type whose stream semantics
|
||||||
* checked via {@link #getDescriptor()}.
|
* can be checked via {@link #getDescriptor()}.
|
||||||
*
|
*
|
||||||
* <p>Use the {@link ReactiveAdapterRegistry} to obtain an adapter for a
|
* <p>Use the {@link ReactiveAdapterRegistry} to obtain an adapter for a
|
||||||
* supported adaptee type or to register additional adapters.
|
* supported adaptee type or to register additional adapters.
|
||||||
|
@ -78,14 +79,12 @@ public interface ReactiveAdapter {
|
||||||
|
|
||||||
private final boolean isNoValue;
|
private final boolean isNoValue;
|
||||||
|
|
||||||
|
|
||||||
public Descriptor(boolean isMultiValue, boolean canBeEmpty, boolean isNoValue) {
|
public Descriptor(boolean isMultiValue, boolean canBeEmpty, boolean isNoValue) {
|
||||||
this.isMultiValue = isMultiValue;
|
this.isMultiValue = isMultiValue;
|
||||||
this.supportsEmpty = canBeEmpty;
|
this.supportsEmpty = canBeEmpty;
|
||||||
this.isNoValue = isNoValue;
|
this.isNoValue = isNoValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return {@code true} if the adaptee implies 0..N values can be produced
|
* Return {@code true} if the adaptee implies 0..N values can be produced
|
||||||
* and is therefore a good fit to adapt to {@link Flux}. A {@code false}
|
* and is therefore a good fit to adapt to {@link Flux}. A {@code false}
|
||||||
|
@ -110,7 +109,6 @@ public interface ReactiveAdapter {
|
||||||
public boolean isNoValue() {
|
public boolean isNoValue() {
|
||||||
return this.isNoValue;
|
return this.isNoValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.core;
|
package org.springframework.core;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
@ -30,7 +31,6 @@ import rx.Completable;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
import rx.Single;
|
import rx.Single;
|
||||||
|
|
||||||
import org.springframework.core.ReactiveAdapter.Descriptor;
|
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,29 +48,25 @@ public class ReactiveAdapterRegistry {
|
||||||
private static final boolean rxJava1Present =
|
private static final boolean rxJava1Present =
|
||||||
ClassUtils.isPresent("rx.Observable", ReactiveAdapterRegistry.class.getClassLoader());
|
ClassUtils.isPresent("rx.Observable", ReactiveAdapterRegistry.class.getClassLoader());
|
||||||
|
|
||||||
|
private final Map<Class<?>, ReactiveAdapter> adapterMap = new LinkedHashMap<>(4);
|
||||||
private final Map<Class<?>, ReactiveAdapter> adapterMap = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a registry and auto-register default adapters.
|
* Create a registry and auto-register default adapters.
|
||||||
*/
|
*/
|
||||||
public ReactiveAdapterRegistry() {
|
public ReactiveAdapterRegistry() {
|
||||||
|
|
||||||
// Flux and Mono ahead of Publisher...
|
// Flux and Mono ahead of Publisher...
|
||||||
registerMonoAdapter(Mono.class,
|
registerMonoAdapter(Mono.class,
|
||||||
source -> (Mono<?>) source, source -> source, new Descriptor(false, true, false));
|
source -> (Mono<?>) source, source -> source,
|
||||||
|
new ReactiveAdapter.Descriptor(false, true, false));
|
||||||
registerFluxAdapter(
|
registerFluxAdapter(
|
||||||
Flux.class, source -> (Flux<?>) source, source -> source);
|
Flux.class, source -> (Flux<?>) source, source -> source);
|
||||||
|
|
||||||
registerFluxAdapter(
|
registerFluxAdapter(
|
||||||
Publisher.class, source -> Flux.from((Publisher<?>) source), source -> source);
|
Publisher.class, source -> Flux.from((Publisher<?>) source), source -> source);
|
||||||
|
|
||||||
registerMonoAdapter(CompletableFuture.class,
|
registerMonoAdapter(CompletableFuture.class,
|
||||||
source -> Mono.fromFuture((CompletableFuture<?>) source),
|
source -> Mono.fromFuture((CompletableFuture<?>) source), Mono::toFuture,
|
||||||
source -> Mono.from((Publisher<?>) source).toFuture(),
|
new ReactiveAdapter.Descriptor(false, true, false)
|
||||||
new Descriptor(false, true, false)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (rxJava1Present) {
|
if (rxJava1Present) {
|
||||||
|
@ -84,9 +80,8 @@ public class ReactiveAdapterRegistry {
|
||||||
* functions can assume that input will never be {@code null} and also that
|
* functions can assume that input will never be {@code null} and also that
|
||||||
* any {@link Optional} wrapper is unwrapped.
|
* any {@link Optional} wrapper is unwrapped.
|
||||||
*/
|
*/
|
||||||
public void registerMonoAdapter(Class<?> adapteeType,
|
public void registerMonoAdapter(Class<?> adapteeType, Function<Object, Mono<?>> toAdapter,
|
||||||
Function<Object, Mono<?>> toAdapter, Function<Mono<?>, Object> fromAdapter,
|
Function<Mono<?>, Object> fromAdapter, ReactiveAdapter.Descriptor descriptor) {
|
||||||
Descriptor descriptor) {
|
|
||||||
|
|
||||||
this.adapterMap.put(adapteeType, new MonoReactiveAdapter(toAdapter, fromAdapter, descriptor));
|
this.adapterMap.put(adapteeType, new MonoReactiveAdapter(toAdapter, fromAdapter, descriptor));
|
||||||
}
|
}
|
||||||
|
@ -96,8 +91,8 @@ public class ReactiveAdapterRegistry {
|
||||||
* functions can assume that input will never be {@code null} and also that
|
* functions can assume that input will never be {@code null} and also that
|
||||||
* any {@link Optional} wrapper is unwrapped.
|
* any {@link Optional} wrapper is unwrapped.
|
||||||
*/
|
*/
|
||||||
public void registerFluxAdapter(Class<?> adapteeType,
|
public void registerFluxAdapter(Class<?> adapteeType, Function<Object, Flux<?>> toAdapter,
|
||||||
Function<Object, Flux<?>> toAdapter, Function<Flux<?>, Object> fromAdapter) {
|
Function<Flux<?>, Object> fromAdapter) {
|
||||||
|
|
||||||
this.adapterMap.put(adapteeType, new FluxReactiveAdapter(toAdapter, fromAdapter));
|
this.adapterMap.put(adapteeType, new FluxReactiveAdapter(toAdapter, fromAdapter));
|
||||||
}
|
}
|
||||||
|
@ -135,6 +130,14 @@ public class ReactiveAdapterRegistry {
|
||||||
return getAdapterInternal(supportedType -> supportedType.equals(actualType));
|
return getAdapterInternal(supportedType -> supportedType.equals(actualType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ReactiveAdapter getAdapterInternal(Predicate<Class<?>> adapteeTypePredicate) {
|
||||||
|
return this.adapterMap.keySet().stream()
|
||||||
|
.filter(adapteeTypePredicate)
|
||||||
|
.map(this.adapterMap::get)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Class<?> getActualType(Class<?> adapteeType, Object adaptee) {
|
private static Class<?> getActualType(Class<?> adapteeType, Object adaptee) {
|
||||||
adaptee = unwrapOptional(adaptee);
|
adaptee = unwrapOptional(adaptee);
|
||||||
|
@ -142,18 +145,7 @@ public class ReactiveAdapterRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object unwrapOptional(Object value) {
|
private static Object unwrapOptional(Object value) {
|
||||||
if (value != null && value instanceof Optional) {
|
return (value instanceof Optional ? ((Optional<?>) value).orElse(null) : value);
|
||||||
value = ((Optional<?>) value).orElse(null);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReactiveAdapter getAdapterInternal(Predicate<Class<?>> adapteeTypePredicate) {
|
|
||||||
return this.adapterMap.keySet().stream()
|
|
||||||
.filter(adapteeTypePredicate)
|
|
||||||
.map(this.adapterMap::get)
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -256,25 +248,23 @@ public class ReactiveAdapterRegistry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class RxJava1AdapterRegistrar {
|
private static class RxJava1AdapterRegistrar {
|
||||||
|
|
||||||
public void register(ReactiveAdapterRegistry registry) {
|
public void register(ReactiveAdapterRegistry registry) {
|
||||||
|
|
||||||
registry.registerFluxAdapter(Observable.class,
|
registry.registerFluxAdapter(Observable.class,
|
||||||
source -> RxJava1Adapter.observableToFlux((Observable<?>) source),
|
source -> RxJava1Adapter.observableToFlux((Observable<?>) source),
|
||||||
RxJava1Adapter::publisherToObservable
|
RxJava1Adapter::publisherToObservable
|
||||||
);
|
);
|
||||||
|
|
||||||
registry.registerMonoAdapter(Single.class,
|
registry.registerMonoAdapter(Single.class,
|
||||||
source -> RxJava1Adapter.singleToMono((Single<?>) source),
|
source -> RxJava1Adapter.singleToMono((Single<?>) source),
|
||||||
RxJava1Adapter::publisherToSingle,
|
RxJava1Adapter::publisherToSingle,
|
||||||
new Descriptor(false, false, false)
|
new ReactiveAdapter.Descriptor(false, false, false)
|
||||||
);
|
);
|
||||||
|
|
||||||
registry.registerMonoAdapter(Completable.class,
|
registry.registerMonoAdapter(Completable.class,
|
||||||
source -> RxJava1Adapter.completableToMono((Completable) source),
|
source -> RxJava1Adapter.completableToMono((Completable) source),
|
||||||
RxJava1Adapter::publisherToCompletable,
|
RxJava1Adapter::publisherToCompletable,
|
||||||
new Descriptor(false, true, true)
|
new ReactiveAdapter.Descriptor(false, true, true)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,10 +30,11 @@ import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||||
import org.springframework.web.context.request.NativeWebRequest;
|
import org.springframework.web.context.request.NativeWebRequest;
|
||||||
import org.springframework.web.context.request.ServletWebRequest;
|
import org.springframework.web.context.request.ServletWebRequest;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test fixture for {@link ContentNegotiationManagerFactoryBean} tests.
|
* Test fixture for {@link ContentNegotiationManagerFactoryBean} tests.
|
||||||
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
*/
|
*/
|
||||||
public class ContentNegotiationManagerFactoryBeanTests {
|
public class ContentNegotiationManagerFactoryBeanTests {
|
||||||
|
@ -119,9 +120,7 @@ public class ContentNegotiationManagerFactoryBeanTests {
|
||||||
assertEquals(Collections.emptyList(), manager.resolveMediaTypes(this.webRequest));
|
assertEquals(Collections.emptyList(), manager.resolveMediaTypes(this.webRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPR-10170
|
@Test(expected = HttpMediaTypeNotAcceptableException.class) // SPR-10170
|
||||||
|
|
||||||
@Test(expected = HttpMediaTypeNotAcceptableException.class)
|
|
||||||
public void favorPathWithIgnoreUnknownPathExtensionTurnedOff() throws Exception {
|
public void favorPathWithIgnoreUnknownPathExtensionTurnedOff() throws Exception {
|
||||||
this.factoryBean.setFavorPathExtension(true);
|
this.factoryBean.setFavorPathExtension(true);
|
||||||
this.factoryBean.setIgnoreUnknownPathExtensions(false);
|
this.factoryBean.setIgnoreUnknownPathExtensions(false);
|
||||||
|
@ -152,9 +151,7 @@ public class ContentNegotiationManagerFactoryBeanTests {
|
||||||
manager.resolveMediaTypes(this.webRequest));
|
manager.resolveMediaTypes(this.webRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPR-10170
|
@Test(expected = HttpMediaTypeNotAcceptableException.class) // SPR-10170
|
||||||
|
|
||||||
@Test(expected = HttpMediaTypeNotAcceptableException.class)
|
|
||||||
public void favorParameterWithUnknownMediaType() throws HttpMediaTypeNotAcceptableException {
|
public void favorParameterWithUnknownMediaType() throws HttpMediaTypeNotAcceptableException {
|
||||||
this.factoryBean.setFavorParameter(true);
|
this.factoryBean.setFavorParameter(true);
|
||||||
this.factoryBean.afterPropertiesSet();
|
this.factoryBean.afterPropertiesSet();
|
||||||
|
@ -188,16 +185,12 @@ public class ContentNegotiationManagerFactoryBeanTests {
|
||||||
manager.resolveMediaTypes(this.webRequest));
|
manager.resolveMediaTypes(this.webRequest));
|
||||||
|
|
||||||
// SPR-10513
|
// SPR-10513
|
||||||
|
|
||||||
this.servletRequest.addHeader("Accept", MediaType.ALL_VALUE);
|
this.servletRequest.addHeader("Accept", MediaType.ALL_VALUE);
|
||||||
|
|
||||||
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON),
|
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON),
|
||||||
manager.resolveMediaTypes(this.webRequest));
|
manager.resolveMediaTypes(this.webRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPR-12286
|
@Test // SPR-12286
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setDefaultContentTypeWithStrategy() throws Exception {
|
public void setDefaultContentTypeWithStrategy() throws Exception {
|
||||||
this.factoryBean.setDefaultContentTypeStrategy(new FixedContentNegotiationStrategy(MediaType.APPLICATION_JSON));
|
this.factoryBean.setDefaultContentTypeStrategy(new FixedContentNegotiationStrategy(MediaType.APPLICATION_JSON));
|
||||||
this.factoryBean.afterPropertiesSet();
|
this.factoryBean.afterPropertiesSet();
|
||||||
|
@ -216,7 +209,6 @@ public class ContentNegotiationManagerFactoryBeanTests {
|
||||||
|
|
||||||
private final Map<String, String> mimeTypes = new HashMap<>();
|
private final Map<String, String> mimeTypes = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
public Map<String, String> getMimeTypes() {
|
public Map<String, String> getMimeTypes() {
|
||||||
return this.mimeTypes;
|
return this.mimeTypes;
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||||
}
|
}
|
||||||
catch (NoSuchMethodException ex) {
|
catch (NoSuchMethodException ex) {
|
||||||
// Should never happen
|
// Should never happen
|
||||||
throw new IllegalStateException("No handler for HTTP OPTIONS", ex);
|
throw new IllegalStateException("Failed to retrieve internal handler method for HTTP OPTIONS", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +183,6 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||||
/**
|
/**
|
||||||
* Iterate all RequestMappingInfo's once again, look if any match by URL at
|
* Iterate all RequestMappingInfo's once again, look if any match by URL at
|
||||||
* least and raise exceptions according to what doesn't match.
|
* least and raise exceptions according to what doesn't match.
|
||||||
*
|
|
||||||
* @throws HttpRequestMethodNotSupportedException if there are matches by URL
|
* @throws HttpRequestMethodNotSupportedException if there are matches by URL
|
||||||
* but not by HTTP method
|
* but not by HTTP method
|
||||||
* @throws HttpMediaTypeNotAcceptableException if there are matches by URL
|
* @throws HttpMediaTypeNotAcceptableException if there are matches by URL
|
||||||
|
@ -243,7 +242,6 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||||
|
|
||||||
private final List<PartialMatch> partialMatches = new ArrayList<>();
|
private final List<PartialMatch> partialMatches = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
public PartialMatchHelper(Set<RequestMappingInfo> infos, HttpServletRequest request) {
|
public PartialMatchHelper(Set<RequestMappingInfo> infos, HttpServletRequest request) {
|
||||||
for (RequestMappingInfo info : infos) {
|
for (RequestMappingInfo info : infos) {
|
||||||
if (info.getPatternsCondition().getMatchingCondition(request) != null) {
|
if (info.getPatternsCondition().getMatchingCondition(request) != null) {
|
||||||
|
@ -252,7 +250,6 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether there any partial matches.
|
* Whether there any partial matches.
|
||||||
*/
|
*/
|
||||||
|
@ -387,20 +384,18 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||||
|
|
||||||
private final boolean paramsMatch;
|
private final boolean paramsMatch;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param info RequestMappingInfo that matches the URL path.
|
* @param info RequestMappingInfo that matches the URL path.
|
||||||
* @param request the current request
|
* @param request the current request
|
||||||
*/
|
*/
|
||||||
public PartialMatch(RequestMappingInfo info, HttpServletRequest request) {
|
public PartialMatch(RequestMappingInfo info, HttpServletRequest request) {
|
||||||
this.info = info;
|
this.info = info;
|
||||||
this.methodsMatch = info.getMethodsCondition().getMatchingCondition(request) != null;
|
this.methodsMatch = (info.getMethodsCondition().getMatchingCondition(request) != null);
|
||||||
this.consumesMatch = info.getConsumesCondition().getMatchingCondition(request) != null;
|
this.consumesMatch = (info.getConsumesCondition().getMatchingCondition(request) != null);
|
||||||
this.producesMatch = info.getProducesCondition().getMatchingCondition(request) != null;
|
this.producesMatch = (info.getProducesCondition().getMatchingCondition(request) != null);
|
||||||
this.paramsMatch = info.getParamsCondition().getMatchingCondition(request) != null;
|
this.paramsMatch = (info.getParamsCondition().getMatchingCondition(request) != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public RequestMappingInfo getInfo() {
|
public RequestMappingInfo getInfo() {
|
||||||
return this.info;
|
return this.info;
|
||||||
}
|
}
|
||||||
|
@ -410,15 +405,15 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasConsumesMatch() {
|
public boolean hasConsumesMatch() {
|
||||||
return hasMethodsMatch() && this.consumesMatch;
|
return (hasMethodsMatch() && this.consumesMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasProducesMatch() {
|
public boolean hasProducesMatch() {
|
||||||
return hasConsumesMatch() && this.producesMatch;
|
return (hasConsumesMatch() && this.producesMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasParamsMatch() {
|
public boolean hasParamsMatch() {
|
||||||
return hasProducesMatch() && this.paramsMatch;
|
return (hasProducesMatch() && this.paramsMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -428,6 +423,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default handler for HTTP OPTIONS.
|
* Default handler for HTTP OPTIONS.
|
||||||
*/
|
*/
|
||||||
|
@ -435,7 +431,6 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
||||||
|
|
||||||
private final HttpHeaders headers = new HttpHeaders();
|
private final HttpHeaders headers = new HttpHeaders();
|
||||||
|
|
||||||
|
|
||||||
public HttpOptionsHandler(Set<String> declaredMethods) {
|
public HttpOptionsHandler(Set<String> declaredMethods) {
|
||||||
this.headers.setAllow(initAllowedHttpMethods(declaredMethods));
|
this.headers.setAllow(initAllowedHttpMethods(declaredMethods));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue