
com.twitter.finagle.Dtab.scala Maven / Gradle / Ivy
package com.twitter.finagle
import com.twitter.app.Flaggable
import com.twitter.io.Buf
import com.twitter.util.Local
import java.io.PrintWriter
import scala.collection.generic.CanBuildFrom
import scala.collection.immutable.VectorBuilder
import scala.collection.mutable.Builder
/**
* A Dtab--short for delegation table--comprises a sequence of
* delegation rules. Together, these describe how to bind a
* [[com.twitter.finagle.Path]] to a set of
* [[com.twitter.finagle.Addr]]. [[com.twitter.finagle.naming.DefaultInterpreter]]
* implements the default binding stategy.
*
* @see The [[http://twitter.github.io/finagle/guide/Names.html#interpreting-paths-with-delegation-tables user guide]]
* for further details.
*/
case class Dtab(dentries0: IndexedSeq[Dentry])
extends IndexedSeq[Dentry] {
private lazy val dentries = dentries0.reverse
def apply(i: Int): Dentry = dentries0(i)
def length: Int = dentries0.length
override def isEmpty: Boolean = dentries0.isEmpty
/**
* Lookup the given `path` with this dtab.
*/
def lookup(path: Path): NameTree[Name.Path] = {
val matches = dentries.collect {
case Dentry(prefix, dst) if prefix.matches(path) =>
val suff = path.drop(prefix.size)
dst.map { pfx => Name.Path(pfx ++ suff) }
}
matches.size match {
case 0 => NameTree.Neg
case 1 => matches.head
case _ => NameTree.Alt(matches:_*)
}
}
/**
* Construct a new Dtab with the given delegation
* entry appended.
*/
def +(dentry: Dentry): Dtab =
Dtab(dentries0 :+ dentry)
/**
* Java API for '+'
*/
def append(dentry: Dentry): Dtab = this + dentry
/**
* Construct a new Dtab with the given dtab appended.
*/
def ++(dtab: Dtab): Dtab = {
if (dtab.isEmpty) this
else Dtab(dentries0 ++ dtab.dentries0)
}
/**
* Java API for '++'
*/
def concat(dtab: Dtab): Dtab = this ++ dtab
/**
* Efficiently removes prefix `prefix` from `dtab`.
*/
def stripPrefix(prefix: Dtab): Dtab = {
if (this eq prefix) return Dtab.empty
if (isEmpty) return this
if (size < prefix.size) return this
var i = 0
while (i < prefix.size) {
val d1 = this(i)
val d2 = prefix(i)
if (d1 != d2)
return this
i += 1
}
if (i == size)
Dtab.empty
else
Dtab(this drop prefix.size)
}
/**
* Print a pretty representation of this Dtab.
*/
def print(printer: PrintWriter) {
printer.println("Dtab("+size+")")
for (Dentry(prefix, dst) <- this)
printer.println(" "+prefix.show+" => "+dst.show)
}
/**
* Simplify the Dtab. This returns a functionally equivalent Dtab
* whose destination name trees have been simplified. The returned
* Dtab is equivalent with respect to evaluation.
*
* @todo dedup equivalent entries so that only the last entry is retained
* @todo collapse entries with common prefixes
*/
def simplified: Dtab = Dtab({
val simple = this map {
case Dentry(prefix, dst) => Dentry(prefix, dst.simplified)
}
// Negative destinations are no-ops
simple.filter(_.dst != NameTree.Neg)
})
def show: String = dentries0 map (_.show) mkString ";"
override def toString: String = "Dtab("+show+")"
}
/**
* Trait Dentry describes a delegation table entry. `prefix` describes
* the paths that the entry applies to. `dst` describes the resulting
* tree for this prefix on lookup.
*/
case class Dentry(prefix: Dentry.Prefix, dst: NameTree[Path]) {
def show: String = "%s=>%s".format(prefix.show, dst.show)
override def toString: String = "Dentry("+show+")"
}
object Dentry {
/** Build a [[Dentry]] with a [[Path]] prefix. */
def apply(path: Path, dst: NameTree[Path]): Dentry =
Dentry(Prefix(path.elems.map(Prefix.Label(_)):_*), dst)
/**
* Parse a Dentry from the string `s` with concrete syntax:
* {{{
* dentry ::= prefix '=>' tree
* }}}
*
* where the production `prefix` is from the grammar documented in
* [[Prefix.read]] and the production `tree` is from the grammar
* documented in [[com.twitter.finagle.NameTree.read
* NameTree.read]].
*/
def read(s: String): Dentry = NameTreeParsers.parseDentry(s)
// The prefix to this is an illegal path in the sense that the
// concrete syntax will not admit it. It will do for a no-op.
val nop: Dentry = Dentry(Prefix(Prefix.Label("/")), NameTree.Neg)
implicit val equiv: Equiv[Dentry] = new Equiv[Dentry] {
def equiv(d1: Dentry, d2: Dentry): Boolean = (
d1.prefix == d2.prefix &&
d1.dst.simplified == d2.dst.simplified
)
}
/**
* A Prefix comprises a [[Path]]-matching expression.
*
* Each element in a prefix may be either a [[Dentry.Prefix.Label]]
* or [[Dentry.Prefix.AnyElem]].
* When matching a [[Path]], Label-elements must match exactly,
* while Any-elements are ignored.
*/
case class Prefix(elems: Prefix.Elem*) {
def matches(path: Path): Boolean = {
if (this.size > path.size)
return false
var i = 0
while (i != this.size)
elems(i) match {
case Prefix.Label(buf) if buf != path.elems(i) =>
return false
case _ => // matches
i += 1
}
true
}
// A prefix acts somewhat like a Seq[Elem]
def take(n: Int): Prefix = Prefix(elems.take(n):_*)
def drop(n: Int): Prefix = Prefix(elems.drop(n):_*)
def ++(that: Prefix): Prefix =
if (that.isEmpty) this
else Prefix((elems ++ that.elems):_*)
def size: Int = elems.size
def isEmpty: Boolean = elems.isEmpty
def ++(path: Path): Prefix =
if (path.isEmpty) this
else this ++ Prefix(path)
lazy val showElems: Seq[String] = elems.map(_.show)
lazy val show: String = showElems.mkString("/", "/", "")
override def toString: String = s"""Prefix(${showElems.mkString(",")})"""
}
object Prefix {
def apply(path: Path): Prefix =
Prefix(path.elems.map(Label(_)):_*)
/**
* Parse `s` as a prefix matching expression with concrete syntax
*
* {{{
* path ::= '/' elems | '/'
*
* elems ::= elem '/' elem | elem
*
* elem ::= '*' | label
*
* label ::= (\\x[a-f0-9][a-f0-9]|[0-9A-Za-z:.#$%-_])+
*
* }}}
*
* for example
*
* {{{
* /foo/bar/baz
* /foo/*/bar/baz
* /
* }}}
*
* parses into the path
*
* {{{
* Prefix(Label(foo),Label(bar),Label(baz))
* Prefix(Label(foo),AnyElem,Label(bar),Label(baz))
* Prefix()
* }}}
*
* @throws IllegalArgumentException when `s` is not a syntactically valid path.
*
* Note: There is a Java-friendly API for this method: [[Dentry.readPrefix]].
*/
def read(s: String): Prefix = NameTreeParsers.parseDentryPrefix(s)
val empty: Prefix = new Prefix()
sealed trait Elem {
def show: String
}
object AnyElem extends Elem {
val show = "*"
}
case class Label(buf: Buf) extends Elem {
require(!buf.isEmpty)
lazy val show: String = Path.showElem(buf)
}
object Label {
def apply(s: String): Label = Label(Buf.Utf8(s))
}
}
/**
* Java compatibility method for `Dentry.Prefix.read`
*/
def readPrefix(s: String): Prefix = Prefix.read(s)
}
/**
* Object Dtab manages 'base' and 'local' Dtabs.
*/
object Dtab {
implicit val equiv: Equiv[Dtab] = new Equiv[Dtab] {
def equiv(d1: Dtab, d2: Dtab): Boolean = (
d1.size == d2.size &&
d1.zip(d2).forall { case (de1, de2) => Equiv[Dentry].equiv(de1, de2) }
)
}
/**
* A failing delegation table.
*/
val fail: Dtab = Dtab.read("/=>!")
/**
* An empty delegation table.
*/
val empty: Dtab = Dtab(Vector.empty)
/**
* The base, or "system", or "global", delegation table applies to
* every request in this process. It is generally set at process
* startup, and not changed thereafter.
*/
@volatile var base: Dtab = empty
/**
* Java API for `base_=`
*/
def setBase(dtab: Dtab): Unit =
base = dtab
private[this] val l = new Local[Dtab]
/**
* The local, or "per-request", delegation table applies to the
* current [[com.twitter.util.Local Local]] scope which is usually
* defined on a per-request basis. Finagle uses the Dtab
* `Dtab.base ++ Dtab.local` to bind [[com.twitter.finagle.Name.Path
* Paths]] via a [[com.twitter.finagle.naming.NameInterpreter]].
*
* Local's scope is dictated by [[com.twitter.util.Local Local]].
*
* The local dtab is serialized into outbound requests when
* supported protocols are used. (Http, Thrift via TTwitter, Mux,
* and ThriftMux are among these.) The upshot is that `local` is
* defined for the entire request graph, so that a local dtab
* defined here will apply to downstream services as well.
*/
def local: Dtab = l() match {
case Some(dtab) => dtab
case None => Dtab.empty
}
def local_=(dtab: Dtab): Unit =
l() = dtab
/**
* Java API for `local_=`
*/
def setLocal(dtab: Dtab): Unit =
local = dtab
def unwind[T](f: => T): T = {
val save = l()
try f finally l.set(save)
}
/**
* Parse a Dtab from string `s` with concrete syntax
*
* {{{
* dtab ::= dentry ';' dtab | dentry
* }}}
*
* where the production `dentry` is from the grammar documented in
* [[com.twitter.finagle.Dentry.read Dentry.read]]
*
*/
def read(s: String): Dtab = NameTreeParsers.parseDtab(s)
/** Scala collection plumbing required to build new dtabs */
def newBuilder: DtabBuilder = new DtabBuilder
implicit val canBuildFrom: CanBuildFrom[TraversableOnce[Dentry], Dentry, Dtab] =
new CanBuildFrom[TraversableOnce[Dentry], Dentry, Dtab] {
def apply(_ign: TraversableOnce[Dentry]): DtabBuilder = newBuilder
def apply(): DtabBuilder = newBuilder
}
/**
* implicit conversion from [[com.twitter.finagle.Dtab]] to
* [[com.twitter.app.Flaggable]], allowing Dtabs to be easily used as
* [[com.twitter.app.Flag]]s
*/
implicit val flaggable: Flaggable[Dtab] = new Flaggable[Dtab] {
override def default: Option[Dtab] = None
def parse(s: String): Dtab = Dtab.read(s)
override def show(dtab: Dtab): String = dtab.show
}
}
final class DtabBuilder extends Builder[Dentry, Dtab] {
private[this] val builder = new VectorBuilder[Dentry]
def +=(d: Dentry): this.type = {
builder += d
this
}
def clear(): Unit = builder.clear()
def result(): Dtab = Dtab(builder.result)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy