org.wildfly.service.Installer Maven / Gradle / Ivy
/*
* Copyright The WildFly Authors
* SPDX-License-Identifier: Apache-2.0
*/
package org.wildfly.service;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.jboss.msc.Service;
import org.jboss.msc.service.LifecycleEvent;
import org.jboss.msc.service.LifecycleListener;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceController.Mode;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceTarget;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StartException;
import org.jboss.msc.service.StopContext;
import org.wildfly.common.function.Functions;
/**
* Encapsulates installation into a generic service target.
* @author Paul Ferraro
* @param the service target type
*/
public interface Installer {
/**
* Installs a service into the specified target.
* @param target a service target
* @return a service controller
*/
ServiceController> install(ST target);
/**
* Builds an installer of a service.
* @param the builder type
* @param the installer type
* @param the service target type
* @param the service builder type
*/
interface Builder, ST extends ServiceTarget, SB extends ServiceBuilder>> {
/**
* Configures a dependency of the installed service.
* @param dependency a dependency
* @return a reference to this builder
*/
B requires(Consumer dependency);
/**
* Configures dependencies of the installed service.
* @param dependencies a variable number of dependencies
* @return a reference to this builder
*/
default B requires(Iterable extends Consumer> dependencies) {
return this.requires(new Consumer<>() {
@Override
public void accept(SB builder) {
for (Consumer dependency : dependencies) {
dependency.accept(builder);
}
}
});
}
/**
* Configures the installed service to automatically start when all of its dependencies are available
* and to automatically stop when any of its dependencies are no longer available.
* @return a reference to this builder
*/
B asPassive();
/**
* Configures the installed service to start immediately after installation, forcing any dependencies to start.
* @return a reference to this builder
*/
B asActive();
/**
* Configures the specified task to be run after the installed service is started.
* @param task a task to execute upon service start
* @return a reference to this builder
*/
B onStart(Runnable task);
/**
* Configures the specified task to be run after the installed service is stopped.
* @param task a task to execute upon service stop
* @return a reference to this builder
*/
B onStop(Runnable task);
/**
* Configures the specified task to be run upon removal of the installed service.
* @param task a task to execute upon service removal
* @return a reference to this builder
*/
B onRemove(Runnable task);
/**
* Builds a service installer.
* @return a service installer
*/
I build();
}
/**
* Implemented by builders with blocking service support.
* @param the builder type
*/
interface BlockingBuilder {
/**
* Indicates that the installed service performs blocking operations on start and/or stop, and should be instrumented accordingly.
* @return a reference to this builder
*/
B blocking();
}
/**
* Builds an installer of a service providing a single value.
* @param the builder type
* @param the installer type
* @param the service target type
* @param the service builder type
* @param the source value type
* @param the service value type
*/
interface UnaryBuilder, ST extends ServiceTarget, SB extends ServiceBuilder>, T, V> extends Builder {
/**
* Configures a service name provided by this service.
* @param name a service name
* @return a reference to this builder
*/
B provides(ServiceName name);
/**
* Configures a captor invoked with the service value on {@link org.jboss.msc.Service#start(org.jboss.msc.service.StartContext)}, and with null on {@link org.jboss.msc.Service#stop(StopContext)}.
* @param captor a consumer of the service value on start, and null on stop.
* @return a reference to this builder
*/
B withCaptor(Consumer captor);
/**
* Configures a task to run on {@link org.jboss.msc.Service#start(org.jboss.msc.service.StartContext)}.
* @param task a task consuming the service value source
* @return a reference to this builder
*/
B onStart(Consumer task);
/**
* Configures a task to run on {@link org.jboss.msc.Service#stop(org.jboss.msc.service.StopContext)}.
* @param task a task consuming the service value source
* @return a reference to this builder
*/
B onStop(Consumer task);
}
/**
* Encapsulates the configuration of an {@link Installer}.
* @param the service builder type
* @param the dependency service builder type
* @param the source value type
* @param the service value type
*/
interface Configuration> {
/**
* Returns the initial mode of the installed service.
* @return a service mode
*/
ServiceController.Mode getInitialMode();
/**
* Returns the dependency of this service
* @return a service dependency
*/
Consumer getDependency();
/**
* Returns the factory of this service
* @return a service factory
*/
Function getServiceFactory();
/**
* Returns tasks to be run per lifecycle event.
* The returned map is either fully populated, or an empty map, if this service has no lifecycle tasks.
* @return a potentially empty map of tasks to be run per lifecycle event.
*/
Map> getLifecycleTasks();
}
/**
* Generic abstract installer implementation that installs a {@link UnaryService}.
* @param the service target type
* @param the service builder type
* @param the dependency service builder type
* @param the source value type
* @param the provided value type of the service
*/
class DefaultInstaller> implements Installer {
private final Function serviceBuilderFactory;
private final ServiceController.Mode mode;
private final Consumer dependency;
private final Function serviceFactory;
private final Map> lifecycleTasks;
protected DefaultInstaller(Installer.Configuration config, Function serviceBuilderFactory) {
this.serviceBuilderFactory = serviceBuilderFactory;
this.serviceFactory = config.getServiceFactory();
this.mode = config.getInitialMode();
this.dependency = config.getDependency();
this.lifecycleTasks = config.getLifecycleTasks();
}
@Override
public ServiceController> install(ST target) {
SB builder = this.serviceBuilderFactory.apply(target);
this.dependency.accept(builder);
// N.B. map of tasks is either empty or fully populated
if (!this.lifecycleTasks.isEmpty()) {
Map> tasks = this.lifecycleTasks;
builder.addListener(new LifecycleListener() {
@Override
public void handleEvent(ServiceController> controller, LifecycleEvent event) {
tasks.get(event).forEach(Runnable::run);
}
});
}
return builder.setInstance(this.serviceFactory.apply(builder)).setInitialMode(this.mode).install();
}
}
abstract class AbstractBuilder, ST extends ServiceTarget, SB extends DSB, DSB extends ServiceBuilder>> implements Installer.Builder, Installer.Configuration {
private volatile ServiceController.Mode mode = ServiceController.Mode.ON_DEMAND;
private volatile Consumer dependency = Functions.discardingConsumer();
private volatile Map> lifecycleTasks = Map.of();
protected abstract B builder();
@Override
public B asPassive() {
this.mode = ServiceController.Mode.PASSIVE;
return this.builder();
}
@Override
public B asActive() {
this.mode = ServiceController.Mode.ACTIVE;
return this.builder();
}
@Override
public B requires(Consumer dependency) {
this.dependency = (this.dependency == Functions.discardingConsumer()) ? dependency : this.dependency.andThen(dependency);
return this.builder();
}
@Override
public B onStart(Runnable task) {
return this.onEvent(LifecycleEvent.UP, task);
}
@Override
public B onStop(Runnable task) {
return this.onEvent(LifecycleEvent.DOWN, task);
}
@Override
public B onRemove(Runnable task) {
return this.onEvent(LifecycleEvent.REMOVED, task);
}
private B onEvent(LifecycleEvent event, Runnable task) {
if (this.lifecycleTasks.isEmpty()) {
// Create EnumMap lazily, when needed
this.lifecycleTasks = new EnumMap<>(LifecycleEvent.class);
for (LifecycleEvent e : EnumSet.allOf(LifecycleEvent.class)) {
this.lifecycleTasks.put(e, (e == event) ? List.of(task) : List.of());
}
} else {
List tasks = this.lifecycleTasks.get(event);
if (tasks.isEmpty()) {
this.lifecycleTasks.put(event, List.of(task));
} else {
if (tasks.size() == 1) {
tasks = new LinkedList<>(tasks);
this.lifecycleTasks.put(event, tasks);
}
tasks.add(task);
}
}
return this.builder();
}
@Override
public Mode getInitialMode() {
return this.mode;
}
@Override
public Consumer getDependency() {
return this.dependency;
}
@Override
public Map> getLifecycleTasks() {
// Return empty map or fully unmodifiable copy
if (this.lifecycleTasks.isEmpty()) return Map.of();
Map> result = new EnumMap<>(LifecycleEvent.class);
for (Map.Entry> entry : this.lifecycleTasks.entrySet()) {
result.put(entry.getKey(), List.copyOf(entry.getValue()));
}
return Collections.unmodifiableMap(result);
}
}
abstract class AbstractNullaryBuilder, ST extends ServiceTarget, SB extends DSB, DSB extends ServiceBuilder>> extends AbstractBuilder implements Function {
private final Service service;
protected AbstractNullaryBuilder(Service service) {
this.service = service;
}
@Override
public Function getServiceFactory() {
return this;
}
@Override
public Service apply(SB builder) {
return this.service;
}
}
abstract class AbstractUnaryBuilder, ST extends ServiceTarget, SB extends DSB, DSB extends ServiceBuilder>, T, V> extends AbstractBuilder implements Installer.UnaryBuilder, Function {
private final List names = new LinkedList<>();
private final Function mapper;
private final Supplier factory;
private final BiFunction, Consumer> provider;
private volatile Consumer captor = Functions.discardingConsumer();
private volatile Consumer startTask = Functions.discardingConsumer();
private volatile Consumer stopTask = Functions.discardingConsumer();
protected AbstractUnaryBuilder(Function mapper, Supplier factory) {
this(mapper, factory, new BiFunction<>() {
@Override
public Consumer apply(SB builder, Collection names) {
return !names.isEmpty() ? builder.provides(names.toArray(ServiceName[]::new)) : Functions.discardingConsumer();
}
});
}
protected AbstractUnaryBuilder(Function mapper, Supplier factory, BiFunction, Consumer> provider) {
this.mapper = mapper;
this.factory = factory;
this.provider = provider;
}
@Override
public B provides(ServiceName name) {
this.names.add(name);
return this.builder();
}
@Override
public B withCaptor(Consumer captor) {
this.captor = compose(this.captor, captor);
return this.builder();
}
@Override
public B onStart(Consumer task) {
this.startTask = compose(this.startTask, task);
return this.builder();
}
@Override
public B onStop(Consumer task) {
this.stopTask = compose(this.stopTask, task);
return this.builder();
}
private static boolean isDefined(Consumer task) {
return task != Functions.discardingConsumer();
}
private static Consumer compose(Consumer currentTask, Consumer newTask) {
return isDefined(currentTask) ? currentTask.andThen(newTask) : newTask;
}
@Override
public Function getServiceFactory() {
return this;
}
@Override
public Service apply(SB builder) {
Consumer injector = this.provider.apply(builder, this.names);
Consumer captor = this.captor;
if (isDefined(captor)) {
injector = isDefined(injector) ? injector.andThen(captor) : captor;
}
return new UnaryService<>(injector, this.mapper, this.factory, this.startTask, this.stopTask);
}
protected boolean hasStopTask() {
return isDefined(this.stopTask);
}
}
class UnaryService implements Service {
private final Consumer captor;
private final Function mapper;
private final Supplier factory;
private final Consumer startTask;
private final Consumer stopTask;
private volatile T value;
UnaryService(Consumer captor, Function mapper, Supplier factory, Consumer startTask, Consumer stopTask) {
this.captor = captor;
this.mapper = mapper;
this.factory = factory;
this.startTask = startTask;
this.stopTask = stopTask;
}
@Override
public void start(StartContext context) throws StartException {
this.value = this.factory.get();
this.startTask.accept(this.value);
this.captor.accept(this.mapper.apply(this.value));
}
@Override
public void stop(StopContext context) {
try {
this.stopTask.accept(this.value);
} finally {
this.value = null;
this.captor.accept(null);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy