/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jooby.reactor; import static java.util.Objects.requireNonNull; import static javaslang.API.$; import static javaslang.API.Case; import static javaslang.API.Match; import static javaslang.Predicates.instanceOf; import java.util.function.Function; import org.jooby.Deferred; import org.jooby.Env; import org.jooby.Jooby; import org.jooby.Route; import com.google.inject.Binder; import com.typesafe.config.Config; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** * <h1>reactor</h1> * <p> * <a href="http://projectreactor.io">Reactor</a> is a second-generation Reactive library for * building non-blocking applications on the JVM * based on the <a href="http://www.reactive-streams.org">Reactive Streams Specification</a> * </p> * * <h2>exports</h2> * <ul> * <li>map route operator that converts {@link Flux} and {@link Mono} into {@link Deferred} API. * </li> * </ul> * * <h2>usage</h2> * <pre>{@code * * ... * import org.jooby.reactor.Reactor; * ... * * { * use(new Reactor()); * * get("/", req -> Flux.just("reactive programming in jooby!")); * } * }</pre> * * <h2>how it works?</h2> * <p> * Previous example is translated to: * </p> * * <pre>{@code * { * use(new Reactor()); * * get("/", req -> { * * return new Deferred(deferred -> { * Flux.just("reactive programming in jooby!") * .consume(deferred::resolve, deferred::reject); * }); * * }); * } * }</pre> * * <p> * Translation is done via {@link Reactor#reactor()} route mapper. If you are a * <a href="http://projectreactor.io">reactor</a> programmer then you don't need to worry * for learning a new API and semantic. The {@link Reactor#reactor()} route operator deal and take * cares of the {@link Deferred} API. * </p> * * <h2>reactor mapper</h2> * <p> * Advanced flux/mono configuration is allowed via function adapters: * </p> * * <pre>{@code * * ... * import org.jooby.reactor.Reactor; * ... * * { * use(new Reactor() * .withFlux(f -> f.publishOn(Computations.concurrent()) * .withMono(m -> m.publishOn(Computations.concurrent())); * * get("/flux", req -> Flux...); * * get("/mono", req -> Mono...); * * } * }</pre> * * <p> * Here every Flux/Mono from a route handler will publish on the <code>concurrent</code> scheduler. * </p> * * @author edgar * @since 1.0.0.CR3 */ @SuppressWarnings("rawtypes") public class Reactor implements Jooby.Module { private Function<Flux, Flux> flux = Function.identity(); private Function<Mono, Mono> mono = Function.identity(); public Reactor withFlux(final Function<Flux, Flux> adapter) { this.flux = requireNonNull(adapter, "Flux's adapter is required."); return this; } public Reactor withMono(final Function<Mono, Mono> adapter) { this.mono = requireNonNull(adapter, "Mono's adapter is required."); return this; } /** * Map a reactor object like {@link Flux} or {@link Mono} into a {@link Deferred} object. * * <pre>{@code * ... * import org.jooby.reactor.Reactor; * ... * * { * with(() -> { * get("/lux", req -> Flux...); * * get("/mono", req -> Mono...); * * }).map(Reactor.reactor( * flux -> flux.publishOn(Computations.concurrent()), * mono -> mono.publishOn(Computations.concurrent())); * } * }</pre> * * @param flux A flux adapter. * @param mono A mono adapter. * @return A new mapper. */ @SuppressWarnings("unchecked") public static Route.Mapper<Object> reactor(final Function<Flux, Flux> flux, final Function<Mono, Mono> mono) { requireNonNull(flux, "Flux's adapter is required."); requireNonNull(mono, "Mono's adapter is required."); return Route.Mapper.create("reactor", value -> Match(value).of( /** Flux: */ Case(instanceOf(Flux.class), it -> new Deferred(deferred -> flux.apply(it) .consume(deferred::set, deferred::set))), /** Mono: */ Case(instanceOf(Mono.class), it -> new Deferred(deferred -> mono.apply(it) .consume(deferred::set, deferred::set))), /** Ignore */ Case($(), value))); } /** * Map a reactor object like {@link Flux} or {@link Mono} into a {@link Deferred} object. * * <pre>{@code * ... * import org.jooby.reactor.Reactor; * ... * * { * with(() -> { * get("/lux", req -> Flux...); * * get("/mono", req -> Mono...); * * }).map(Reactor.reactor( * flux -> flux.publishOn(Computations.concurrent()), * mono -> mono.publishOn(Computations.concurrent())); * } * }</pre> * * @return A new mapper. */ public static Route.Mapper<Object> reactor() { return reactor(Function.identity(), Function.identity()); } @Override public void configure(final Env env, final Config conf, final Binder binder) { env.router() .map(reactor(flux, mono)); } }