/* * Copyright 2015 the original author or authors. * * Licensed 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 ratpack.exec; import ratpack.api.NonBlocking; import ratpack.exec.internal.DefaultOperation; import ratpack.func.Action; import ratpack.func.Block; import ratpack.func.Factory; import ratpack.func.Function; import java.util.Optional; /** * A logical operation. * <p> * An operation encapsulates a logical piece of work, which will complete some time in the future. * It is similar to a {@link Promise} except that it does not produce a value. * It merely succeeds, or throws an exception. * <p> * The {@link #then(Block)} method allows specifying what should happen after the operation completes. * The {@link #onError(Action)} method allows specifying what should happen if the operation fails. * Like {@link Promise}, the operation will not start until it is subscribed to, via {@link #then(Block)} or {@link #then()}. * <p> * It is common for methods that would naturally return {@code void} to return an {@link Operation} instead, * to allow the method implementation to be effectively asynchronous. * The caller of the method is then expected to use the {@link #then(Block)} method to specify what should happen after the operation * that the method represents finishes. * <pre class="java">{@code * import ratpack.exec.Blocking; * import ratpack.exec.Operation; * import com.google.common.collect.Lists; * import ratpack.test.exec.ExecHarness; * * import java.util.Arrays; * import java.util.List; * * import static org.junit.Assert.assertEquals; * * public class Example { * public static void main(String... args) throws Exception { * List<String> events = Lists.newArrayList(); * ExecHarness.runSingle(e -> * Operation.of(() -> * Blocking.get(() -> events.add("1")) * .then(b -> events.add("2")) * ) * .then(() -> events.add("3")) * ); * assertEquals(Arrays.asList("1", "2", "3"), events); * } * } * }</pre> */ public interface Operation { static Operation of(Block block) { return new DefaultOperation(Promise.sync(() -> { block.execute(); return null; })); } /** * Create an operation that delegates to another operation. * * @param factory a factory for the operation * @return an operation * @since 1.5 */ static Operation flatten(Factory<Operation> factory) { return new DefaultOperation(Promise.flatten(() -> factory.create().promise())); } Operation onError(Action<? super Throwable> onError); /** * Convert an error to a success or different error. * <p> * The given action receives the upstream error and is executed as an operation. * If the operation completes without error, the original error is considered handled * and the returned operation will propagate success. * <p> * If the given action operation throws an exception, * the returned operation will propagate that exception. * <p> * This method differs to {@link #onError(Action)} in that it does not terminate the operation. * * @param action the error handler * @return an operation * @since 1.5 */ default Operation mapError(Action<? super Throwable> action) { return promise().transform(up -> down -> up.connect(new Downstream<Void>() { @Override public void success(Void value) { down.success(value); } @Override public void error(Throwable throwable) { Operation.of(() -> action.execute(throwable)).promise().connect(new Downstream<Void>() { @Override public void success(Void value) { down.success(value); } @Override public void error(Throwable throwable) { down.error(throwable); } @Override public void complete() { down.complete(); } }); } @Override public void complete() { down.complete(); } }) ).operation(); } @NonBlocking void then(Block block); @NonBlocking default void then() { then(Block.noop()); } Promise<Void> promise(); default <T> Promise<T> map(Factory<? extends T> factory) { return promise().map(n -> factory.create()); } default <T> Promise<T> flatMap(Factory<? extends Promise<T>> factory) { return promise().flatMap(n -> factory.create()); } default <T> Promise<T> flatMap(Promise<T> promise) { return promise().flatMap(n -> promise); } default Operation next(Operation operation) { return new DefaultOperation(flatMap(operation::promise)); } default Operation next(Block operation) { return next(Operation.of(operation)); } /** * Executes the given block as an operation, on a blocking thread. * * @param operation a block of code to be executed, on a blocking thread * @return an operation * @since 1.4 */ default Operation blockingNext(Block operation) { return next(Blocking.op(operation)); } default <O> O to(Function<? super Operation, ? extends O> function) throws Exception { return function.apply(this); } default Operation wiretap(Action<? super Optional<? extends Throwable>> action) { return promise().wiretap(r -> { if (r.isError()) { action.execute(Optional.of(r.getThrowable())); } else { action.execute(Optional.<Throwable>empty()); } } ).operation(); } static Operation noop() { return of(Block.noop()); } }