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

com.loopfor.zookeeper.Zookeeper.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013 David Edwards
 *
 * 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 com.loopfor.zookeeper

import java.util.concurrent.atomic.AtomicReference
import java.util.concurrent.TimeUnit.NANOSECONDS
import org.apache.zookeeper.{KeeperException => ZException, _}
import org.apache.zookeeper.KeeperException.Code
import org.apache.zookeeper.data.{ACL => ZACL, Stat}
import scala.annotation.tailrec
import scala.collection.JavaConverters._
import scala.concurrent._
import scala.language._

/**
 * An instance of a ZooKeeper client.
 */
trait Zookeeper {
  /**
   * Returns a view of this client in which operations are performed ''synchronously''.
   * 
   * @return a synchronous view of this client
   */
  def sync: SynchronousZookeeper

  /**
   * Returns a view of this client in which operations are performed ''asynchronously''.
   * 
   * @return an asynchronous view of this client
   */
  def async: AsynchronousZookeeper

  /**
   * Ensures that the value of a node, specified by the given path, is synchronized across the ZooKeeper cluster.
   * 
   * '''An important note on consistency''': ZooKeeper does not guarantee, for any given point in time, that all clients will
   * have a consistent view of the cluster. Since reads can be served by any node in the cluster, whereas writes are serialized
   * through the leader, there exists the possibility in which two separate clients may have inconsistent views. This scenario
   * occurs when the leader commits a change once consensus is reached, but the change has not yet propagated across the
   * cluster. Therefore, reads occurring before the commit has propagated will be globally inconsistent. This behavior is
   * normally acceptable, but for some use cases, writes may need to be globally visible before subsequent reads occur.
   * 
   * This method is particularly useful when a ''write'' occurring in one process is followed by a ''read'' in another process.
   * For example, consider the following sequence of operations:
   * 
   *  - process A writes a value
   *  - process A sends a message to process B
   *  - process B reads the value
   * 
   * The assumption is that process B expects to see the value written by process A, but as mentioned, ZooKeeper does not make
   * this guarantee. A call to this method before process B attempts to read the value ensures that all prior writes are
   * consistently applied across the cluster, thus observing the write in process A.
   * 
   * @return a future that completes when the node is synchronized across the cluster
   */
  def ensure(path: String): Future[Unit]

  /**
   * Closes the client connection to the ZooKeeper cluster.
   * 
   * A consequence of closing the connection is that ZooKeeper will expire the corresponding session, which further implies
   * that all ephemeral nodes created by this client will be deleted.
   */
  def close(): Unit
}

/**
 * A ZooKeeper client with ''synchronous'' operations.
 */
trait SynchronousZookeeper extends Zookeeper {
  /**
   * Creates a new node at the given path.
   * 
   * If a ''sequential'' [[Disposition disposition]] is provided in `disp`, then `path` is appended with a monotonically
   * increasing sequence, thus guaranteeing that all sequential nodes are unique with `path` as their prefix.
   * 
   * @param path the path of the node to create
   * @param data the data to associate with the node, which may be empty, but not `null`
   * @param acl an access control list to apply to the node, which must not be empty
   * @param disp the disposition of the node
   * @return the final path of the created node, which will differ from `path` if `disp` is either [[PersistentSequential]]
   * or [[EphemeralSequential]]
   */
  def create(path: String, data: Array[Byte], acl: Seq[ACL], disp: Disposition): String

  /**
   * Deletes the node specified by the given path.
   * 
   * @param path the path of the node
   * @param version a `Some` containing the expected version of the node or `None` if a version match is not required
   * 
   * @throws NoNodeException if the node does not exist
   * @throws BadVersionException if `version` is specified and does not match the node version
   * @throws NotEmptyException if the node contains children
   */
  def delete(path: String, version: Option[Int]): Unit

  /**
   * Returns the data and status of the node specified by the given path.
   * 
   * @param path the path of the node
   * @return a tuple containing the data and status of the node
   * 
   * @throws NoNodeException if the node does not exist
   */
  def get(path: String): (Array[Byte], Status)

