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

ratpack.service.internal.ServicesGraph Maven / Gradle / Ivy

/*
 * Copyright 2016 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.service.internal;

import com.google.common.base.Joiner;
import com.google.common.collect.*;
import org.slf4j.Logger;
import ratpack.api.Nullable;
import ratpack.exec.ExecController;
import ratpack.func.Predicate;
import ratpack.registry.Registry;
import ratpack.server.StartupFailureException;
import ratpack.server.internal.DefaultRatpackServer;
import ratpack.service.*;

import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public class ServicesGraph {

  public static final Logger LOGGER = DefaultRatpackServer.LOGGER;

  private final List nodes;
  private final AtomicInteger toStartCount = new AtomicInteger();
  private final AtomicInteger toStopCount = new AtomicInteger();
  private final AtomicInteger starting = new AtomicInteger();

  private final CountDownLatch startLatch = new CountDownLatch(1);
  private final CountDownLatch stopLatch = new CountDownLatch(1);
  private final ExecController execController;

  private final AtomicReference failureRef = new AtomicReference<>();
  private volatile boolean startupFailed;

  public ServicesGraph(Registry registry) throws Exception {
    this.nodes = ImmutableList.builder()
      .addAll(Iterables.transform(registry.getAll(Service.class), Node::new))
      .addAll(adaptLegacy(registry))
      .build();
    this.toStartCount.set(nodes.size());
    this.toStopCount.set(nodes.size());
    this.execController = registry.get(ExecController.class);

    defineDependencies(registry);
  }

  private Iterable adaptLegacy(Registry registry) {
    @SuppressWarnings("deprecation") Class type = ratpack.server.Service.class;
    return Iterables.transform(registry.getAll(type), s -> new Node(new DefaultLegacyServiceAdapter(s)));
  }

  private void defineDependencies(Registry registry) throws Exception {
    SpecBacking specBacking = new SpecBacking();
    for (ServiceDependencies dependencies : registry.getAll(ServiceDependencies.class)) {
      dependencies.define(specBacking);
    }
    List legacyNodes = Lists.newArrayList();
    for (Node node : nodes) {
      if (node.isLegacy()) {
        for (Node legacyNode : legacyNodes) {
          node.addDependency(legacyNode);
          legacyNode.addDependent(node);
        }
        legacyNodes.add(node);
      }
      DependsOn dependsOn = node.getDependsOn();
      if (dependsOn != null) {
        specBacking.dependsOn(
          s -> node.getImplClass().isInstance(s),
          s -> {
            for (Class dependencyType : dependsOn.value()) {
              if (dependencyType.isInstance(unpackIfLegacy(s))) {
                return true;
              }
            }
            return false;
          }
        );
      }
    }
  }

  public synchronized void start(StartEvent startEvent) throws StartupFailureException, InterruptedException {
    if (startLatch.getCount() > 0) {
      if (nodes.isEmpty()) {
        startLatch.countDown();
      } else {
        LOGGER.info("Initializing " + nodes.size() + " services...");
        starting.incrementAndGet();
        //noinspection Convert2streamapi
        for (Node node : nodes) {
          if (node.dependencies.isEmpty()) {
            node.start(startEvent);
          }
        }

        if (starting.decrementAndGet() == 0 && toStartCount.get() > 0) {
          onCycle();
        }
      }
    }

    startLatch.await();
    StartupFailureException startupFailureException = failureRef.get();
    if (startupFailureException != null) {
      stop(new DefaultEvent(startEvent.getRegistry(), false));
      throw startupFailureException;
    }
  }

  public synchronized void stop(StopEvent stopEvent) throws InterruptedException {
    if (stopLatch.getCount() > 0) {
      doStop(stopEvent);
    }
    stopLatch.await();
  }

  private void doStop(StopEvent stopEvent) {
    if (nodes.isEmpty()) {
      stopLatch.countDown();
    } else {
      LOGGER.info("Stopping " + nodes.size() + " services...");
      nodes.forEach(n -> {
        if (n.dependentsToStopCount.get() == 0) {
          n.stop(stopEvent);
        }
      });
    }
  }

  private void serviceDidStart(Node node, StartEvent startEvent) {
    node.dependencies.forEach(Node::dependentStarted);

    if (toStartCount.decrementAndGet() == 0) {
      if (startupFailed) {
        StartupFailureException exception = processFailure();
        failureRef.set(exception);
      }
      startLatch.countDown();
    } else {
      node.dependents.forEach(n -> n.dependencyStarted(startEvent));
      if (starting.decrementAndGet() == 0 && toStartCount.get() > 0) {
        onCycle();
      }
    }
  }

  private void onCycle() {
    String joinedServiceNames = FluentIterable.from(nodes)
      .filter(Node::notStarted)
      .transform(n -> n.service.getName())
      .join(Joiner.on(", "));

    failureRef.set(new StartupFailureException("dependency cycle detected involving the following services: [" + joinedServiceNames + "]"));
    startLatch.countDown();
  }

  @Nullable
  private StartupFailureException processFailure() {
    StartupFailureException exception = null;
    for (Node node : nodes) {
      if (node.startError != null) {
        StartupFailureException nodeException = new StartupFailureException("Service '" + node.service.getName() + "' failed to start", node.startError);
        if (exception == null) {
          exception = nodeException;
        } else {
          exception.addSuppressed(nodeException);
        }
      }
    }
    return exception;
  }

  private void serviceDidStop(Node node, StopEvent stopEvent) {
    if (toStopCount.decrementAndGet() == 0) {
      stopLatch.countDown();
    } else {
      node.dependencies.forEach(n -> n.dependentStopped(stopEvent));
    }
  }

  private class SpecBacking implements ServiceDependenciesSpec {
    @Override
    public ServiceDependenciesSpec dependsOn(Predicate dependents, Predicate dependencies) throws Exception {
      List dependentNodes = Lists.newArrayList();
      List dependencyNodes = Lists.newArrayList();
      for (Node node : nodes) {
        boolean dependent = false;
        if (dependents.apply(node.service)) {
          dependent = true;
          dependentNodes.add(node);
        }
        if (dependencies.apply(node.service)) {
          if (dependent) {
            throw new IllegalStateException("Service '" + node.service.getName() + "' marked as dependent and dependency");
          }
          dependencyNodes.add(node);
        }
      }
      for (Node dependencyNode : dependencyNodes) {
        for (Node dependentNode : dependentNodes) {
          dependentNode.addDependency(dependencyNode);
          dependencyNode.addDependent(dependentNode);
        }
      }
      return this;
    }
  }

  private class Node {

    private final Service service;
    private final Set dependents = Sets.newHashSet();
    private final AtomicInteger dependentsToStopCount = new AtomicInteger();
    private final Set dependencies = Sets.newHashSet();
    private final AtomicInteger dependenciesToStartCount = new AtomicInteger();

    private final AtomicBoolean stopped = new AtomicBoolean();
    private volatile boolean running;
    private volatile Throwable startError;

    public Node(Service service) {
      this.service = service;
    }

    public DependsOn getDependsOn() {
      return getImplClass().getAnnotation(DependsOn.class);
    }

    private Class getImplClass() {
      return isLegacy() ? ((DefaultLegacyServiceAdapter) service).getAdapted().getClass() : service.getClass();
    }

    private boolean isLegacy() {
      return service instanceof DefaultLegacyServiceAdapter;
    }

    public boolean notStarted() {
      return !running;
    }

    public void addDependency(Node node) {
      dependencies.add(node);
      dependenciesToStartCount.incrementAndGet();
    }

    public void addDependent(Node node) {
      dependents.add(node);
    }

    public void dependencyStarted(StartEvent startEvent) {
      if (dependenciesToStartCount.decrementAndGet() == 0) {
        start(startEvent);
      }
    }

    public void dependentStarted() {
      dependentsToStopCount.incrementAndGet();
    }

    public void dependentStopped(StopEvent stopEvent) {
      if (dependentsToStopCount.decrementAndGet() <= 0) {
        stop(stopEvent);
      }
    }

    public void start(StartEvent startEvent) {
      starting.incrementAndGet();
      if (startupFailed) {
        serviceDidStart(this, startEvent);
        return;
      }

      execController.fork()
        .onComplete(e -> {
          if (startError == null) {
            running = true;
          }
          serviceDidStart(this, startEvent);
        })
        .onError(e -> {
          startError = e;
          startupFailed = true;
        })
        .start(e -> service.onStart(startEvent));
    }

    public void stop(StopEvent stopEvent) {
      if (stopped.compareAndSet(false, true)) {
        if (running) {
          execController.fork()
            .onComplete(e ->
              serviceDidStop(this, stopEvent)
            )
            .onError(e ->
              LOGGER.warn("Service '" + service.getName() + "' thrown an exception while stopping.", e)
            )
            .start(e -> service.onStop(stopEvent));
        } else {
          serviceDidStop(this, stopEvent);
        }
      }
    }
  }

  @SuppressWarnings("deprecation")
  public static boolean isOfType(Service service, Class type) {
    if (service instanceof LegacyServiceAdapter) {
      ratpack.server.Service legacyService = ((LegacyServiceAdapter) service).getAdapted();
      return type.isInstance(legacyService);
    } else {
      return type.isInstance(service);
    }
  }

  @SuppressWarnings("deprecation")
  public static Object unpackIfLegacy(Service service) {
    if (service instanceof LegacyServiceAdapter) {
      return ((LegacyServiceAdapter) service).getAdapted();
    } else {
      return service;
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy