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

com.github.mumoshu.play2.memcached.MemcachedCacheApi.scala Maven / Gradle / Ivy

The newest version!
package com.github.mumoshu.play2.memcached

import akka.Done

import com.typesafe.config.Config
import net.spy.memcached.transcoders.Transcoder
import net.spy.memcached.internal.{ GetCompletionListener, GetFuture, OperationCompletionListener, OperationFuture }
import net.spy.memcached.ops.StatusCode
import play.api.cache.AsyncCacheApi
import play.api.{Environment, Logger}

import javax.inject.{Inject, Singleton}

import scala.concurrent.duration.Duration

import net.spy.memcached.MemcachedClient

import scala.concurrent.{ ExecutionContext, Future, Promise }
import scala.reflect.ClassTag

@Singleton
class MemcachedCacheApi @Inject() (val namespace: String, val client: MemcachedClient, config: Config, environment: Environment)(implicit context: ExecutionContext) extends AsyncCacheApi {
  lazy val logger = Logger("memcached.plugin")
  lazy val tc = new CustomSerializing(environment.classLoader).asInstanceOf[Transcoder[Any]]
  lazy val hashkeys: String = config.getString("memcached.hashkeys")
  lazy val throwExceptionFromGetOnError: Boolean = config.getBoolean("memcached.throwExceptionFromGetOnError")

  def get[T: ClassTag](key: String): Future[Option[T]] = {
    if (key.isEmpty) {
      Future.successful(None)
    } else {
      val ct = implicitly[ClassTag[T]]

      logger.debug("Getting the cache for key " + namespace + key)
      val p = Promise[Option[T]]() // create incomplete promise/future
      client.asyncGet(namespace + hash(key), tc).addListener(new GetCompletionListener() {
        def onComplete(result: GetFuture[_]): Unit = {
          try {
            result.getStatus().getStatusCode() match {
              case StatusCode.SUCCESS => {
                val any = result.get
                logger.debug("any is " + any.getClass)
                p.success(Option(
                  any match {
                    case x if ct.runtimeClass.isInstance(x) => x.asInstanceOf[T]
                    case x if ct == ClassTag.Nothing => x.asInstanceOf[T]
                    case x => x.asInstanceOf[T]
                  }
                ))
              }
              case StatusCode.ERR_NOT_FOUND => {
                logger.debug("Cache miss for " + namespace + key)
                p.success(None)
              }
              case _ => {
                fail(result, None)
              }
            }
          } catch {
            case e: Throwable => fail(result, Some(e))
          }
        }

        def fail(result: GetFuture[_], exception: Option[Throwable]): Unit = {
          val msg = "An error has occured while getting the value from memcached. ct=" + ct + ". key=" + key + ". " +
            "spymemcached code: " + result.getStatus().getStatusCode() + " memcached code:" + result.getStatus().getMessage()
          if (throwExceptionFromGetOnError) {
            p.failure(exception.fold(new RuntimeException(msg))(new RuntimeException(msg, _)))
          } else {
            logger.error(msg, exception.orNull)
            p.success(None)
          }
        }
      })
      p.future
    }
  }

  def getOrElseUpdate[A: ClassTag](key: String, expiration: Duration)(orElse: => Future[A]): Future[A] = {
    get[A](key).flatMap {
      case Some(value) => Future.successful(value)
      case None => orElse.flatMap(value => set(key, value, expiration).map(_ => value))
    }
  }

  def set(key: String, value: Any, expiration: Duration = Duration.Inf): Future[Done] = {
    if (!key.isEmpty) {
      val p = Promise[Done]() // create incomplete promise/future
      val exp = if (expiration.isFinite) expiration.toSeconds.toInt else 0
      client.set(namespace + hash(key), exp, value, tc).addListener(new OperationCompletionListener() {
        def onComplete(result: OperationFuture[_]): Unit = {
          result.getStatus().getStatusCode() match {
            case StatusCode.SUCCESS => {
              p.success(Done)
            }
            case _ => {
              val msg = "An error has occured while setting the value in memcached. key=" + key + ". value=" + value + ". " +
                "spymemcached code: " + result.getStatus().getStatusCode() + " memcached code:" + result.getStatus().getMessage()
              if (throwExceptionFromGetOnError) {
                p.failure(new RuntimeException(msg))
              } else {
                logger.error(msg)
                p.success(Done)
              }
            }
          }
        }
      })
      p.future
    } else {
      Future.successful(Done)
    }
  }

  def remove(key: String): Future[Done] = {
    if (!key.isEmpty) {
      val p = Promise[Done]() // create incomplete promise/future
      client.delete(namespace + hash(key)).addListener(new OperationCompletionListener() {
        def onComplete(result: OperationFuture[_]): Unit = {
          result.getStatus().getStatusCode() match {
            case StatusCode.SUCCESS => {
              p.success(Done)
            }
            case StatusCode.ERR_NOT_FOUND => {
              logger.debug("Cache miss when removing " + namespace + key)
              p.success(Done)
            }
            case _ => {
              val msg = "An error has occured while removing the value in memcached. key=" + key + ". " +
                "spymemcached code: " + result.getStatus().getStatusCode() + " memcached code:" + result.getStatus().getMessage()
              if (throwExceptionFromGetOnError) {
                p.failure(new RuntimeException(msg))
              } else {
                logger.error(msg)
                p.success(Done)
              }
            }
          }
        }
      })
      p.future
    } else {
      Future.successful(Done)
    }
  }

  def removeAll(): Future[Done] = {
    val p = Promise[Done]() // create incomplete promise/future
    client.flush().addListener(new OperationCompletionListener() {
      def onComplete(result: OperationFuture[_]): Unit = {
        result.getStatus().getStatusCode() match {
          case StatusCode.SUCCESS => {
            p.success(Done)
          }
          case _ => {
            val msg = "An error has occured while removing all values from memcached. " +
              "spymemcached code: " + result.getStatus().getStatusCode() + " memcached code:" + result.getStatus().getMessage()
            if (throwExceptionFromGetOnError) {
              p.failure(new RuntimeException(msg))
            } else {
              logger.error(msg)
              p.success(Done)
            }
          }
        }
      }
    })
    p.future
  }

  // you may override hash implementation to use more sophisticated hashes, like xxHash for higher performance
  protected def hash(key: String): String = if(hashkeys == "off") key
    else java.security.MessageDigest.getInstance(hashkeys).digest(key.getBytes).map("%02x".format(_)).mkString
}

object MemcachedCacheApi {
  object ValFromJavaObject {
    def unapply(x: AnyRef): Option[AnyVal] = x match {
      case x: java.lang.Byte => Some(x.byteValue())
      case x: java.lang.Short => Some(x.shortValue())
      case x: java.lang.Integer => Some(x.intValue())
      case x: java.lang.Long => Some(x.longValue())
      case x: java.lang.Float => Some(x.floatValue())
      case x: java.lang.Double => Some(x.doubleValue())
      case x: java.lang.Character => Some(x.charValue())
      case x: java.lang.Boolean => Some(x.booleanValue())
      case _ => None
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy