All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wildfly.service.Installer Maven / Gradle / Ivy

There is a newer version: 27.0.0.Final
Show newest version
/*
 * 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> 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);
            }
        }
    }
}