package org.zalando.riptide.stream;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.zalando.fauxpas.ThrowingConsumer;
import org.zalando.riptide.Route;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
/**
* Main entry point for <b>Riptide Streams</b> extension to capture arbitrary infinite object streams. It allows to
* receive infinite streams using application/x-json-stream and application/json-seq format, as well as simple finite
* streams from lists and arrays. The feature must be enabled by registering the {@link StreamConverter} with Riptide
* (using {@link Streams#streamConverter()}) and declare a route for your stream that is calling a the stream consumer
* as follows:
*
* <pre>
* try (Rest rest = Rest.builder().baseUrl("https://api.github.com").converter(streamConverter()).build()) {
* rest.get("/repos/{org}/{repo}/contributors", "zalando", "riptide")
* .accept(MediaType.APPLICATION_JSON)
* .dispatch(series(),
* on(SUCCESSFUL).call(streamOf(User.class),
* forEach(user -> println(user.login + " (" + user.contributions + ")"))))
* .get();
* }
* </pre>
*
* <b>Note:</b> The stream converter is an replacement to the default spring JSON converter that does not support
* streaming, and thus should be not registered together with it.
*/
public final class Streams {
/** Default singleton {@link MediaType media type} for application/x-json-stream. */
public static final MediaType APPLICATION_X_JSON_STREAM = //
new MediaType("application", "x-json-stream", StandardCharsets.UTF_8);
/** Default singleton {@link MediaType media type} for application/json-seq. */
public static final MediaType APPLICATION_JSON_SEQ = //
new MediaType("application", "json-seq", StandardCharsets.UTF_8);
/**
* Creates specialized stream {@link TypeToken type token} for the given element {@link Class class type}. Used to
* declare the expected stream response {@link TypeToken type token} in Riptide {@link Route route} as follows:
*
* <pre>
* on(...).call(streamOf(Result.class),...)
* </pre>
*
* @param type element class type.
* @return stream token type.
*/
public static <T> TypeToken<Stream<T>> streamOf(final Class<T> type) {
return streamOf(TypeToken.of(type));
}
/**
* Creates specialized stream {@link TypeToken type token} for the given element {@link TypeToken type token}. Used
* to declare the expected stream response {@link TypeToken type token} in Riptide {@link Route route} as follows:
*
* <pre>
* on(...).call(streamOf(resultTypeToken),...)
* </pre>
*
* @param type element token type.
* @return stream token type.
*/
@SuppressWarnings("serial")
public static <T> TypeToken<Stream<T>> streamOf(final TypeToken<T> type) {
final TypeToken<Stream<T>> streamType = new TypeToken<Stream<T>>() {
// no overriding needed.
};
final TypeParameter<T> elementType = new TypeParameter<T>() {
// no overriding needed.
};
return streamType.where(elementType, type);
}
/**
* Creates {@link ThrowingConsumer stream consumer} for given {@link ThrowingConsumer element consumer}. Used to
* standardly wrap a single entity consumer function in a stream consumer function as follows:
*
* <pre>
* on(...).call(streamOf(...), forEach(element -> { println(element); }))
* </pre>
*
* @param consumer element consumer function.
* @return stream consumer function.
*/
public static <I, X extends Throwable> ThrowingConsumer<Stream<I>, X> forEach(final ThrowingConsumer<I, X> consumer) {
return input -> {
if (input == null) {
return;
}
try {
input.forEach(consumer);
} finally {
input.close();
}
};
}
/**
* Create default stream converter.
*
* @deprecated use {@link #streamConverter(ObjectMapper)} or {@link #streamConverter(ObjectMapper, List)}
* @return default stream converter.
*/
@Deprecated
public static HttpMessageConverter<?> streamConverter() {
return streamConverter(new ObjectMapper());
}
/**
* Create stream converter with custom {@link ObjectMapper object mapper).
*
* @param mapper custom {@link ObjectMapper object mapper}.
*
* @return stream converter with customer object mapper.
*/
public static HttpMessageConverter<?> streamConverter(final ObjectMapper mapper) {
return streamConverter(mapper, Arrays.asList(APPLICATION_JSON_SEQ, APPLICATION_X_JSON_STREAM));
}
/**
* Create stream converter with custom {@link ObjectMapper object mapper), and custom list of
* {@link MediaType supported media types}.
*
* @param mapper custom {@link ObjectMapper object mapper}.
* @param supportedMediaTypes custom list of {@link MediaType media types}.
*
* @return stream converter with customer object mapper.
*/
public static HttpMessageConverter<?> streamConverter(final ObjectMapper mapper,
final List<MediaType> supportedMediaTypes) {
return new StreamConverter<>(mapper, supportedMediaTypes);
}
}