/** * Test a standard {@link Encoder#encode encode} scenario. * * @param input the input to be provided to the encoder * @param inputType the input type * @param stepConsumer a consumer to {@linkplain StepVerifier verify} the output * @param mimeType the mime type to use for decoding. May be {@code null}. * @param hints the hints used for decoding. May be {@code null}. * @param <T> the output type */ @SuppressWarnings("unchecked") protected <T> void testEncode(Publisher<? extends T> input, ResolvableType inputType, Consumer<StepVerifier.FirstStep<DataBuffer>> stepConsumer, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { Flux<DataBuffer> result = encoder().encode(input, this.bufferFactory, inputType, mimeType, hints); StepVerifier.FirstStep<DataBuffer> step = StepVerifier.create(result); stepConsumer.accept(step); }
@Override public boolean canWrite(ResolvableType elementType, @Nullable MediaType mediaType) { return this.encoder.canEncode(elementType, mediaType); }
private HttpMessageWriter<String> getWriter(Flux<DataBuffer> encodedStream, MimeType... mimeTypes) { List<MimeType> typeList = Arrays.asList(mimeTypes); when(this.encoder.getEncodableMimeTypes()).thenReturn(typeList); when(this.encoder.encode(any(), any(), any(), this.mediaTypeCaptor.capture(), any())).thenReturn(encodedStream); return new EncoderHttpMessageWriter<>(this.encoder); }
/** * Create an instance wrapping the given {@link Encoder}. */ public EncoderHttpMessageWriter(Encoder<T> encoder) { Assert.notNull(encoder, "Encoder is required"); initLogger(encoder); this.encoder = encoder; this.mediaTypes = MediaType.asMediaTypes(encoder.getEncodableMimeTypes()); this.defaultMediaType = initDefaultMediaType(this.mediaTypes); }
/** * Create an instance wrapping the given {@link Encoder}. */ public EncoderHttpMessageWriter(Encoder<T> encoder) { Assert.notNull(encoder, "Encoder is required"); this.encoder = encoder; this.mediaTypes = MediaType.asMediaTypes(encoder.getEncodableMimeTypes()); this.defaultMediaType = initDefaultMediaType(this.mediaTypes); initLogger(encoder); }
@SuppressWarnings("unchecked") private <T> Flux<DataBuffer> encodeData(@Nullable T data, ResolvableType valueType, MediaType mediaType, DataBufferFactory factory, Map<String, Object> hints) { if (data == null) { return Flux.empty(); } if (data instanceof String) { String text = (String) data; return Flux.from(encodeText(StringUtils.replace(text, "\n", "\ndata:") + "\n", mediaType, factory)); } if (this.encoder == null) { return Flux.error(new CodecException("No SSE encoder configured and the data is not String.")); } return ((Encoder<T>) this.encoder) .encode(Mono.just(data), factory, valueType, mediaType, hints) .concatWith(encodeText("\n", mediaType, factory)); }
@Override public boolean canWrite(ResolvableType elementType, @Nullable MediaType mediaType) { return this.encoder.canEncode(elementType, mediaType); }
/** * Create an instance wrapping the given {@link Encoder}. */ public EncoderHttpMessageWriter(Encoder<T> encoder) { Assert.notNull(encoder, "Encoder is required"); this.encoder = encoder; this.mediaTypes = MediaType.asMediaTypes(encoder.getEncodableMimeTypes()); this.defaultMediaType = initDefaultMediaType(this.mediaTypes); }
@SuppressWarnings("unchecked") @Override public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType elementType, @Nullable MediaType mediaType, ReactiveHttpOutputMessage message, Map<String, Object> hints) { MediaType contentType = updateContentType(message, mediaType); Flux<DataBuffer> body = this.encoder.encode( inputStream, message.bufferFactory(), elementType, contentType, hints); if (inputStream instanceof Mono) { HttpHeaders headers = message.getHeaders(); return Mono.from(body) .switchIfEmpty(Mono.defer(() -> { headers.setContentLength(0); return message.setComplete().then(Mono.empty()); })) .flatMap(buffer -> { headers.setContentLength(buffer.readableByteCount()); return message.writeWith(Mono.just(buffer)); }); } return (isStreamingMediaType(contentType) ? message.writeAndFlushWith(body.map(Flux::just)) : message.writeWith(body)); }
private void assertStringEncoder(Encoder<?> encoder, boolean textOnly) { assertEquals(CharSequenceEncoder.class, encoder.getClass()); assertTrue(encoder.canEncode(forClass(String.class), MimeTypeUtils.TEXT_PLAIN)); assertEquals(!textOnly, encoder.canEncode(forClass(String.class), MediaType.TEXT_EVENT_STREAM)); }
/** * Create an instance wrapping the given {@link Encoder}. */ public EncoderHttpMessageWriter(Encoder<T> encoder) { Assert.notNull(encoder, "Encoder is required"); this.encoder = encoder; this.mediaTypes = MediaType.asMediaTypes(encoder.getEncodableMimeTypes()); this.defaultMediaType = initDefaultMediaType(this.mediaTypes); initLogger(encoder); }
@Override protected void testEncodeError(Publisher<?> input, ResolvableType outputType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { Flux<Resource> i = Flux.error(new InputException()); Flux<DataBuffer> result = ((Encoder<Resource>) this.encoder).encode(i, this.bufferFactory, outputType, mimeType, hints); StepVerifier.create(result) .expectError(InputException.class) .verify(); }
private void assertStringEncoder(Encoder<?> encoder, boolean textOnly) { assertEquals(CharSequenceEncoder.class, encoder.getClass()); assertTrue(encoder.canEncode(forClass(String.class), MimeTypeUtils.TEXT_PLAIN)); assertEquals(!textOnly, encoder.canEncode(forClass(String.class), MediaType.TEXT_EVENT_STREAM)); }
/** * Test a {@link Encoder#encode encode} scenario where the input stream is empty. * The output is expected to be empty as well. * * @param inputType the input type * @param mimeType the mime type to use for decoding. May be {@code null}. * @param hints the hints used for decoding. May be {@code null}. */ protected void testEncodeEmpty(ResolvableType inputType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { Flux<?> input = Flux.empty(); Flux<DataBuffer> result = encoder().encode(input, this.bufferFactory, inputType, mimeType, hints); StepVerifier.create(result) .verifyComplete(); }
private void assertStringEncoder(Encoder<?> encoder, boolean textOnly) { assertEquals(CharSequenceEncoder.class, encoder.getClass()); assertTrue(encoder.canEncode(ResolvableType.forClass(String.class), MimeTypeUtils.TEXT_PLAIN)); assertEquals(!textOnly, encoder.canEncode(ResolvableType.forClass(String.class), MediaType.TEXT_EVENT_STREAM)); }
@SuppressWarnings("unchecked") private <T> Flux<DataBuffer> encodeData(@Nullable T data, ResolvableType valueType, MediaType mediaType, DataBufferFactory factory, Map<String, Object> hints) { if (data == null) { return Flux.empty(); } if (data instanceof String) { String text = (String) data; return Flux.from(encodeText(StringUtils.replace(text, "\n", "\ndata:") + "\n", mediaType, factory)); } if (this.encoder == null) { return Flux.error(new CodecException("No SSE encoder configured and the data is not String.")); } return ((Encoder<T>) this.encoder) .encode(Mono.just(data), factory, valueType, mediaType, hints) .concatWith(encodeText("\n", mediaType, factory)); }
@Test public void defaultsOffWithCustomWriters() { Encoder<?> customEncoder1 = mock(Encoder.class); Encoder<?> customEncoder2 = mock(Encoder.class); when(customEncoder1.canEncode(ResolvableType.forClass(Object.class), null)).thenReturn(false); when(customEncoder2.canEncode(ResolvableType.forClass(Object.class), null)).thenReturn(true); HttpMessageWriter<?> customWriter1 = mock(HttpMessageWriter.class); HttpMessageWriter<?> customWriter2 = mock(HttpMessageWriter.class); when(customWriter1.canWrite(ResolvableType.forClass(Object.class), null)).thenReturn(false); when(customWriter2.canWrite(ResolvableType.forClass(Object.class), null)).thenReturn(true); this.configurer.customCodecs().encoder(customEncoder1); this.configurer.customCodecs().encoder(customEncoder2); this.configurer.customCodecs().writer(customWriter1); this.configurer.customCodecs().writer(customWriter2); this.configurer.registerDefaults(false); List<HttpMessageWriter<?>> writers = this.configurer.getWriters(); assertEquals(4, writers.size()); assertSame(customEncoder1, getNextEncoder(writers)); assertSame(customWriter1, writers.get(this.index.getAndIncrement())); assertSame(customEncoder2, getNextEncoder(writers)); assertSame(customWriter2, writers.get(this.index.getAndIncrement())); }
/** * Test a {@link Encoder#encode encode} scenario where the input stream is canceled. * This test method will feed the first element of the {@code input} stream to the decoder, * followed by a cancel signal. * The result is expected to contain one "normal" element. * * @param input the input to be provided to the encoder * @param inputType the input type * @param mimeType the mime type to use for decoding. May be {@code null}. * @param hints the hints used for decoding. May be {@code null}. */ protected void testEncodeCancel(Publisher<?> input, ResolvableType inputType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { Flux<DataBuffer> result = encoder().encode(input, this.bufferFactory, inputType, mimeType, hints); StepVerifier.create(result) .consumeNextWith(DataBufferUtils::release) .thenCancel() .verify(); }
@SuppressWarnings("unchecked") @Override public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType elementType, @Nullable MediaType mediaType, ReactiveHttpOutputMessage message, Map<String, Object> hints) { MediaType contentType = updateContentType(message, mediaType); Flux<DataBuffer> body = this.encoder.encode( inputStream, message.bufferFactory(), elementType, contentType, hints); if (inputStream instanceof Mono) { HttpHeaders headers = message.getHeaders(); return Mono.from(body) .switchIfEmpty(Mono.defer(() -> { headers.setContentLength(0); return message.setComplete().then(Mono.empty()); })) .flatMap(buffer -> { headers.setContentLength(buffer.readableByteCount()); return message.writeWith(Mono.just(buffer)); }); } return (isStreamingMediaType(contentType) ? message.writeAndFlushWith(body.map(Flux::just)) : message.writeWith(body)); }