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

com.twitter.storehaus.redis.RedisSetStore.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013 Twitter Inc.
 *
 * 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.twitter.storehaus.redis

import com.twitter.util.{ Duration, Future, Time }
import com.twitter.finagle.redis.Client
import com.twitter.storehaus.Store
import org.jboss.netty.buffer.ChannelBuffer

/**
 *
 *  @author Doug Tangren
 */

object RedisSetStore {
  def apply(client: Client, ttl: Option[Duration] = RedisStore.Default.TTL) =
    new RedisSetStore(client, ttl)
  def members(client: Client, ttl: Option[Duration] = RedisStore.Default.TTL) =
    new RedisSetMembershipStore(RedisSetStore(client, ttl))
}

/**
 * A Store for sets of values backed by a Redis set.
 */
class RedisSetStore(val client: Client, ttl: Option[Duration])
  extends Store[ChannelBuffer, Set[ChannelBuffer]] {

  override def get(k: ChannelBuffer): Future[Option[Set[ChannelBuffer]]] =
    client.sMembers(k).map {
      case e if(e.isEmpty) => None
      case s => Some(s)
    }

  override def put(kv: (ChannelBuffer, Option[Set[ChannelBuffer]])): Future[Unit] =
    kv match {
      case (k, Some(v)) =>
        client.del(Seq(k)) // put, not merge, semantics
        ttl.map(exp => client.expire(k, exp.inSeconds))
        set(k, v.toList)
      case (k, None) =>
        client.del(Seq(k)).unit
    }

  /** Provides a view of this store for set membership */
  def members: RedisSetMembershipStore =
    new RedisSetMembershipStore(this)

  protected [redis] def set(k: ChannelBuffer, v: List[ChannelBuffer]) = {
    ttl.map(exp => client.expire(k, exp.inSeconds))
    client.sAdd(k, v).unit
  }

  protected [redis] def delete(k: ChannelBuffer, v: List[ChannelBuffer]) =
    client.sRem(k, v).unit

  override def close(t: Time) = client.quit.foreach { _ => client.release }
}

/**
 * A Store for sets of values backed by a Redis set.
 * This Store wraps a RedisSetStore providing
 * a view into set membership on a per-member basis. Set members are encoded
 * in the Store's keys as (setkey, setmember). The Store's
 * value type, Unit, simply denotes the presence of the member
 * within the Store.
 */
class RedisSetMembershipStore(store: RedisSetStore)
  extends Store[(ChannelBuffer, ChannelBuffer), Unit] {

  override def get(k: (ChannelBuffer, ChannelBuffer)): Future[Option[Unit]] =
    store.client.sIsMember(k._1, k._2).map {
      case java.lang.Boolean.TRUE => Some(())
      case _ => None
    }

  override def put(kv: ((ChannelBuffer, ChannelBuffer), Option[Unit])): Future[Unit] =
    kv match {
      case (key, Some(_)) => store.set(key._1, List(key._2))
      case (key, None) => store.delete(key._1, List(key._2))
    }

  override def multiPut[K1 <: (ChannelBuffer, ChannelBuffer)](kv: Map[K1, Option[Unit]]): Map[K1, Future[Unit]]  = {
    // we are exploiting redis's built-in support for bulk updates and removals
    // by partioning deletions and updates into 2 maps indexed by the first
    // component of the composite key, the key of the set
    def emptyMap = Map.empty[ChannelBuffer, List[K1]].withDefaultValue(Nil)
    val (del, persist) = ((emptyMap, emptyMap) /: kv) {
      case ((deleting, storing), (key, Some(_))) =>
        (deleting, storing.updated(key._1, key :: storing(key._1)))
      case ((deleting, storing), (key, None)) =>
        (deleting.updated(key._1, key :: deleting(key._1)), storing)
    }
    del.map {
      case (k, members) =>
        val value = store.delete(k, members.map(_._2))
        members.map(_ -> value)
    }.flatten ++ persist.map {
      case (k, members) =>
        val value = store.set(k, members.map(_._2))
        members.map(_ -> value)
    }.flatten toMap
  }

  /** Calling close on this store will also close it's underlying
   *  RedisSetStore
   */
  override def close(t: Time) = store.close(t)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy