
io.github.mike10004.containment.lifecycle.ContainerLifecycles Maven / Gradle / Ivy
package io.github.mike10004.containment.lifecycle;
import io.github.mike10004.containment.ContainerCreator;
import io.github.mike10004.containment.ContainerParametry;
import io.github.mike10004.containment.RunningContainer;
import io.github.mike10004.containment.StartableContainer;
import io.github.mike10004.containment.StartedContainer;
import io.github.mike10004.containment.dockerjava.DjContainerCreator;
import io.github.mike10004.containment.dockerjava.DjManualContainerMonitor;
import io.github.mike10004.containment.dockerjava.DjShutdownHookContainerMonitor;
import io.github.mike10004.containment.dockerjava.DockerClientBuilder;
import java.util.StringJoiner;
import static java.util.Objects.requireNonNull;
/**
* Class that contains utility methods for building container lifecycles.
*
* We define management of a container to mean cleaning up containers created and/or started
* by this library. Global management means to add a shutdown hook that performs cleanup.
* There are two ways to globally manage a container: one is to construct the container
* with a {@link io.github.mike10004.containment.dockerjava.DjContainerMonitor monitor}
* that adds the shutdown hook, and the other is to manage the lifecycle of the container
* as a resource created by
* {@link LifecycledResourceBuilder#buildResourceDecommissionedOnJvmTermination(Lifecycle)}.
*
*/
public class ContainerLifecycles {
private ContainerLifecycles() {}
private static class ContainerCreatorStage extends DecoupledLifecycle {
public ContainerCreatorStage(Commissioner commissioner) {
super(commissioner, new AutoCloseableDecommissioner<>());
}
@Override
public String toString() {
return String.format("ContainerCreatorStage@%08x", hashCode());
}
}
private static class StartableContainerStage extends DecoupledLifecycleStage {
public StartableContainerStage(ContainerParametry containerParametry) {
super(creator -> creator.create(containerParametry), new AutoCloseableDecommissioner<>());
}
@Override
public String toString() {
return String.format("StartableContainerStage@%08x", hashCode());
}
}
private static class SimpleStartedContainerStage extends DecoupledLifecycleStage {
public SimpleStartedContainerStage() {
super(StartableContainer::start, AutoCloseableDecommissioner.byTransform(StartedContainer.class::cast));
}
@Override
public String toString() {
return String.format("SimpleStartedContainerStage@%08x", hashCode());
}
}
private static class ActionStageResult {
public final C container;
public final T content;
protected ActionStageResult(C container, T content) {
this.container = requireNonNull(container);
this.content = content;
}
}
private static final class PreStartResult extends ActionStageResult {
public PreStartResult(StartableContainer container, T content) {
super(container, content);
}
}
private static final class PostStartResult extends ActionStageResult {
public PostStartResult(StartedContainer container, T content) {
super(container, content);
}
}
private static class ContainerPreStartStage implements LifecycleStage, PreStartResult> {
private final ContainerPreStartAction innerStage;
public ContainerPreStartStage(ContainerPreStartAction innerStage) {
this.innerStage = requireNonNull(innerStage);
}
public ContainerPreStartStage(ContainerInitialPreStartAction innerStage) {
this((container, requirement) -> innerStage.perform(container));
}
@Override
public String toString() {
return new StringJoiner(", ", ContainerPreStartStage.class.getSimpleName() + "[", "]")
.toString();
}
@Override
public PreStartResult commission(PreStartResult requirement) throws Exception {
V content = innerStage.perform(requirement.container, requirement.content);
return new PreStartResult<>(requirement.container, content);
}
@Override
public void decommission() {
// no op
}
}
private static class ContainerPostStartStage implements LifecycleStage, PostStartResult> {
private final ContainerPostStartAction action;
public ContainerPostStartStage(ContainerPostStartAction action) {
this.action = action;
}
public ContainerPostStartStage(ContainerInitialPostStartAction action) {
this((container, requirement) -> action.perform(container));
}
@Override
public PostStartResult commission(PostStartResult requirement) throws Exception {
V content = action.perform(requirement.container, requirement.content);
return new PostStartResult<>(requirement.container, content);
}
@Override
public void decommission() {
// no op
}
}
/**
* Creates a new builder of container lifecycle instances.
* @param ctor constructor of the {@link ContainerCreator} instance
* @return a new builder
*/
public static PreCreate builder(ContainerCreatorFactory ctor) {
return new PreCreateImpl(ctor);
}
/**
* Returns a service that can be used to build lifecycles of containers that are not managed.
* Creating and starting unmanaged containers has system-wide effects that persist after JVM
* termination. (The "system" in this context is the computer on which the JVM is running.)
* Therefore, to be a good citizen of the system, you must explicitly clean up any unmanaged
* containers that you create and/or start.
*
* If you are building a lifecycled resource and you will execute the stages of the lifecycle,
* either explicitly with
* {@link LifecycledResourceBuilder#buildResource(Lifecycle)}
* or implicitly with
* {@link LifecycledResourceBuilder#buildResourceDecommissionedOnJvmTermination(Lifecycle)} decommission-on-shutdown},
* then it is fine to leave your containers unmanaged (because execution of the lifecycle effectively
* cleans up the container).
*
* @return a pre-create service
*/
public static PreCreate builderOfLifecyclesOfUnmanagedContainers() {
ContainerCreatorFactory ctor = new GlobalContainerCreatorFactory(DjContainerCreator::new, clientConfig -> new DjManualContainerMonitor());
return new PreCreateImpl(ctor);
}
/**
* Returns a service that can be used to build lifecycles of containers that are managed globally.
* The creation and starting of a globally-managed container causes a JVM shutdown hook to be
* registered to clean up the container on JVM termination. Cleanup means stopping a started
* container and removing a created container.
* @return a pre-create service
*/
public static PreCreate builderOfLifecyclesOfGloballyManagedContainers() {
ContainerCreatorFactory ctor = new LocalContainerCreatorFactory(DjContainerCreator::new, clientConfig -> new DjShutdownHookContainerMonitor(() -> DockerClientBuilder.getInstance(clientConfig).build()));
return new PreCreateImpl(ctor);
}
/**
* Parent interface for services that can produce complete lifecycle instances.
* @param resource type
*/
public interface LifecycleFinisher
{
Lifecycle
finish();
Lifecycle finishWithContainer();
}
/**
* Interface of a service that allows definition of container parametry
* when building a container lifecycle.
*/
public interface PreCreate {
PreStartInitial creating(ContainerParametry containerParametry);
}
/**
* Interface of a service that supports defining actions that can be defined
* in the pre-start stage before any other actions have been defined.
*/
public interface PreStartInitial extends LifecycleFinisher {
PreStartSubsequent
pre(ContainerInitialPreStartAction
action);
PreStartSubsequent runPre(ContainerPreStartRunnable runnable);
PostStart
post(ContainerInitialPostStartAction
action);
PostStart runPost(ContainerPostStartRunnable runnable);
}
/**
* Interface of a service that supports defining actions that can be defined
* in the pre-start stage
* @param resource type produced if lifecycle were finished now
*/
public interface PreStartSubsequent
extends LifecycleFinisher
{
PreStartSubsequent pre(ContainerPreStartAction action);
PreStartSubsequent
runPre(ContainerPreStartRunnable runnable);
PostStart post(ContainerPostStartAction action);
PostStart
runPost(ContainerPostStartRunnable runnable);
}
/**
* Interface of a service that supports defining post-start actions.
* @param
resource type produced if lifecycle were finished now
*/
public interface PostStart
extends LifecycleFinisher
{ // post
PostStart post(ContainerPostStartAction action);
PostStart
runPost(ContainerPostStartRunnable runnable);
}
private static class PreCreateImpl extends BuilderBase implements PreCreate {
public PreCreateImpl(ContainerCreatorFactory ctor) {
super(LifecycleStack.startingAt(new ContainerCreatorStage(ctor::instantiate)));
}
@Override
public PreStartInitial creating(ContainerParametry containerParametry) {
return new PreStartInitialImpl(stacker.andThen(new StartableContainerStage(containerParametry)));
}
}
private static abstract class BuilderBase {
protected final LifecycleStackElement stacker;
protected BuilderBase(LifecycleStackElement stacker) {
this.stacker = requireNonNull(stacker);
}
protected static LifecycleStage, PostStartResult> transitionPreToPost() {
DecoupledLifecycleStage.Commissioner, PostStartResult> tCommissioner = new DecoupledLifecycleStage.Commissioner, PostStartResult>() {
@Override
public PostStartResult commission(PreStartResult requirement) throws Exception {
StartedContainer startedContainer = requirement.container.start();
return new PostStartResult<>(startedContainer, requirement.content);
}
};
DecoupledLifecycleStage.Decommissioner> tDecommissioner = AutoCloseableDecommissioner.byTransform(postStartResult -> postStartResult.container);
return new DecoupledLifecycleStage<>(tCommissioner, tDecommissioner);
}
protected static LifecycleStage> transitionStartableToPre() {
return new LifecycleStage>() {
@Override
public PreStartResult commission(StartableContainer requirement) {
return new PreStartResult<>(requirement, null);
}
@Override
public void decommission() {
}
};
}
public static LifecycleStage, U> transitionFinishing() {
return new LifecycleStage, U>() {
@Override
public U commission(PostStartResult requirement) {
return requirement.content;
}
@Override
public void decommission() {
}
};
}
}
private static class PreStartInitialImpl extends BuilderBase implements PreStartInitial {
public PreStartInitialImpl(LifecycleStackElement stackElement) {
super(stackElement);
}
@Override
public Lifecycle finish() {
return finishWithContainer();
}
@Override
public PreStartSubsequent runPre(ContainerPreStartRunnable runnable) {
return pre(ContainerRunnables.asInitialAction(runnable));
}
@Override
public PostStart runPost(ContainerPostStartRunnable runnable) {
return post(ContainerRunnables.asInitialAction(runnable));
}
@Override
public PreStartSubsequent
pre(ContainerInitialPreStartAction
action) {
LifecycleStackElement> transition = stacker.andThen(transitionStartableToPre());
LifecycleStage, PreStartResult> stageWrapper = new ContainerPreStartStage<>(action);
LifecycleStackElement> pStacker = transition.andThen(stageWrapper);
return new PreStartSubsequentImpl<>(pStacker);
}
@Override
public PostStart
post(ContainerInitialPostStartAction
action) {
return new PostStartImpl<>(stacker
.andThen(transitionStartableToPre())
.andThen(transitionPreToPost())
.andThen(new ContainerPostStartStage<>(action))
);
}
@Override
public Lifecycle finishWithContainer() {
return stacker.andThen(new SimpleStartedContainerStage()).toSequence();
}
}
private static class PreStartSubsequentImpl extends BuilderBase> implements PreStartSubsequent {
public PreStartSubsequentImpl(LifecycleStackElement> stacker) {
super(stacker);
}
@Override
public Lifecycle finish() {
return stacker.andThen(transitionPreToPost())
.andThen(transitionFinishing()).toSequence();
}
@Override
public Lifecycle finishWithContainer() {
return post((container, x) -> container).finish();
}
@Override
public PostStart runPost(ContainerPostStartRunnable runnable) {
return post(ContainerRunnables.asPassThru(runnable));
}
@Override
public PreStartSubsequent runPre(ContainerPreStartRunnable runnable) {
return pre(ContainerRunnables.asPassThru(runnable));
}
@Override
public PreStartSubsequent pre(ContainerPreStartAction action) {
return new PreStartSubsequentImpl<>(stacker.andThen(new ContainerPreStartStage<>(action)));
}
@Override
public PostStart post(ContainerPostStartAction action) {
return new PostStartImpl<>(stacker
.andThen(transitionPreToPost())
.andThen(new ContainerPostStartStage<>(action)));
}
}
private static class PostStartImpl extends BuilderBase> implements PostStart {
public PostStartImpl(LifecycleStackElement> b) {
super(b);
}
@Override
public Lifecycle finish() {
return stacker.andThen(BuilderBase.transitionFinishing()).toSequence();
}
@Override
public PostStart post(ContainerPostStartAction action) {
return new PostStartImpl<>(stacker.andThen(new ContainerPostStartStage<>(action)));
}
@Override
public PostStart runPost(ContainerPostStartRunnable runnable) {
return post(ContainerRunnables.asPassThru(runnable));
}
@Override
public Lifecycle finishWithContainer() {
return post((container, requirement) -> container).finish();
}
}
@Override
public String toString() {
return String.format("Container%s", super.toString());
}
}