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

org.kiwiproject.curator.leader.ManagedLeaderLatchCreator Maven / Gradle / Ivy

Go to download

This is a small library that integrates Apache Curator's Leader Latch recipe into a Dropwizard service.

There is a newer version: 2.3.1
Show newest version
package org.kiwiproject.curator.leader;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Objects.nonNull;
import static org.kiwiproject.base.KiwiPreconditions.checkArgumentNotNull;
import static org.kiwiproject.base.KiwiPreconditions.requireNotNull;

import io.dropwizard.setup.Environment;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.leader.LeaderLatchListener;
import org.kiwiproject.curator.leader.exception.ManagedLeaderLatchException;
import org.kiwiproject.curator.leader.health.ManagedLeaderLatchHealthCheck;
import org.kiwiproject.curator.leader.resource.GotLeaderLatchResource;
import org.kiwiproject.curator.leader.resource.LeaderResource;

import java.util.List;
import java.util.Optional;

/**
 * Entry point to initialize {@link ManagedLeaderLatch}, which wraps an Apache Curator
 * {@link org.apache.curator.framework.recipes.leader.LeaderLatch} and allows for easy determination
 * whether a JVM process is the leader in a group of JVMs connected to a ZooKeeper cluster.
 * 

* The {@link ManagedLeaderLatch} created by this class will be started immediately, but is a Dropwizard * {@link io.dropwizard.lifecycle.Managed} so that it will be stopped when the Dropwizard service shuts down. *

* In addition, by default a {@link ManagedLeaderLatchHealthCheck} is registered with Dropwizard. Two REST resources * are registered, {@link GotLeaderLatchResource} and {@link LeaderResource}. */ @Slf4j public class ManagedLeaderLatchCreator { // Initialized at construction private final CuratorFramework client; private final Environment environment; private final ServiceDescriptor serviceDescriptor; private final List listeners; // Initialized via instance start() method so cannot be final (unless we used Kotlin and lateinit) private ManagedLeaderLatch leaderLatch; private boolean latchStarted; private boolean addHealthCheck; private ManagedLeaderLatchHealthCheck healthCheck; private boolean addResources; private ManagedLeaderLatchCreator(CuratorFramework client, Environment environment, ServiceDescriptor serviceDescriptor, List listeners) { this.client = requireNotNull(client); checkState(client.getState() == CuratorFrameworkState.STARTED, "CuratorFramework must be started"); this.environment = requireNotNull(environment); this.serviceDescriptor = requireNotNull(serviceDescriptor); this.listeners = requireNotNull(listeners); this.addHealthCheck = true; this.addResources = true; } /** * Static factory method to create a {@link ManagedLeaderLatchCreator}. *

* Use this method when you want to perform additional configuration before starting the leader latch. *

* You will need to call {@link #start()} on the returned instance in order to start the * {@link ManagedLeaderLatch}. * * @param client the Curator client * @param environment the Dropwizard environment * @param serviceDescriptor service metadata * @param listeners optional listeners * @return a new instance * @throws IllegalStateException if the {@code client} is not started, or any required arguments are null */ public static ManagedLeaderLatchCreator from(CuratorFramework client, Environment environment, ServiceDescriptor serviceDescriptor, LeaderLatchListener... listeners) { // The list of listeners passed to the constructor must be mutable or else exceptions will be thrown if // addLeaderLatchListener is called subsequently (since it adds to the existing list). return new ManagedLeaderLatchCreator(client, environment, serviceDescriptor, newArrayList(listeners)); } /** * If the only thing you want is a {@link ManagedLeaderLatch} and you want the standard options (a health * check and JAX-RS REST resources) and you do not need references to them, use this method to create and * start a latch. *

* Otherwise use {@link #start(CuratorFramework, Environment, ServiceDescriptor, LeaderLatchListener...)}. * * @param client the Curator client * @param environment the Dropwizard environment * @param serviceDescriptor the service information from the registry * @param listeners optional listeners * @return a started {@link ManagedLeaderLatch} * @throws IllegalStateException if the {@code client} is not started, or any required arguments are null * @see #start(CuratorFramework, Environment, ServiceDescriptor, LeaderLatchListener...) */ public static ManagedLeaderLatch startLeaderLatch(CuratorFramework client, Environment environment, ServiceDescriptor serviceDescriptor, LeaderLatchListener... listeners) { var leaderLatchCreator = start(client, environment, serviceDescriptor, listeners); return leaderLatchCreator.getLeaderLatch(); } /** * If you want a {@link ManagedLeaderLatch} and you want the standard options (a health * check and JAX-RS REST resources) and you might need references to them, use this method to create and * start a latch. *

* The returned {@link ManagedLeaderLatchCreator} can be used to then obtain the {@link ManagedLeaderLatch} * as well as the health check and listeners. * * @param client the Curator client * @param environment the Dropwizard environment * @param serviceInfo the service information from the registry * @param listeners optional listeners * @return a {@link ManagedLeaderLatchCreator} with a started {@link ManagedLeaderLatch} * @throws IllegalStateException if the {@code client} is not started, or any required arguments are null */ public static ManagedLeaderLatchCreator start(CuratorFramework client, Environment environment, ServiceDescriptor serviceInfo, LeaderLatchListener... listeners) { return from(client, environment, serviceInfo, listeners).start(); } /** * Configures without a health check. *

* Use only when constructing a new {@link ManagedLeaderLatchCreator}. * * @return this instance, for method chaining */ public ManagedLeaderLatchCreator withoutHealthCheck() { addHealthCheck = false; return this; } /** * Configures without REST resources to check for leadership and if a leader latch is present. *

* Use only when constructing a new {@link ManagedLeaderLatchCreator}. * * @return this instance, for method chaining */ public ManagedLeaderLatchCreator withoutResources() { addResources = false; return this; } /** * Adds the specified {@link LeaderLatchListener}. *

* Use only when constructing a new {@link ManagedLeaderLatchCreator}. * * @param listener the listener to add * @return this instance, for method chaining */ public ManagedLeaderLatchCreator addLeaderLatchListener(LeaderLatchListener listener) { listeners.add(listener); return this; } /** * Starts the leader latch, performing the following actions: *

    *
  • * Creates a new {@link ManagedLeaderLatch}, starts it, and tells the Dropwizard lifecycle to manage (stop) it *
  • *
  • * Creates and registers a {@link ManagedLeaderLatchHealthCheck} unless explicitly disabled via * {@link #withoutHealthCheck()} *
  • *
  • * Creates and registers the JAX-RS REST endpoints unless explicitly disabled via {@link #withoutResources()} *
  • *
*

* Note that once this method is called, nothing about the {@link ManagedLeaderLatch} can be changed, and calls * to other instance methods (e.g. addLeaderLatchListener) will have no effect. Similarly, calling this method * more than once is considered unexpected behavior, and we will simply return the existing instance without * taking any other actions. * * @return this instance, from which you can then retrieve the (started) {@link ManagedLeaderLatch} * @throws ManagedLeaderLatchException if an error occurs starting the latch */ public ManagedLeaderLatchCreator start() { if (nonNull(leaderLatch)) { LOG.warn("start() has already been called. Ignoring this invocation."); return this; } var listenerArray = listeners.toArray(new LeaderLatchListener[0]); leaderLatch = new ManagedLeaderLatch(client, serviceDescriptor, listenerArray); environment.lifecycle().manage(leaderLatch); startLatchOrThrow(); addHealthCheckIfConfigured(); addResourcesIfConfigured(); return this; } private void startLatchOrThrow() { startLatchOrThrow(leaderLatch); latchStarted = true; } static void startLatchOrThrow(ManagedLeaderLatch leaderLatch) { checkArgumentNotNull(leaderLatch); try { leaderLatch.start(); } catch (Exception e) { throw new ManagedLeaderLatchException("Error starting leader latch", e); } } private void addHealthCheckIfConfigured() { if (addHealthCheck) { healthCheck = new ManagedLeaderLatchHealthCheck(leaderLatch); environment.healthChecks().register("leaderLatch", healthCheck); } } private void addResourcesIfConfigured() { if (addResources) { environment.jersey().register(new GotLeaderLatchResource()); environment.jersey().register(new LeaderResource(leaderLatch)); } } /** * Has this instance created and started a {@link ManagedLeaderLatch}? * * @return true if this instance has created and started a leader latch */ public boolean isLeaderLatchStarted() { return latchStarted; } /** * Returns the {@link ManagedLeaderLatch} created after {@link #start()} has been called. *

* Use {@link #isLeaderLatchStarted()} to ensure the latch has been started to ensure this method will succeed. * * @return the leader latch * @throws IllegalStateException if called but the latch has not been started yet */ public ManagedLeaderLatch getLeaderLatch() { validateStarted(); return leaderLatch; } /** * Returns the health check (if registered) after {@link #start()} has been called. * * @return the registered health check * @throws IllegalStateException if called but the latch has not been started yet */ public Optional getHealthCheck() { validateStarted(); return Optional.ofNullable(healthCheck); } /** * Returns a list containing all registered {@link LeaderLatchListener}s after {@link #start()} has been called. * * @return any registered {@link LeaderLatchListener}s * @throws IllegalStateException if called but the latch has not been started yet * @implNote The returned list is an unmodifiable list containing the registered listeners */ public List getListeners() { validateStarted(); return List.copyOf(listeners); } private void validateStarted() { checkState(latchStarted, "Leader latch is not started; call start() first"); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy