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

epic.util.Cache.scala Maven / Gradle / Ivy

The newest version!
package epic.util

import java.io.File
import java.util.Collections
import org.mapdb.{Pump, Serializer, DBMaker}
import scala.collection.JavaConverters._
import scala.collection.concurrent.Map
import java.util

import scala.collection.{mutable, GenTraversableOnce}
import com.typesafe.scalalogging.slf4j.LazyLogging

@SerialVersionUID(1L)
case class CacheBroker(path: File = null, copyFrom: File = null, clearCaches: String = "", autocommit: Boolean = true, disableWriteAheadLog: Boolean = false) extends Serializable {
  // this is how one makes a transient lazy val, sadly.
  @transient
  private var _actualCache:CacheBroker.ActualCache = null
  private def actualCache = synchronized {
    lazy val dbMaker = if(path eq null) {
      DBMaker.newMemoryDB()
    } else {
      DBMaker.newFileDB(path)
    }.closeOnJvmShutdown().cacheSoftRefEnable()

    if(_actualCache eq null) {
      _actualCache = CacheBroker.getCacheBroker(path, dbMaker, autocommit, copyFrom)
    }
    if(disableWriteAheadLog) _actualCache.dbMaker.writeAheadLogDisable()
    if(clearCaches != null && clearCaches.nonEmpty)
      for(toDisable <- clearCaches.split(",")) {
        _actualCache.db.getHashMap(toDisable).clear()
      }

    _actualCache
  }


  def dbMaker = actualCache.dbMaker
  def db = actualCache.db


  def commit() { db.commit()}
  def close() {actualCache.close()}

  def make[K,V](name: String)(implicit kser: Serializer[K] = null, vser: Serializer[V] = null): Map[K, V] = new CacheBroker.CacheMap[K, V](name, this)

}

object CacheBroker extends LazyLogging {
  private class ActualCache private[CacheBroker] (val path: File, val dbMaker: DBMaker, val autocommit: Boolean, copyFrom: File = null) {
    lazy val db = {
      val db = dbMaker.make()
      if(copyFrom != null) {
        logger.info(s"Copying database from $copyFrom to ${if (path ne null) path else "in memory database"}")
        val from = DBMaker.newFileDB(copyFrom).make()
        Pump.copy(from, db)
        from.close()
      }
      if(autocommit) cacheThread.start()

      db
    }
    private lazy val cacheThread: Thread = new Thread(new Runnable {
      def run() {
        try {
          while(!db.isClosed && !Thread.interrupted()) {
            Thread.sleep(1000 * 60)
            if(!db.isClosed)
              db.commit()
          }
        } catch {
          case ex: InterruptedException =>
          case ex: IllegalAccessError =>
        }
      }
    }) {
      setDaemon(true)
    }

    def close() {
      db.close()
      cacheThread.interrupt()
      if (path ne null)
        cacheCache -= path
    }
  }

  private val cacheCache = Collections.synchronizedMap(new util.HashMap[File, ActualCache]()).asScala

  private def getCacheBroker(path: File, dbMaker: =>DBMaker, autocommit: Boolean, copyFrom: File) = {
    if(path eq null) new ActualCache(path, dbMaker, autocommit)
    else cacheCache.getOrElseUpdate(path, new ActualCache(path, dbMaker, autocommit, copyFrom))
  }


  @SerialVersionUID(1L)
  private class CacheMap[K, V](name: String, cache: CacheBroker)(implicit kser: Serializer[K], vser: Serializer[V]) extends Map[K, V] with Serializable {
    import cache._
    @transient
    private var _theMap : Map[K, V] = null
    def theMap = synchronized {
      if(_theMap eq null) {
        _theMap = try {
          // this throws if the hash map exists, and there's no "does it exist" method
          // that takes the serializers...
          db.createHashMap[K, V](name, true, kser, vser).asScala
        } catch {
          case ex: IllegalArgumentException => db.getHashMap[K, V](name).asScala
        }
      }
      _theMap
    }


    def +=(kv: (K, V)): this.type = {theMap += kv; this}

    def -=(key: K): this.type = {theMap -= key; this}

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

    def iterator: Iterator[(K, V)] = theMap.iterator

    def putIfAbsent(k: K, v: V): Option[V] = theMap.putIfAbsent(k: K, v: V)

    def remove(k: K, v: V): Boolean = theMap.remove(k: K, v: V)

    def replace(k: K, oldvalue: V, newvalue: V): Boolean = theMap.replace(k: K, oldvalue: V, newvalue: V)

    def replace(k: K, v: V): Option[V] = theMap.replace(k: K, v: V)

    override def size: Int =  theMap.size

    override def put(key: K, value: V): Option[V] = theMap.put(key: K, value: V): Option[V]

    override def update(key: K, value: V) {
      theMap.update(key, value)
    }

    override def updated[B1 >: V](key: K, value: B1): mutable.Map[K, B1] = theMap.updated(key, value)

    override def +[B1 >: V](kv: (K, B1)): mutable.Map[K, B1] = theMap.+(kv)

    override def +[B1 >: V](elem1: (K, B1), elem2: (K, B1), elems: (K, B1)*): mutable.Map[K, B1] = theMap.+(elem1, elem2, elems:_*)

    override def ++[B1 >: V](xs: GenTraversableOnce[(K, B1)]): mutable.Map[K, B1] = theMap.++(xs)

    override def remove(key: K): Option[V] = theMap.remove(key)

    override def -(key: K): mutable.Map[K, V] = theMap.-(key)

    override def clear() {
      theMap.clear()
    }

    override def getOrElseUpdate(key: K, op: => V): V = theMap.getOrElseUpdate(key, op)

    override def transform(f: (K, V) => V): this.type = {theMap.transform(f);this}

    override def retain(p: (K, V) => Boolean): this.type = {theMap.retain(p); this}

    override def clone(): mutable.Map[K, V] = theMap.clone()

    override def result(): mutable.Map[K, V] = theMap.result()

    override def -(elem1: K, elem2: K, elems: K*): mutable.Map[K, V] = theMap.-(elem1, elem2, elems:_*)

    override def --(xs: GenTraversableOnce[K]): mutable.Map[K, V] = theMap.--(xs)

  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy