private boolean isMatch(Map.Entry<String, ?> entry) { if (entry.getValue() == null) { return false; } if (!getModelKeys().isEmpty() && !getModelKeys().contains(entry.getKey())) { return false; } ResolvableType type = ResolvableType.forInstance(entry.getValue()); return getMessageWriter().canWrite(type, null); }
@Override @SuppressWarnings("unchecked") public Mono<Void> render( @Nullable Map<String, ?> model, @Nullable MediaType contentType, ServerWebExchange exchange) { Object value = getObjectToRender(model); return (value != null ? write(value, contentType, exchange) : exchange.getResponse().setComplete()); }
private String doRender() { this.view.render(this.model, MediaType.APPLICATION_JSON, this.exchange).block(Duration.ZERO); return this.exchange.getResponse().getBodyAsString().block(Duration.ZERO); }
protected Object extractObjectToRender(Map<String, ?> model) { Map<String, Object> map = new HashMap<>(model.size()); for (Map.Entry<String, ?> entry : model.entrySet()) { if (isEligibleAttribute(entry.getKey(), entry.getValue())) { map.put(entry.getKey(), entry.getValue()); } } if (map.isEmpty()) { return null; } else if (map.size() == 1) { return map.values().iterator().next(); } else if (getMessageWriter().canWrite(ResolvableType.forClass(Map.class), null)) { return map; } else { throw new IllegalStateException( "Multiple matching attributes found: " + map + ". " + "However Map rendering is not supported by " + getMessageWriter()); } }
@Test public void multipleMatchesNotSupported() throws Exception { this.view = new HttpMessageWriterView(CharSequenceEncoder.allMimeTypes()); this.view.setModelKeys(new HashSet<>(Arrays.asList("foo1", "foo2"))); this.model.addAttribute("foo1", "bar1"); this.model.addAttribute("foo2", "bar2"); try { doRender(); fail(); } catch (IllegalStateException ex) { String message = ex.getMessage(); assertTrue(message, message.contains("Map rendering is not supported")); } }
@Nullable private Object getObjectToRender(@Nullable Map<String, ?> model) { if (model == null) { return null; } Map<String, ?> result = model.entrySet().stream() .filter(this::isMatch) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); if (result.isEmpty()) { return null; } else if (result.size() == 1) { return result.values().iterator().next(); } else if (this.canWriteMap) { return result; } else { throw new IllegalStateException("Multiple matches found: " + result + " but " + "Map rendering is not supported by " + getMessageWriter().getClass().getName()); } }
@Test public void render() throws Exception { Map<String, String> pojoData = new LinkedHashMap<>(); pojoData.put("foo", "f"); pojoData.put("bar", "b"); this.model.addAttribute("pojoData", pojoData); this.view.setModelKeys(Collections.singleton("pojoData")); this.view.render(this.model, MediaType.APPLICATION_JSON, exchange).block(Duration.ZERO); StepVerifier.create(this.exchange.getResponse().getBody()) .consumeNextWith(buf -> assertEquals("{\"foo\":\"f\",\"bar\":\"b\"}", dumpString(buf))) .expectComplete() .verify(); }
@Override public Mono<Void> render(Map<String, ?> model, MediaType contentType, ServerWebExchange exchange) { Object value = extractObjectToRender(model); return applyMessageWriter(value, contentType, exchange); }
@Test public void multipleMatches() throws Exception { this.view.setModelKeys(new HashSet<>(Arrays.asList("foo1", "foo2"))); this.model.addAttribute("foo1", Collections.singleton("bar1")); this.model.addAttribute("foo2", Collections.singleton("bar2")); this.model.addAttribute("foo3", Collections.singleton("bar3")); assertEquals("{\"foo1\":[\"bar1\"],\"foo2\":[\"bar2\"]}", doRender()); }
@Override protected void configureViewResolvers(ViewResolverRegistry registry) { registry.freeMarker(); registry.defaultViews(new HttpMessageWriterView(new Jackson2JsonEncoder())); }
@Test public void supportedMediaTypes() throws Exception { assertEquals(Arrays.asList( MediaType.parseMediaType("application/json;charset=UTF-8"), MediaType.parseMediaType("application/*+json;charset=UTF-8")), this.view.getSupportedMediaTypes()); }
@Test public void noMatchBecauseNotSupported() throws Exception { this.view = new HttpMessageWriterView(new Jaxb2XmlEncoder()); this.view.setModelKeys(new HashSet<>(Collections.singletonList("foo1"))); this.model.addAttribute("foo1", "bar1"); assertEquals("", doRender()); }
@SuppressWarnings("unchecked") private <T> Mono<Void> applyMessageWriter(Object value, MediaType contentType, ServerWebExchange exchange) { if (value == null) { return Mono.empty(); } Publisher<? extends T> stream = Mono.just((T) value); ResolvableType type = ResolvableType.forClass(value.getClass()); ServerHttpResponse response = exchange.getResponse(); return ((HttpMessageWriter<T>) getMessageWriter()).write(stream, type, contentType, response, Collections.emptyMap()); }
@Test public void singleMatch() throws Exception { this.view.setModelKeys(Collections.singleton("foo2")); this.model.addAttribute("foo1", Collections.singleton("bar1")); this.model.addAttribute("foo2", Collections.singleton("bar2")); this.model.addAttribute("foo3", Collections.singleton("bar3")); assertEquals("[\"bar2\"]", doRender()); }
@Test public void defaultViews() throws Exception { View view = new HttpMessageWriterView(new Jackson2JsonEncoder()); this.registry.defaultViews(view); assertEquals(1, this.registry.getDefaultViews().size()); assertSame(view, this.registry.getDefaultViews().get(0)); }
/** * Whether the given model attribute key-value pair is eligible for encoding. * <p>The default implementation checks against the configured * {@link #setModelKeys model keys} and whether the Encoder supports the * value type. */ protected boolean isEligibleAttribute(String attributeName, Object attributeValue) { ResolvableType type = ResolvableType.forClass(attributeValue.getClass()); if (getModelKeys().isEmpty()) { return getMessageWriter().canWrite(type, null); } if (getModelKeys().contains(attributeName)) { if (getMessageWriter().canWrite(type, null)) { return true; } throw new IllegalStateException( "Model object [" + attributeValue + "] retrieved via key " + "[" + attributeName + "] is not supported by " + getMessageWriter()); } return false; }
@Test public void noMatch() throws Exception { this.view.setModelKeys(Collections.singleton("foo2")); this.model.addAttribute("foo1", "bar1"); assertEquals("", doRender()); }