ratpack.exec.Operation Maven / Gradle / Ivy
/*
* 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.
*
* 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.
*
* 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()}.
*
* 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.
*
{@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 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);
* }
* }
* }
*/
public interface Operation {
static Operation of(Block block) {
return new DefaultOperation(Promise.async(f -> {
block.execute();
f.success(null);
}));
}
Operation onError(Action super Throwable> onError);
@NonBlocking
void then(Block block);
@NonBlocking
default void then() {
then(Block.noop());
}
Promise promise();
default Promise map(Factory extends T> factory) {
return promise().map(n -> factory.create());
}
default Promise flatMap(Factory extends Promise> factory) {
return promise().flatMap(n -> factory.create());
}
default Promise flatMap(Promise 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 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.empty());
}
}
).operation();
}
static Operation noop() {
return of(Block.noop());
}
}