  /**
   * Sets the data for the node specified by the given path.
   * 
   * @param path the path of the node
   * @param data the data to associate with the node, which may be empty, but not `null`
   * @param version a `Some` containing the expected version of the node or `None` if a version match is not required
   * @return the status of the node
   * 
   * @throws NoNodeException if the node does not exist
   * @throws BadVersionException if `version` is specified and does not match the node version
   */
  def set(path: String, data: Array[Byte], version: Option[Int]): Status

  /**
   * Returns the status of the node specified by the given path if it exists.
   * 
   * @param path the path of the node
   * @return a `Some` containing the node status or `None` if the node does not exist
   */
  def exists(path: String): Option[Status]

  /**
   * Returns the children of the node specified by the given path.
   * 
   * @param path the path of the node
   * @return an unordered sequence containing the names of each child node
   * 
   * @throws NoNodeException if the node does not exist
   */
  def children(path: String): Seq[String]

  /**
   * Returns the ACL and status of the node specified by the given path.
   * 
   * @param path the path of the node
   * @return a tuple containing the ACL and status of the node
   * 
   * @throws NoNodeException if the node does not exist
   */
  def getACL(path: String): (Seq[ACL], Status)

  /**
   * Sets the ACL for the node specified by the given path.
   * 
   * @param path the path of the node
   * @param acl an access control list to apply to the node, which must not be empty
   * @param version a `Some` containing the expected version of the node or `None` if a version match is not required
   * @return the status of the node
   * 
   * @throws NoNodeException if the node does not exist
   * @throws BadVersionException if `version` is specified and does not match the node version
   */
  def setACL(path: String, acl: Seq[ACL], version: Option[Int]): Status

  /**
   * Returns a synchronous client in which operations implicitly attach the specified watch function.
   * 
   * The partial function `fn` is invoked when a watch is triggered or the session state changes. This method is typically
   * used in a transient manner to introduce a watch function prior to performing a watchable ZooKeeper operation.
   * 
   * Example:
   * {{{
   * val zk = SynchronousZookeeper(config)
   * val (data, node) = zk watch {
   *   case e: NodeEvent => ...
   *   case e: StateEvent => ...
   * } get "/foo"
   * }}}
   */
  def watch(fn: PartialFunction[Event, Unit]): SynchronousWatchableZookeeper

  /**
   * Atomically performs a set of operations, either committing all or none.
   * 
   * The set of operations are applied by ZooKeeper in sequential order. If successful, this method returns a `Right`
   * containing a sequence of results that positionally correlate to the sequence of operations. Otherwise, it returns a
   * `Left` similarly containing a sequence of problems.
   * 
   * Example:
   * {{{
   * val ops = CheckOperation("/foo", None) ::
   *           CreateOperation("/bar", Array(), ACL.EveryoneAll, Persistent) :: Nil
   * zk transact ops match {
   *   case Right(results) => ...
   *   case Left(problems) => ...
   * }
   * }}}
   */
  def transact(ops: Seq[Operation]): Either[Seq[Problem], Seq[Result]]
}

/**
 * A ZooKeeper client with ''synchronous'' and ''watchable'' operations.
 */
trait SynchronousWatchableZookeeper extends Zookeeper {
  /**
   * Returns the data and status of the node specified by the given path and additionally sets a watch for any changes.
   * 
   * The watch is triggered when one of the following conditions occur:
   *  - the data associated with the node changes
   *  - the node is deleted
   *  - the session state changes
   * 
   * @param path the path of the node
   * @return a tuple containing the data and status of the node
   * 
   * @throws NoNodeException if the node does not exist
   */
  def get(path: String): (Array[Byte], Status)

  /**
   * Returns the status of the node specified by the given path if it exists and additionally sets a watch for any changes.
   * 
   * The watch is triggered when one of the following conditions occur:
   *  - the data associated with the node changes
   *  - the node is created
   *  - the node is deleted
   *  - the session state changes
   * 
   * @param path the path of the node
   * @return a `Some` containing the node status or `None` if the node does not exist
   */
  def exists(path: String): Option[Status]

