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

com.reactific.helpers.Registration.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015-2017 Reactific Software LLC
 *
 * 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.reactific.helpers

import java.util.concurrent.ConcurrentHashMap

import scala.collection.JavaConverters._
import scala.language.postfixOps
import scala.util.Random

trait Identifiable {
  def id: Symbol
  lazy val label: String = id.name
}

trait IdentifiedWithRegistry extends Identifiable {
  def registry: Registry[_]
  def registryName: String = registry.registryName
}

/** Mix this in to anything you want to register and define the "id";
  * then pass that object to the Registrar you want
 */
// scalastyle:off
trait Registrable[T <: Registrable[T]]
    extends IdentifiedWithRegistry with LoggingHelper { self: T ⇒
  def registry: Registry[T]
  def register(): Unit = registry.register(self)
  def unregister(): Unit = registry.unregister(self)
  def isRegistered: Boolean = registry.isRegistered(id)
  def reference: RegistryReference[T] = RegistryReference(registry, id)
  override def finalize(): Unit = { // scalastyle:ignore
    try {
      if (isRegistered) unregister()
    } catch {
      case xcptn: Throwable ⇒
        /* ignore exceptions to always garbage collect the object */
        log.trace(
          s"Ignoring failure to unregister $label at finalize so " +
            "exception doesn't thwart GC. Cause:",
          xcptn
        )
    }
  }
  register()
}
// scalastyle:on

/** Abstract Registry Of Key-Value Pairs
 * This trait implements a key-value map using ConcurrentHashMap to minimize
 * lock contention for multiple threads accessing the registry. It is
 * intended to identify race conditions upon insertion and deletion of entries.
 * @tparam K The type of Key
 * @tparam V The type of Value
 */
trait AbstractRegistry[K <: Any, V <: AnyRef]
    extends LoggingHelper with ThrowingHelper {
  def contains(key: K): Boolean = _registrants.contains(key)

  def exists(name: K): Boolean = _registrants.contains(name)

  def containsValue(value: V): Boolean = _registrants.values.exists { item ⇒
    item == value
  }

  def lookup(key: K): Option[V] = _registrants.get(key)

  def lookupOrElse(key: K, value: V): Option[V] = {
    _registrants.putIfAbsent(key, value)
    _registrants.get(key)
  }

  def pick(index: Int): K = {
    require(
      index >= 0 && index < size,
      s"Registrant index ($index) out of range [0-$size)"
    )
    _registrants.keySet.toSeq(index)
  }

  def size: Int = _registrants.size

  def isEmpty: Boolean = _registrants.isEmpty

  def nonEmpty: Boolean = _registrants.nonEmpty

  def values: Seq[V] = _registrants.values.toSeq

  def keys: Seq[K] = _registrants.keys.toSeq

  def select(f: ((K, V)) => Boolean): Iterable[V] = {
    for { (k, v) <- _registrants if f((k, v)) } yield { v }
  }

  def map[W](f: ((K, V)) => (K, W)): Map[K, W] = {
    _registrants.map { (x) =>
      f(x)
    } toMap
  }

  protected final def _register(key: K, obj: V): obj.type = {
    _registrants.putIfAbsent(key, obj) match {
      case Some(otherObj) =>
        if (otherObj != obj) {
          toss(
            s"Insertion of $key was preempted by another thread. " +
              "Fix your race condition."
          )
        } else {
          log.warn(
            s"Preemptive replacement of $key yielding same value is " +
              "indicative of race condition."
          )
          obj
        }
      case None =>
        obj
    }
  }

  protected final def _unregister(key: K): Unit = {
    _registrants.remove(key) match {
      case Some(_) =>
      // ignore
      case None =>
        toss(
          s"Removal of $key was preempted by another thread. " +
            s"Fix your race condition."
        )
    }
  }

  private[this] val _registrants = new ConcurrentHashMap[K, V] asScala

}

/** A trait for specifying the registration of some type of object (T)
  * which must have at least Registrable mixed in.
  * This abstracts the notion of a registry of objects that conform to an
  * interface.
 */
trait Registry[T <: Registrable[T]] extends AbstractRegistry[Symbol, T] {
  def registryName: String
  def registrantsName: String
  override protected def createLoggerName = s"${registryName}Registry"

  def isRegistered(name: Symbol): Boolean = contains(name)
  def getRegistrant(name: Symbol): Option[T] = lookup(name)

  def apply(name: Symbol): Option[T] = lookup(name)

  def find(ids: Seq[Symbol]): Seq[T] = {
    values.filter { t ⇒
      ids.contains(t.id)
    }
  }

  def as[U <: Registrable[T]](id: Symbol): U = {
    this(id) match {
      case Some(typ) ⇒ typ.asInstanceOf[U]
      case None ⇒ toss(s"Could not find $registrantsName named '$id'")
    }
  }

  def register(thing: T): Unit = {
    val theName = s""""${thing.id.name}"""" // scalastyle:ignore
    // If id is null, let's fail before we do anything
    if (contains(thing.id)) {
      toss(
        s"There is already a $registrantsName " +
          s"named $theName registered with $registryName"
      )
    }
    _register(thing.id, thing)
    log.trace(
      s"Registered ${thing.getClass.getName} as $theName " +
        s"with $size other ${Pluralizer.pluralize(registrantsName)}"
    )
  }

  def unregister(thing: T): Unit = {
    val theName = s""""${thing.id.name}"""" // scalastyle:ignore
    // If id is null, let's fail before we do anything
    if (!contains(thing.id)) {
      toss(
        s"There is no $registrantsName named $theName " +
          s"registered with $registryName"
      )
    }
    _unregister(thing.id)
    log.trace(
      s"Unregistered ${thing.getClass.getName} as $theName with " +
        s"$size other ${Pluralizer.pluralize(registrantsName)}"
    )
  }

  val rand = new Random(System.currentTimeMillis())

  def pick: T = {
    val random_index =
      if (size == 0) {
        toss("Can't pick from an empty registry")
      } else {
        rand.nextInt(size)
      }
    val key: Symbol = pick(random_index)
    getRegistrant(key).get
  }
}

/** Reference A Memory Object From The Database
  *
  * Many of the objects used in Scrupal are not stored in the database.
  * Essentially those things coming from a Module are just retained in
  * memory: Module, Feature, Entity, Type, Handler, etc. However, we need to
  * reference these things from objects that are stored in the database. We
  * do not want to be loading the low level memory objects all the time but
  * would just like to "find" them. That's what the RegistryReference is for.
  * We can register long-lived immutable objects in a registry and find them
  * later, by name. But, there is a registry per object type, by design. So,
  * we need a way to capture a reference to a named object in one of the
  * registries. We do that by simply caching the registry object and
  * the registrable object's id. This information is then stored in the
  * database and reconstructed into the referenced object with the apply method.
  */
case class RegistryReference[T <: Registrable[T]] private[helpers] (
  registry: Registry[T],
  id: Symbol)
    extends (() ⇒ Option[T]) {

  def as[C](): Option[C] = this.apply().map { x ⇒
    x.asInstanceOf[C]
  }

  def apply(): Option[T] = {
    registry.lookup(id)
  }
}

object RegistryReference {

  def to[T <: Registrable[T]](o: T): RegistryReference[T] = {
    RegistryReference[T](o.registry, o.id)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy