@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()); }
@Test public void handleReturnTypes() throws Exception { Object returnValue = ok("abc"); MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); testHandle(returnValue, returnType); returnType = on(TestController.class).resolveReturnType(Object.class); testHandle(returnValue, returnType); returnValue = Mono.just(ok("abc")); returnType = on(TestController.class).resolveReturnType(Mono.class, entity(String.class)); testHandle(returnValue, returnType); returnValue = Mono.just(ok("abc")); returnType = on(TestController.class).resolveReturnType(Single.class, entity(String.class)); testHandle(returnValue, returnType); returnValue = Mono.just(ok("abc")); returnType = on(TestController.class).resolveReturnType(CompletableFuture.class, entity(String.class)); testHandle(returnValue, returnType); }
/** * Filter on methods not annotated with the given annotation type. */ @SafeVarargs public final Builder<T> annotNotPresent(Class<? extends Annotation>... annotationTypes) { String message = "annotationNotPresent=" + Arrays.toString(annotationTypes); addFilter(message, method -> { if (annotationTypes.length != 0) { return Arrays.stream(annotationTypes).noneMatch(annotType -> AnnotatedElementUtils.findMergedAnnotation(method, annotType) != null); } else { return method.getAnnotations().length == 0; } }); return this; }
returnType = on(Handler.class).resolveReturnType(View.class); returnValue = new TestView("account"); testHandle("/path", returnType, returnValue, "account: {id=123}"); returnType = on(Handler.class).resolveReturnType(Mono.class, View.class); returnValue = Mono.just(new TestView("account")); testHandle("/path", returnType, returnValue, "account: {id=123}"); returnType = on(Handler.class).annotNotPresent(ModelAttribute.class).resolveReturnType(String.class); returnValue = "account"; testHandle("/path", returnType, returnValue, "account: {id=123}", resolver); returnType = on(Handler.class).annotPresent(ModelAttribute.class).resolveReturnType(String.class); returnValue = "123"; testHandle("/account", returnType, returnValue, "account: {id=123, myString=123}", resolver); returnType = on(Handler.class).resolveReturnType(Mono.class, String.class); returnValue = Mono.just("account"); testHandle("/path", returnType, returnValue, "account: {id=123}", resolver); returnType = on(Handler.class).resolveReturnType(Model.class); returnValue = new ConcurrentModel().addAttribute("name", "Joe").addAttribute("ignore", null); testHandle("/account", returnType, returnValue, "account: {id=123, name=Joe}", resolver); returnType = on(Handler.class).annotNotPresent(ModelAttribute.class).resolveReturnType(Map.class); returnValue = Collections.singletonMap("name", "Joe"); testHandle("/account", returnType, returnValue, "account: {id=123, name=Joe}", resolver); returnType = on(Handler.class).annotPresent(ModelAttribute.class).resolveReturnType(Map.class);
@Test public void responseBodyEmitter() throws Exception { MethodParameter type = on(TestController.class).resolveReturnType(ResponseBodyEmitter.class); ResponseBodyEmitter emitter = new ResponseBodyEmitter(); this.handler.handleReturnValue(emitter, type, this.mavContainer, this.webRequest); assertTrue(this.request.isAsyncStarted()); assertEquals("", this.response.getContentAsString()); SimpleBean bean = new SimpleBean(); bean.setId(1L); bean.setName("Joe"); emitter.send(bean); emitter.send("\n"); bean.setId(2L); bean.setName("John"); emitter.send(bean); emitter.send("\n"); bean.setId(3L); bean.setName("Jason"); emitter.send(bean); assertEquals("{\"id\":1,\"name\":\"Joe\"}\n" + "{\"id\":2,\"name\":\"John\"}\n" + "{\"id\":3,\"name\":\"Jason\"}", this.response.getContentAsString()); MockAsyncContext asyncContext = (MockAsyncContext) this.request.getAsyncContext(); assertNull(asyncContext.getDispatchedPath()); emitter.complete(); assertNotNull(asyncContext.getDispatchedPath()); }
@Test public void sseEmitter() throws Exception { MethodParameter type = on(TestController.class).resolveReturnType(SseEmitter.class); SseEmitter emitter = new SseEmitter(); this.handler.handleReturnValue(emitter, type, this.mavContainer, this.webRequest); assertTrue(this.request.isAsyncStarted()); assertEquals(200, this.response.getStatus()); assertEquals("text/event-stream;charset=UTF-8", this.response.getContentType()); SimpleBean bean1 = new SimpleBean(); bean1.setId(1L); bean1.setName("Joe"); SimpleBean bean2 = new SimpleBean(); bean2.setId(2L); bean2.setName("John"); emitter.send(SseEmitter.event(). comment("a test").name("update").id("1").reconnectTime(5000L).data(bean1).data(bean2)); assertEquals(":a test\n" + "event:update\n" + "id:1\n" + "retry:5000\n" + "data:{\"id\":1,\"name\":\"Joe\"}\n" + "data:{\"id\":2,\"name\":\"John\"}\n" + "\n", this.response.getContentAsString()); }
@Test public void supports() { testSupports(on(Handler.class).annotPresent(ModelAttribute.class).resolveReturnType(String.class)); testSupports(on(Handler.class).annotNotPresent(ModelAttribute.class).resolveReturnType(String.class)); testSupports(on(Handler.class).resolveReturnType(Mono.class, String.class)); testSupports(on(Handler.class).resolveReturnType(Rendering.class)); testSupports(on(Handler.class).resolveReturnType(Mono.class, Rendering.class)); testSupports(on(Handler.class).resolveReturnType(View.class)); testSupports(on(Handler.class).resolveReturnType(Mono.class, View.class)); testSupports(on(Handler.class).resolveReturnType(void.class)); testSupports(on(Handler.class).resolveReturnType(Mono.class, Void.class)); testSupports(on(Handler.class).resolveReturnType(Completable.class)); testSupports(on(Handler.class).resolveReturnType(Model.class)); testSupports(on(Handler.class).annotPresent(ModelAttribute.class).resolveReturnType(Map.class)); testSupports(on(Handler.class).annotNotPresent(ModelAttribute.class).resolveReturnType(Map.class)); testSupports(on(Handler.class).resolveReturnType(TestBean.class)); testSupports(on(Handler.class).annotPresent(ModelAttribute.class).resolveReturnType(Long.class)); testDoesNotSupport(on(Handler.class).annotNotPresent(ModelAttribute.class).resolveReturnType(Long.class)); // SPR-15464 testSupports(on(Handler.class).resolveReturnType(Mono.class)); }
@Test public void supports() throws Exception { Object value = null; MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); assertTrue(this.resultHandler.supports(handlerResult(value, returnType))); returnType = on(TestController.class).resolveReturnType(Mono.class, entity(String.class)); assertTrue(this.resultHandler.supports(handlerResult(value, returnType))); returnType = on(TestController.class).resolveReturnType(Single.class, entity(String.class)); assertTrue(this.resultHandler.supports(handlerResult(value, returnType))); returnType = on(TestController.class).resolveReturnType(CompletableFuture.class, entity(String.class)); assertTrue(this.resultHandler.supports(handlerResult(value, returnType))); returnType = on(TestController.class).resolveReturnType(HttpHeaders.class); assertTrue(this.resultHandler.supports(handlerResult(value, returnType))); // SPR-15785 value = ResponseEntity.ok("testing"); returnType = on(TestController.class).resolveReturnType(Object.class); assertTrue(this.resultHandler.supports(handlerResult(value, returnType))); }
@Test public void contentNegotiation() { TestBean value = new TestBean("Joe"); MethodParameter returnType = on(Handler.class).resolveReturnType(TestBean.class); HandlerResult handlerResult = new HandlerResult(new Object(), value, returnType, this.bindingContext); MockServerWebExchange exchange = MockServerWebExchange.from(get("/account").accept(APPLICATION_JSON)); TestView defaultView = new TestView("jsonView", APPLICATION_JSON); resultHandler(Collections.singletonList(defaultView), new TestViewResolver("account")) .handleResult(exchange, handlerResult) .block(Duration.ofSeconds(5)); assertEquals(APPLICATION_JSON, exchange.getResponse().getHeaders().getContentType()); assertResponseBody(exchange, "jsonView: {" + "org.springframework.validation.BindingResult.testBean=" + "org.springframework.validation.BeanPropertyBindingResult: 0 errors, " + "testBean=TestBean[name=Joe]" + "}"); }
@Test public void responseEntityFlux() throws Exception { EmitterProcessor<String> processor = EmitterProcessor.create(); ResponseEntity<Flux<String>> entity = ResponseEntity.ok().body(processor); ResolvableType bodyType = forClassWithGenerics(Flux.class, String.class); MethodParameter type = on(TestController.class).resolveReturnType(ResponseEntity.class, bodyType); this.handler.handleReturnValue(entity, type, this.mavContainer, this.webRequest); assertTrue(this.request.isAsyncStarted()); assertEquals(200, this.response.getStatus()); assertEquals("text/plain", this.response.getContentType()); processor.onNext("foo"); processor.onNext("bar"); processor.onNext("baz"); processor.onComplete(); assertEquals("foobarbaz", this.response.getContentAsString()); }
@Test public void handleReturnValueChangedETagAndLastModified() throws Exception { String etag = "\"deadb33f8badf00d\""; String newEtag = "\"changed-etag-value\""; Instant currentTime = Instant.now().truncatedTo(ChronoUnit.SECONDS); Instant oneMinAgo = currentTime.minusSeconds(60); MockServerWebExchange exchange = MockServerWebExchange.from(get("/path") .ifNoneMatch(etag) .ifModifiedSince(currentTime.toEpochMilli()) ); ResponseEntity<String> entity = ok().eTag(newEtag).lastModified(oneMinAgo.toEpochMilli()).body("body"); MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); HandlerResult result = handlerResult(entity, returnType); this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); assertConditionalResponse(exchange, HttpStatus.OK, "body", newEtag, oneMinAgo); }
@Test public void clearModelAttributeFromSession() throws Exception { WebSession session = this.exchange.getSession().block(Duration.ZERO); assertNotNull(session); TestBean testBean = new TestBean("Session Bean"); session.getAttributes().put("bean", testBean); 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)); context.getSessionStatus().setComplete(); context.saveModel(); assertEquals(0, session.getAttributes().size()); }
@Test public void retrieveModelAttributeFromSession() throws Exception { WebSession session = this.exchange.getSession().block(Duration.ZERO); assertNotNull(session); TestBean testBean = new TestBean("Session Bean"); session.getAttributes().put("bean", testBean); 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)); context.saveModel(); assertEquals(1, session.getAttributes().size()); assertEquals("Session Bean", ((TestBean) session.getRequiredAttribute("bean")).getName()); }
@Test public void handleReturnValueETagAndLastModified() throws Exception { String eTag = "\"deadb33f8badf00d\""; Instant currentTime = Instant.now().truncatedTo(ChronoUnit.SECONDS); Instant oneMinAgo = currentTime.minusSeconds(60); MockServerWebExchange exchange = MockServerWebExchange.from(get("/path") .ifNoneMatch(eTag) .ifModifiedSince(currentTime.toEpochMilli()) ); ResponseEntity<String> entity = ok().eTag(eTag).lastModified(oneMinAgo.toEpochMilli()).body("body"); MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); HandlerResult result = handlerResult(entity, returnType); this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); assertConditionalResponse(exchange, HttpStatus.NOT_MODIFIED, null, eTag, oneMinAgo); }
@SuppressWarnings("unchecked") @Test public void responseBodyEmitterWithErrorValue() throws Exception { AsyncWebRequest asyncWebRequest = mock(AsyncWebRequest.class); WebAsyncUtils.getAsyncManager(this.request).setAsyncWebRequest(asyncWebRequest); ResponseBodyEmitter emitter = new ResponseBodyEmitter(19000L); emitter.onError(mock(Consumer.class)); emitter.onCompletion(mock(Runnable.class)); MethodParameter type = on(TestController.class).resolveReturnType(ResponseBodyEmitter.class); this.handler.handleReturnValue(emitter, type, this.mavContainer, this.webRequest); verify(asyncWebRequest).addErrorHandler(any(Consumer.class)); verify(asyncWebRequest, times(2)).addCompletionHandler(any(Runnable.class)); verify(asyncWebRequest).startAsync(); }
@Test public void voidReturnType() throws Exception { testVoid(null, on(TestController.class).resolveReturnType(void.class)); testVoid(Mono.empty(), on(TestController.class).resolveReturnType(Mono.class, Void.class)); testVoid(Flux.empty(), on(TestController.class).resolveReturnType(Flux.class, Void.class)); testVoid(Completable.complete(), on(TestController.class).resolveReturnType(Completable.class)); testVoid(Observable.empty(), on(TestController.class).resolveReturnType(Observable.class, Void.class)); MethodParameter type = on(TestController.class).resolveReturnType(io.reactivex.Completable.class); testVoid(io.reactivex.Completable.complete(), type); type = on(TestController.class).resolveReturnType(io.reactivex.Observable.class, Void.class); testVoid(io.reactivex.Observable.empty(), type); type = on(TestController.class).resolveReturnType(Flowable.class, Void.class); testVoid(Flowable.empty(), type); }
@Test public void responseBodyFlux() throws Exception { this.request.addHeader("Accept", "text/event-stream"); MethodParameter type = on(TestController.class).resolveReturnType(Flux.class, String.class); EmitterProcessor<String> processor = EmitterProcessor.create(); this.handler.handleReturnValue(processor, type, this.mavContainer, this.webRequest); assertTrue(this.request.isAsyncStarted()); assertEquals(200, this.response.getStatus()); assertEquals("text/event-stream;charset=UTF-8", this.response.getContentType()); processor.onNext("foo"); processor.onNext("bar"); processor.onNext("baz"); processor.onComplete(); assertEquals("data:foo\n\ndata:bar\n\ndata:baz\n\n", this.response.getContentAsString()); }
@Test public void supportsReturnTypes() throws Exception { assertTrue(this.handler.supportsReturnType( on(TestController.class).resolveReturnType(ResponseBodyEmitter.class))); assertTrue(this.handler.supportsReturnType( on(TestController.class).resolveReturnType(SseEmitter.class))); assertTrue(this.handler.supportsReturnType( on(TestController.class).resolveReturnType(ResponseEntity.class, ResponseBodyEmitter.class))); assertTrue(this.handler.supportsReturnType( on(TestController.class).resolveReturnType(Flux.class, String.class))); assertTrue(this.handler.supportsReturnType( on(TestController.class).resolveReturnType(forClassWithGenerics(ResponseEntity.class, forClassWithGenerics(Flux.class, String.class))))); }
@Test public void responseBodyEmitterWithTimeoutValue() throws Exception { AsyncWebRequest asyncWebRequest = mock(AsyncWebRequest.class); WebAsyncUtils.getAsyncManager(this.request).setAsyncWebRequest(asyncWebRequest); ResponseBodyEmitter emitter = new ResponseBodyEmitter(19000L); emitter.onTimeout(mock(Runnable.class)); emitter.onCompletion(mock(Runnable.class)); MethodParameter type = on(TestController.class).resolveReturnType(ResponseBodyEmitter.class); this.handler.handleReturnValue(emitter, type, this.mavContainer, this.webRequest); verify(asyncWebRequest).setTimeout(19000L); verify(asyncWebRequest).addTimeoutHandler(any(Runnable.class)); verify(asyncWebRequest, times(2)).addCompletionHandler(any(Runnable.class)); verify(asyncWebRequest).startAsync(); }
@Test public void saveModelAttributeToSession() 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)); WebSession session = this.exchange.getSession().block(Duration.ZERO); assertNotNull(session); assertEquals(0, session.getAttributes().size()); context.saveModel(); assertEquals(1, session.getAttributes().size()); assertEquals("Bean", ((TestBean) session.getRequiredAttribute("bean")).getName()); }