Polishing

This commit is contained in:
Juergen Hoeller 2016-07-22 22:28:20 +02:00
parent e59a5993f3
commit 382a931e7d
4 changed files with 39 additions and 64 deletions

View File

@ -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;
} }
} }
} }

View File

@ -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)
); );
} }
} }

View File

@ -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;
} }

View File

@ -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));
} }