  /**
   * Returns the children of the node specified by the given path and additionally sets a watch for any changes.
   * 
   * The watch is triggered when one of the following conditions occur:
   *  - the session state changes
   * 
   * @param path the path of the node
   * @return an unordered sequence containing the names of each child node
   * 
   * @throws NoNodeException if the node does not exist
   */
  def children(path: String): Seq[String]
}

/**
 * A ZooKeeper client with ''asynchronous'' operations.
 */
trait AsynchronousZookeeper extends Zookeeper {
  /**
   * Asynchronously creates a new node at the given path.
   * 
   * If a ''sequential'' [[Disposition disposition]] is provided in `disp`, then `path` is appended with a monotonically
   * increasing sequence, thus guaranteeing that all sequential nodes are unique with `path` as their prefix.
   * 
   * @param path the path of the node to create
   * @param data the data to associate with the node, which may be empty, but not `null`
   * @param acl an access control list to apply to the node, which must not be empty
   * @param disp the disposition of the node
   * @return a future yielding the final path of the created node, which will differ from `path` if `disp` is either
   * [[PersistentSequential]] or [[EphemeralSequential]]
   */
  def create(path: String, data: Array[Byte], acl: Seq[ACL], disp: Disposition): Future[String]

  /**
   * Asynchronously deletes the node specified by the given path.
   * 
   * @param path the path of the node
   * @param version a `Some` containing the expected version of the node or `None` if a version match is not required
   * @return a future, which upon success, yields `Unit`, otherwise one of the following exceptions:
   *  - `NoNodeException` if the node does not exist
   *  - `BadVersionException` if `version` is specified and does not match the node version
   *  - `NotEmptyException` if the node contains children
   */
  def delete(path: String, version: Option[Int]): Future[Unit]

  /**
   * Asynchronously gets the data and status of the node specified by the given path.
   * 
   * @param path the path of the node
   * @return a future, which upon success, yeilds a tuple containing the data and status of the node, otherwise one of the
   * following exceptions:
   *  - NoNodeException if the node does not exist
   */
  def get(path: String): Future[(Array[Byte], Status)]

  /**
   * Asynchronously sets the data for the node specified by the given path.
   * 
   * @param path the path of the node
   * @param data the data to associate with the node, which may be empty, but not `null`
   * @param version a `Some` containing the expected version of the node or `None` if a version match is not required
   * @return a future, which upon success, yields the status of the node, otherwise one of the following exceptions:
   *  - NoNodeException if the node does not exist
   *  - BadVersionException if `version` is specified and does not match the node version
   */
  def set(path: String, data: Array[Byte], version: Option[Int]): Future[Status]

  /**
   * Asynchronously determines the status of the node specified by the given path if it exists.
   * 
   * @param path the path of the node
   * @return a future yielding a `Some` containing the node status or `None` if the node does not exist
   */
  def exists(path: String): Future[Option[Status]]

  /**
   * Asynchronously gets the children of the node specified by the given path.
   * 
   * @param path the path of the node
   * @return a future, which upon success, yields an unordered sequence containing the names of each child node, otherwise one
   * of the following exceptions:
   *  - NoNodeException if the node does not exist
   */
  def children(path: String): Future[(Seq[String], Status)]

  /**
   * Asynchronously gets the ACL and status of the node specified by the given path.
   * 
   * @param path the path of the node
   * @return a future, which upon success, yields a tuple containing the ACL and status of the node, otherwise one of the
   * following exceptions:
   *  - NoNodeException if the node does not exist
   */
  def getACL(path: String): Future[(Seq[ACL], Status)]

  /**
   * Asynchronously sets the ACL for the node specified by the given path.
   * 
   * @param path the path of the node
   * @param acl an access control list to apply to the node, which must not be empty
   * @param version a `Some` containing the expected version of the node or `None` if a version match is not required
   * @return a future, which upon success, yields the status of the node, otherwise one of the following conditions:
   *  - NoNodeException if the node does not exist
   *  - BadVersionException if `version` is specified and does not match the node version
   */
  def setACL(path: String, acl: Seq[ACL], version: Option[Int]): Future[Status]

