DefaultRendering(Object view, @Nullable Model model, @Nullable HttpStatus status, @Nullable HttpHeaders headers) { this.view = view; this.model = (model != null ? model.asMap() : Collections.emptyMap()); this.status = status; this.headers = (headers != null ? headers : EMPTY_HEADERS); }
private void updateBindingContext(BindingContext context, ServerWebExchange exchange) { Map<String, Object> model = context.getModel().asMap(); model.keySet().stream() .filter(name -> isBindingCandidate(name, model.get(name))) .filter(name -> !model.containsKey(BindingResult.MODEL_KEY_PREFIX + name)) .forEach(name -> { WebExchangeDataBinder binder = context.createDataBinder(exchange, model.get(name), name); model.put(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); }); }
@Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue == null) { return; } else if (returnValue instanceof Model) { mavContainer.addAllAttributes(((Model) returnValue).asMap()); } else { // should not happen throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); } }
@Override public Object resolveArgumentValue( MethodParameter parameter, BindingContext context, ServerWebExchange exchange) { Class<?> type = parameter.getParameterType(); if (Model.class.isAssignableFrom(type)) { return context.getModel(); } else if (Map.class.isAssignableFrom(type)) { return context.getModel().asMap(); } else { // Should never happen.. throw new IllegalStateException("Unexpected method parameter type: " + type); } }
private void invokeBinderMethod( WebExchangeDataBinder dataBinder, ServerWebExchange exchange, SyncInvocableHandlerMethod binderMethod) { HandlerResult result = binderMethod.invokeForHandlerResult(exchange, this.binderMethodContext, dataBinder); if (result != null && result.getReturnValue() != null) { throw new IllegalStateException( "@InitBinder methods must not return a value (should be void): " + binderMethod); } // Should not happen (no Model argument resolution) ... if (!this.binderMethodContext.getModel().asMap().isEmpty()) { throw new IllegalStateException( "@InitBinder methods are not allowed to add model attributes: " + binderMethod); } }
@SuppressWarnings("unchecked") <T> T updateAndReturn(Model model, String methodName, T returnValue) throws IOException { ((List<String>) model.asMap().get("methods")).add(methodName); return returnValue; } }
@Test public void modelAttributeAdvice() throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class); RequestMappingHandlerAdapter adapter = createAdapter(context); TestController controller = context.getBean(TestController.class); Model model = handle(adapter, controller, "handle").getModel(); assertEquals(2, model.asMap().size()); assertEquals("lAttr1", model.asMap().get("attr1")); assertEquals("gAttr2", model.asMap().get("attr2")); }
private Object getErrors(MethodParameter parameter, BindingContext context) { Assert.isTrue(parameter.getParameterIndex() > 0, "Errors argument must be declared immediately after a model attribute argument"); int index = parameter.getParameterIndex() - 1; MethodParameter attributeParam = MethodParameter.forExecutable(parameter.getExecutable(), index); ReactiveAdapter adapter = getAdapterRegistry().getAdapter(attributeParam.getParameterType()); Assert.state(adapter == null, "An @ModelAttribute and an Errors/BindingResult argument " + "cannot both be declared with an async type wrapper. " + "Either declare the @ModelAttribute without an async wrapper type or " + "handle a WebExchangeBindException error signal through the async type."); ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); String name = (ann != null && StringUtils.hasText(ann.value()) ? ann.value() : Conventions.getVariableNameForParameter(attributeParam)); Object errors = context.getModel().asMap().get(BindingResult.MODEL_KEY_PREFIX + name); Assert.state(errors != null, () -> "An Errors/BindingResult argument is expected " + "immediately after the @ModelAttribute argument to which it applies. " + "For @RequestBody and @RequestPart arguments, please declare them with a reactive " + "type wrapper and use its onError operators to handle WebExchangeBindException: " + parameter.getMethod()); return errors; }
/** * Provide the context required to apply {@link #saveModel()} after the * controller method has been invoked. */ public void setSessionContext(SessionAttributesHandler attributesHandler, WebSession session) { this.saveModelOperation = () -> { if (getSessionStatus().isComplete()) { attributesHandler.cleanupAttributes(session); } else { attributesHandler.storeAttributes(session, getModel().asMap()); } }; }
private Mono<Void> handleResult(HandlerResult handlerResult, BindingContext bindingContext) { Object value = handlerResult.getReturnValue(); if (value != null) { ResolvableType type = handlerResult.getReturnType(); ReactiveAdapter adapter = this.adapterRegistry.getAdapter(type.resolve(), value); if (isAsyncVoidType(type, adapter)) { return Mono.from(adapter.toPublisher(value)); } String name = getAttributeName(handlerResult.getReturnTypeSource()); bindingContext.getModel().asMap().putIfAbsent(name, value); } return Mono.empty(); }
private ServerWebExchange testHandle(String path, MethodParameter returnType, Object returnValue, String responseBody, ViewResolver... resolvers) { Model model = this.bindingContext.getModel(); model.asMap().clear(); model.addAttribute("id", "123"); HandlerResult result = new HandlerResult(new Object(), returnValue, returnType, this.bindingContext); MockServerWebExchange exchange = MockServerWebExchange.from(get(path)); resultHandler(resolvers).handleResult(exchange, result).block(Duration.ofSeconds(5)); assertResponseBody(exchange, responseBody); return exchange; }
private Mono<?> prepareAttributeMono(String attributeName, ResolvableType attributeType, BindingContext context, ServerWebExchange exchange) { Object attribute = context.getModel().asMap().get(attributeName); if (attribute == null) { attribute = findAndRemoveReactiveAttribute(context.getModel(), attributeName); } if (attribute == null) { return createAttribute(attributeName, attributeType.toClass(), context, exchange); } ReactiveAdapter adapterFrom = getAdapterRegistry().getAdapter(null, attribute); if (adapterFrom != null) { Assert.isTrue(!adapterFrom.isMultiValue(), "Data binding only supports single-value async types"); return Mono.from(adapterFrom.toPublisher(attribute)); } else { return Mono.justOrEmpty(attribute); } }
@Test public void resolve() { BindingResult bindingResult = createBindingResult(new Foo(), "foo"); this.bindingContext.getModel().asMap().put(BindingResult.MODEL_KEY_PREFIX + "foo", bindingResult); MethodParameter parameter = this.testMethod.arg(Errors.class); Object actual = this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange) .block(Duration.ofMillis(5000)); assertSame(bindingResult, actual); }
private void testBindFoo(String modelKey, MethodParameter param, Function<Object, Foo> valueExtractor) throws Exception { Object value = createResolver() .resolveArgument(param, this.bindContext, postForm("name=Robert&age=25")) .block(Duration.ZERO); Foo foo = valueExtractor.apply(value); assertEquals("Robert", foo.getName()); assertEquals(25, foo.getAge()); String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + modelKey; Map<String, Object> map = bindContext.getModel().asMap(); assertEquals(map.toString(), 2, map.size()); assertSame(foo, map.get(modelKey)); assertNotNull(map.get(bindingResultKey)); assertTrue(map.get(bindingResultKey) instanceof BindingResult); }
@Test public void bindExistingMono() throws Exception { Foo foo = new Foo(); foo.setName("Jim"); this.bindContext.getModel().addAttribute("fooMono", Mono.just(foo)); MethodParameter parameter = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Foo.class); testBindFoo("foo", parameter, value -> { assertEquals(Foo.class, value.getClass()); return (Foo) value; }); assertSame(foo, this.bindContext.getModel().asMap().get("foo")); }
@Test public void resolveWithMono() { BindingResult bindingResult = createBindingResult(new Foo(), "foo"); MonoProcessor<BindingResult> monoProcessor = MonoProcessor.create(); monoProcessor.onNext(bindingResult); this.bindingContext.getModel().asMap().put(BindingResult.MODEL_KEY_PREFIX + "foo", monoProcessor); MethodParameter parameter = this.testMethod.arg(Errors.class); Object actual = this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange) .block(Duration.ofMillis(5000)); assertSame(bindingResult, actual); }
@Test public void bindExisting() throws Exception { Foo foo = new Foo(); foo.setName("Jim"); this.bindContext.getModel().addAttribute(foo); MethodParameter parameter = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Foo.class); testBindFoo("foo", parameter, value -> { assertEquals(Foo.class, value.getClass()); return (Foo) value; }); assertSame(foo, this.bindContext.getModel().asMap().get("foo")); }
private void testBindBar(MethodParameter param) throws Exception { Object value = createResolver() .resolveArgument(param, this.bindContext, postForm("name=Robert&age=25&count=1")) .block(Duration.ZERO); Bar bar = (Bar) value; assertEquals("Robert", bar.getName()); assertEquals(25, bar.getAge()); assertEquals(1, bar.getCount()); String key = "bar"; String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + key; Map<String, Object> map = bindContext.getModel().asMap(); assertEquals(map.toString(), 2, map.size()); assertSame(bar, map.get(key)); assertNotNull(map.get(bindingResultKey)); assertTrue(map.get(bindingResultKey) instanceof BindingResult); }
@Test public void bindExistingSingle() throws Exception { Foo foo = new Foo(); foo.setName("Jim"); this.bindContext.getModel().addAttribute("fooSingle", Single.just(foo)); MethodParameter parameter = this.testMethod.annotNotPresent(ModelAttribute.class).arg(Foo.class); testBindFoo("foo", parameter, value -> { assertEquals(Foo.class, value.getClass()); return (Foo) value; }); assertSame(foo, this.bindContext.getModel().asMap().get("foo")); }
@SuppressWarnings("unchecked") @Test public void modelAttributeMethods() throws Exception { TestController controller = new TestController(); InitBinderBindingContext context = getBindingContext(controller); Method method = ResolvableMethod.on(TestController.class).annotPresent(GetMapping.class).resolveMethod(); HandlerMethod handlerMethod = new HandlerMethod(controller, method); this.modelInitializer.initModel(handlerMethod, context, this.exchange).block(Duration.ofMillis(5000)); Map<String, Object> model = context.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()); }