/* * 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.annotation; import java.lang.reflect.Method; import java.time.Duration; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.junit.Test; import reactor.core.publisher.Mono; import rx.Single; import org.springframework.core.MethodIntrospector; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.ui.Model; import org.springframework.util.ReflectionUtils; import org.springframework.validation.Validator; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.bind.support.WebBindingInitializer; import org.springframework.web.bind.support.WebExchangeDataBinder; import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.result.method.InvocableHandlerMethod; import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod; import org.springframework.web.server.ServerWebExchange; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; /** * Unit tests for {@link ModelInitializer}. * @author Rossen Stoyanchev */ public class ModelInitializerTests { private final ModelInitializer modelInitializer = new ModelInitializer(new ReactiveAdapterRegistry()); private final ServerWebExchange exchange = MockServerHttpRequest.get("/path").toExchange(); @SuppressWarnings("unchecked") @Test public void basic() throws Exception { TestController controller = new TestController(); Validator validator = mock(Validator.class); controller.setValidator(validator); List<SyncInvocableHandlerMethod> binderMethods = getBinderMethods(controller); List<InvocableHandlerMethod> attributeMethods = getAttributeMethods(controller); WebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer(); BindingContext bindingContext = new InitBinderBindingContext(bindingInitializer, binderMethods); this.modelInitializer.initModel(bindingContext, attributeMethods, this.exchange).block(Duration.ofMillis(5000)); WebExchangeDataBinder binder = bindingContext.createDataBinder(this.exchange, "name"); assertEquals(Collections.singletonList(validator), binder.getValidators()); Map<String, Object> model = bindingContext.getModel().asMap(); assertEquals(5, model.size()); Object value = model.get("bean"); assertEquals("Bean", ((TestBean) value).getName()); value = model.get("monoBean"); assertEquals("Mono Bean", ((Mono<TestBean>) value).block(Duration.ofMillis(5000)).getName()); value = model.get("singleBean"); assertEquals("Single Bean", ((Single<TestBean>) value).toBlocking().value().getName()); value = model.get("voidMethodBean"); assertEquals("Void Method Bean", ((TestBean) value).getName()); value = model.get("voidMonoMethodBean"); assertEquals("Void Mono Method Bean", ((TestBean) value).getName()); } private List<SyncInvocableHandlerMethod> getBinderMethods(Object controller) { return MethodIntrospector .selectMethods(controller.getClass(), BINDER_METHODS).stream() .map(method -> new SyncInvocableHandlerMethod(controller, method)) .collect(Collectors.toList()); } private List<InvocableHandlerMethod> getAttributeMethods(Object controller) { return MethodIntrospector .selectMethods(controller.getClass(), ATTRIBUTE_METHODS).stream() .map(method -> toInvocable(controller, method)) .collect(Collectors.toList()); } private InvocableHandlerMethod toInvocable(Object controller, Method method) { ModelArgumentResolver resolver = new ModelArgumentResolver(new ReactiveAdapterRegistry()); InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(controller, method); handlerMethod.setArgumentResolvers(Collections.singletonList(resolver)); return handlerMethod; } @SuppressWarnings("unused") private static class TestController { private Validator validator; void setValidator(Validator validator) { this.validator = validator; } @InitBinder public void initDataBinder(WebDataBinder dataBinder) { if (this.validator != null) { dataBinder.addValidators(this.validator); } } @ModelAttribute("bean") public TestBean returnValue() { return new TestBean("Bean"); } @ModelAttribute("monoBean") public Mono<TestBean> returnValueMono() { return Mono.just(new TestBean("Mono Bean")); } @ModelAttribute("singleBean") public Single<TestBean> returnValueSingle() { return Single.just(new TestBean("Single Bean")); } @ModelAttribute public void voidMethodBean(Model model) { model.addAttribute("voidMethodBean", new TestBean("Void Method Bean")); } @ModelAttribute public Mono<Void> voidMonoMethodBean(Model model) { return Mono.just("Void Mono Method Bean") .doOnNext(name -> model.addAttribute("voidMonoMethodBean", new TestBean(name))) .then(); } @RequestMapping public void handle() {} } private static class TestBean { private final String name; TestBean(String name) { this.name = name; } public String getName() { return this.name; } @Override public String toString() { return "TestBean[name=" + this.name + "]"; } } private static final ReflectionUtils.MethodFilter BINDER_METHODS = method -> AnnotationUtils.findAnnotation(method, InitBinder.class) != null; private static final ReflectionUtils.MethodFilter ATTRIBUTE_METHODS = method -> (AnnotationUtils.findAnnotation(method, RequestMapping.class) == null) && (AnnotationUtils.findAnnotation(method, ModelAttribute.class) != null); }