  /**
   * Returns an asynchronous client in which operations implicitly attach the specified watch function.
   * 
   * The partial function `fn` is invoked when a watch is triggered or the session state changes. This method is typically
   * used in a transient manner to introduce a watch function prior to performing a watchable ZooKeeper operation.
   * 
   * Example:
   * {{{
   * val zk = AsynchronousZookeeper(config)
   * val future = zk watch {
   *   case e: NodeEvent => ...
   *   case e: StateEvent => ...
   * } get "/foo"
   * ...
   * future onSuccess {
   *   case (data, node) => ...
   * }
   * future onFailure {
   *   case e: NoNodeException => ...
   * }
   * }}}
   */
  def watch(fn: PartialFunction[Event, Unit]): AsynchronousWatchableZookeeper
}

/**
 * A ZooKeeper client with ''asynchronous'' and ''watchable'' operations.
 */
trait AsynchronousWatchableZookeeper extends Zookeeper {
  /**
   * Asynchronously gets the data and status of the node specified by the given path and additionally sets a watch for any
   * changes.
   * 
   * The watch is triggered when one of the following conditions occur:
   *  - the data associated with the node changes
   *  - the node is deleted
   *  - the session state changes
   * 
   * @param path the path of the node
   * @return a future, which upon success, yields a tuple containing the data and status of the node, otherwise one of the
   * following exceptions:
   *  - NoNodeException if the node does not exist
   */
  def get(path: String): Future[(Array[Byte], Status)]

  /**
   * Asynchronously determines the status of the node specified by the given path if it exists and additionally sets a watch
   * for any changes.
   * 
   * The watch is triggered when one of the following conditions occur:
   *  - the data associated with the node changes
   *  - the node is created
   *  - the node is deleted
   *  - the session state changes
   * 
   * @param path the path of the node
   * @return a future yielding a `Some` containing the node status or `None` if the node does not exist
   */
  def exists(path: String): Future[Option[Status]]

  /**
   * Asynchronously gets the children of the node specified by the given path and additionally sets a watch for any changes.
   * 
   * The watch is triggered when one of the following conditions occur:
   *  - the session state changes
   * 
   * @param path the path of the node
   * @return a future, which upon success, yields an unordered sequence containing the names of each child node, otherwise
   * one of the following exceptions:
   *  - NoNodeException if the node does not exist
   */
  def children(path: String): Future[(Seq[String], Status)]
}

private class BaseZK(zk: ZooKeeper, exec: ExecutionContext) extends Zookeeper {
  private implicit val _exec = exec

  def sync: SynchronousZookeeper = new SynchronousZK(zk, exec)

  def async: AsynchronousZookeeper = new AsynchronousZK(zk, exec)

  def ensure(path: String): Future[Unit] = {
    val p = promise[Unit]
    zk.sync(path, VoidHandler(p), null)
    p.future
  }

  def close(): Unit = zk.close()
}

private class SynchronousZK(zk: ZooKeeper, exec: ExecutionContext) extends BaseZK(zk, exec) with SynchronousZookeeper {
  def create(path: String, data: Array[Byte], acl: Seq[ACL], disp: Disposition) = {
    zk.create(path, data, ACL.toZACL(acl), disp.mode)
  }

  def delete(path: String, version: Option[Int]) {
    zk.delete(path, version getOrElse -1)
  }

  def get(path: String): (Array[Byte], Status) = {
    val stat = new Stat
    val data = zk.getData(path, false, stat)
    (if (data == null) Array() else data, Status(path, stat))
  }

  def set(path: String, data: Array[Byte], version: Option[Int]): Status = {
    val stat = zk.setData(path, data, version getOrElse -1)
    Status(path, stat)
  }

  def exists(path: String): Option[Status] = {
    val stat = zk.exists(path, false)
    if (stat == null) None else Some(Status(path, stat))
  }

  def children(path: String): Seq[String] = {
    zk.getChildren(path, false).asScala.toList
  }

  def getACL(path: String): (Seq[ACL], Status) = {
    val stat = new Stat
    val zacl = zk.getACL(path, stat)
    (ACL(zacl), Status(path, stat))
  }

  def setACL(path: String, acl: Seq[ACL], version: Option[Int]): Status = {
    val stat = zk.setACL(path, ACL.toZACL(acl), version getOrElse -1)
    Status(path, stat)
  }

  def watch(fn: PartialFunction[Event, Unit]): SynchronousWatchableZookeeper = {
    new SynchronousWatchableZK(zk, exec, fn)
  }

  def transact(ops: Seq[Operation]): Either[Seq[Problem], Seq[Result]] = {
    try {
      val _ops = ops map { _.op }
      val results = zk.multi(_ops.asJava).asScala
      Right(ops zip results map {
        case (op, result) => op match {
          case _op: CreateOperation => CreateResult(_op, result.asInstanceOf[OpResult.CreateResult].getPath)
          case _op: DeleteOperation => DeleteResult(_op)
          case _op: SetOperation => SetResult(_op, Status(_op.path, result.asInstanceOf[OpResult.SetDataResult].getStat))
          case _op: CheckOperation => CheckResult(_op)
        }
      })
    } catch {
      case e: KeeperException if e.getResults != null =>
        Left(ops zip e.getResults.asScala map {
          case (op, result) =>
            val rc = result.asInstanceOf[OpResult.ErrorResult].getErr
            val error = if (rc == 0) None else Some(ZException.create(Code.get(rc)))
            op match {
              case _op: CreateOperation => CreateProblem(_op, error)
              case _op: DeleteOperation => DeleteProblem(_op, error)
              case _op: SetOperation => SetProblem(_op, error)
              case _op: CheckOperation => CheckProblem(_op, error)
            }
        })
    }
  }
}

private class SynchronousWatchableZK(zk: ZooKeeper, exec: ExecutionContext, fn: PartialFunction[Event, Unit])
      extends BaseZK(zk, exec) with SynchronousWatchableZookeeper {
  private[this] val watcher = new Watcher {
    def process(event: WatchedEvent) {
      val e = Event(event)
      if (fn.isDefinedAt(e)) fn(e)
    }
  }

  def get(path: String): (Array[Byte], Status) = {
    val stat = new Stat
    val data = zk.getData(path, watcher, stat)
    (if (data == null) Array() else data, Status(path, stat))
  }

  def exists(path: String): Option[Status] = {
    val stat = zk.exists(path, watcher)
    if (stat == null) None else Some(Status(path, stat))
  }

  def children(path: String): Seq[String] = {
    zk.getChildren(path, watcher).asScala.toList
  }
}

private class AsynchronousZK(zk: ZooKeeper, exec: ExecutionContext) extends BaseZK(zk, exec) with AsynchronousZookeeper {
  private implicit val _exec = exec

  def create(path: String, data: Array[Byte], acl: Seq[ACL], disp: Disposition): Future[String] = {
    val p = promise[String]
    zk.create(path, data, ACL.toZACL(acl), disp.mode, StringHandler(p), null)
    p.future
  }

  def delete(path: String, version: Option[Int]): Future[Unit] = {
    val p = promise[Unit]
    zk.delete(path, version getOrElse -1, VoidHandler(p), null)
    p.future
  }

  def get(path: String): Future[(Array[Byte], Status)] = {
    val p = promise[(Array[Byte], Status)]
    zk.getData(path, false, DataHandler(p), null)
    p.future
  }

  def set(path: String, data: Array[Byte], version: Option[Int]): Future[Status] = {
    val p = promise[Status]
    zk.setData(path, data, version getOrElse -1, StatHandler(p), null)
    p.future
  }

  def exists(path: String): Future[Option[Status]] = {
    val p = promise[Option[Status]]
    zk.exists(path, false, ExistsHandler(p), null)
    p.future
  }

  def children(path: String): Future[(Seq[String], Status)] = {
    val p = promise[(Seq[String], Status)]
    zk.getChildren(path, false, ChildrenHandler(p), null)
    p.future
  }

  def getACL(path: String): Future[(Seq[ACL], Status)] = {
    val p = promise[(Seq[ACL], Status)]
    zk.getACL(path, new Stat, ACLHandler(p), null)
    p.future
  }

  def setACL(path: String, acl: Seq[ACL], version: Option[Int]): Future[Status] = {
    val p = promise[Status]
    zk.setACL(path, ACL.toZACL(acl), version getOrElse -1, StatHandler(p), null)
    p.future
  }

  def watch(fn: PartialFunction[Event, Unit]): AsynchronousWatchableZookeeper = {
    new AsynchronousWatchableZK(zk, exec, fn)
  }
}

private class AsynchronousWatchableZK(zk: ZooKeeper, exec: ExecutionContext, fn: PartialFunction[Event, Unit])
      extends BaseZK(zk, exec) with AsynchronousWatchableZookeeper {
  private implicit val _exec = exec

  private[this] val watcher = new Watcher {
    def process(event: WatchedEvent) {
      val e = Event(event)
      if (fn.isDefinedAt(e)) fn(e)
    }
  }

  def get(path: String): Future[(Array[Byte], Status)] = {
    val p = promise[(Array[Byte], Status)]
    zk.getData(path, watcher, DataHandler(p), null)
    p.future
  }

  def exists(path: String): Future[Option[Status]] = {
    val p = promise[Option[Status]]
    zk.exists(path, watcher, ExistsHandler(p), null)
    p.future
  }

  def children(path: String): Future[(Seq[String], Status)] = {
    val p = promise[(Seq[String], Status)]
    zk.getChildren(path, watcher, ChildrenHandler(p), null)
    p.future
  }
}

private object StringHandler {
  def apply(p: Promise[String]) = new AsyncCallback.StringCallback {
    def processResult(rc: Int, path: String, context: Object, name: String) {
      (Code.get(rc): @unchecked) match {
        case Code.OK => p success name
        case code => p failure ZException.create(code)
      }
    }
  }
}

private object VoidHandler {
  def apply(p: Promise[Unit]) = new AsyncCallback.VoidCallback {
    def processResult(rc: Int, path: String, context: Object) {
      (Code.get(rc): @unchecked) match {
        case Code.OK => p success ()
        case code => p failure ZException.create(code)
      }
    }
  }
}

private object DataHandler {
  def apply(p: Promise[(Array[Byte], Status)]) = new AsyncCallback.DataCallback {
    def processResult(rc: Int, path: String, context: Object, data: Array[Byte], stat: Stat) {
      (Code.get(rc): @unchecked) match {
        case Code.OK => p success (if (data == null) Array() else data, Status(path, stat))
        case code => p failure ZException.create(code)
      }
    }
  }
}

private object ChildrenHandler {
  def apply(p: Promise[(Seq[String], Status)]) = new AsyncCallback.Children2Callback {
    def processResult(rc: Int, path: String, context: Object, children: java.util.List[String], stat: Stat) {
      (Code.get(rc): @unchecked) match {
        case Code.OK => p success (children.asScala.toList, Status(path, stat))
        case code => p failure ZException.create(code)
      }
    }
  }
}

private object ACLHandler {
  def apply(p: Promise[(Seq[ACL], Status)]) = new AsyncCallback.ACLCallback {
    def processResult(rc: Int, path: String, context: Object, zacl: java.util.List[ZACL], stat: Stat) {
      (Code.get(rc): @unchecked) match {
        case Code.OK => p success (ACL(zacl), Status(path, stat))
        case code => p failure ZException.create(code)
      }
    }
  }
}

private object StatHandler {
  def apply(p: Promise[Status]) = new AsyncCallback.StatCallback {
    def processResult(rc: Int, path: String, context: Object, stat: Stat) {
      (Code.get(rc): @unchecked) match {
        case Code.OK => p success Status(path, stat)
        case code => p failure ZException.create(code)
      }
    }
  }
}

private object ExistsHandler {
  def apply(p: Promise[Option[Status]]) = new AsyncCallback.StatCallback {
    def processResult(rc: Int, path: String, context: Object, stat: Stat) {
      (Code.get(rc): @unchecked) match {
        case Code.OK => p success (if (stat == null) None else Some(Status(path, stat)))
        case code => p failure ZException.create(code)
      }
    }
  }
}

/**
 * Constructs [[Zookeeper]] values.
 */
object Zookeeper {
  /**
   * Constructs a new ZooKeeper client using the given configuration.
   * 
   * @param config the client configuration
   * @return a client with the given `config`
   */
  def apply(config: Configuration): Zookeeper = apply(config, null)

  /**
   * Constructs a new ZooKeeper client using the given configuration and session credential.
   * 
   * @param config the client configuration
   * @param cred the session credentials
   * @return a client with the given `config` and `cred`
   */
  def apply(config: Configuration, cred: Credential): Zookeeper = {
    val servers = ("" /: config.servers) {
      case (buf, addr) => (if (buf.isEmpty) buf else buf + ",") + addr.getHostName + ":" + addr.getPort
    }
    val path = (if (config.path startsWith "/") "" else "/") + config.path
    val timeout = {
      val millis = config.timeout.toMillis
      if (millis < 0) 0 else if (millis > Int.MaxValue) Int.MaxValue else millis.asInstanceOf[Int]
    }
    val watcher = {
      val fn = if (config.watcher == null) (_: StateEvent, _: Session) => () else config.watcher
      new ConnectionWatcher(fn)
    }
    val exec = if (config.exec == null) ExecutionContext.global else config.exec
    val zk = cred match {
      case null => new ZooKeeper(servers + path, timeout, watcher, config.allowReadOnly)
      case _ => new ZooKeeper(servers + path, timeout, watcher, cred.id, cred.password, config.allowReadOnly)
    }
    watcher.associate(zk)
    new BaseZK(zk, exec)
  }

  private class ConnectionWatcher(fn: (StateEvent, Session) => Unit) extends Watcher {
    private[this] val ref = new AtomicReference[ZooKeeper]

    def process(e: WatchedEvent) {
      @tailrec def waitfor(zk: ZooKeeper): ZooKeeper = {
        if (zk == null) waitfor(ref.get()) else zk
      }
      val zk = waitfor(ref.get())
      Event(e) match {
        case event: StateEvent => fn(event, Session(zk))
        case event => throw new AssertionError("unexpected event: " + event)
      }
    }

    def associate(zk: ZooKeeper) {
      if (!ref.compareAndSet(null, zk))
        throw new AssertionError("zookeeper instance already associated")
    }
  }
}

/**
 * Constructs [[SynchronousZookeeper]] values.
 * 
 * This companion object is provided as a convenience and is equivalent to:
 * {{{
 * Zookeeper(...).sync
 * }}}
 * 
 * @see [[Zookeeper]]
 */
object SynchronousZookeeper {
  /**
   * Constructs a new synchronous ZooKeeper client using the given configuration.
   * 
   * @param config the client configuration
   * @return a client with the given `config`
   */
  def apply(config: Configuration): SynchronousZookeeper = Zookeeper(config).sync

  /**
   * Constructs a new synchronous ZooKeeper client using the given configuration and session credential.
   * 
   * @param config the client configuration
   * @param cred the session credentials
   * @return a client with the given `config` and `cred`
   */
  def apply(config: Configuration, cred: Credential): SynchronousZookeeper = Zookeeper(config, cred).sync
}

/**
 * Constructs [[AsynchronousZookeeper]] values.
 * 
 * This companion object is provided as a convenience and is equivalent to:
 * {{{
 * Zookeeper(...).async
 * }}}
 * 
 * @see [[Zookeeper]]
 */
object AsynchronousZookeeper {
  /**
   * Constructs a new asynchronous ZooKeeper client using the given configuration.
   * 
   * @param config the client configuration
   * @return a client with the given `config`
   */
  def apply(config: Configuration): AsynchronousZookeeper = Zookeeper(config).async

  /**
   * Constructs a new asynchronous ZooKeeper client using the given configuration and session credential.
   * 
   * @param config the client configuration
   * @param cred the session credentials
   * @return a client with the given `config` and `cred`
   */
  def apply(config: Configuration, cred: Credential): AsynchronousZookeeper = Zookeeper(config, cred).async
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy