diff --git a/composer.json b/composer.json index 8e50f99..6ab4ae2 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ }, "require-dev": { "phpunit/phpunit": "^4.8", - "squizlabs/php_codesniffer": "^2.0@dev" + "squizlabs/php_codesniffer": "^2.0@dev", + "mockery/mockery": "^0.9" }, "license": "GPLv3", "authors": [ diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 434cb9e..5987fb6 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -8,5 +8,8 @@ test/UuidGenerator + + test/KeyCache + diff --git a/src/KeyCache/IUuidCache.php b/src/KeyCache/IUuidCache.php new file mode 100644 index 0000000..2cfb01b --- /dev/null +++ b/src/KeyCache/IUuidCache.php @@ -0,0 +1,81 @@ + value service to store UUID -> Fedora Paths + * not yet indexed into the triplestore (ie. in a transaction) + * @author Jared Whiklo + * @since 2016-04-12 + */ +interface IUuidCache +{ + + /** + * Sets the value for the provided key. + * + * @var string $txID + * The transaction ID. + * @var string $uuid + * The UUID. + * @var string $path + * The Fedora path. + * @var int $expire + * The number of seconds from now to expire. Default to an hour. + * @return bool + * Was key set successful. + */ + public function set($txID, $uuid, $path, $expire = 3600); + + /** + * Gets the Fedora path for the provided UUID. + * + * @var string $txID + * The transaction ID. + * @var string $uuid + * The UUID + * @return string + * The UUID corresponding to the path in the transaction or NULL + */ + public function getByUuid($txID, $uuid); + + /** + * Gets the UUID for the provided path. + * + * @var string $txID + * The transaction ID. + * @var string $path + * The key. + * @return string + * The UUID corresponding to the path in the transaction or NULL + */ + public function getByPath($txID, $path); + + /** + * Delete any pairs related to transaction ID. + * + * @var string $txID + * The transaction ID. + * @return void + */ + public function delete($txID); + + /** + * Set/reset the transaction to expire in $seconds seconds. + * + * @var string $txID + * The transaction ID. + * @var int $seconds + * The number of seconds from now to expire. + * @return bool + * Whether the expiry was set or not. + */ + public function extend($txID, $seconds); +} diff --git a/src/KeyCache/RedisKeyCache.php b/src/KeyCache/RedisKeyCache.php new file mode 100644 index 0000000..2093968 --- /dev/null +++ b/src/KeyCache/RedisKeyCache.php @@ -0,0 +1,161 @@ + + * @since 2016-04-12 + */ +class RedisKeyCache implements IUuidCache +{ + + /** + * @var Redis $redis + * The Redis object. + */ + protected $redis; + /** + * @var bool $connected + * Are we connected to the Redis server yet. + */ + private $connected = false; + /** + * @var string $host + * The hostname of the redis server. + */ + private $host; + /** + * @var int $port + * The port of the redis server. + */ + private $port; + + /** + * @param \Redis $instance + * An instance of the Redis client + */ + public function __construct(\Redis $instance, $host, $port) + { + $this->redis = $instance; + $this->redis->host = $host; + $this->redis->port = $port; + } + + /** + * + */ + public function __destruct() + { + $this->redis->close(); + } + + /** + * Create using a hostname and port. + * + * @param $host + * The hostname of the redis server + * @param $port + * The port of the redis server + * @return RedisKeyCache + */ + public static function create($host = "localhost", $port = 6379) + { + $redis = new \Redis(); + $redis->connect($host, $port); + return new RedisKeyCache($redis, $host, $port); + } + + /** + * Check if we are connected to the Redis server. + * + * @return bool Whether we are connected. + */ + private function isConnected() + { + try { + $result = $this->redis->ping(); + $connected = ($result == "+PONG"); + } catch (RedisException $e) { + error_log("RedisKeyCache: Error communicating with Redis server - " . $e->getMessage()); + # Try connecting once more. + $this->redis->connect($this->host, $this->port); + $result = $this->redis->ping(); + $connected = ($result == "+PONG"); + } + return $connected; + } + + /** + * {@inheritdoc} + */ + public function set($txID, $uuid, $path, $expire = 3600) + { + if ($this->isConnected()) { + $result = $this->redis->hSetNx($txID, $uuid, $path); + if ($result) { + $this->redis->expire($txID, $expire); + } + return $result; + } + /** + * TODO: return exceptions?? + */ + return false; + } + + /** + * {@inheritdoc} + */ + public function getByUuid($txId, $uuid) + { + if ($this->isConnected()) { + return $this->redis->hget($txId, $uuid); + } + } + + /** + * {@inheritdoc} + */ + public function getByPath($txId, $path) + { + if ($this->isConnected()) { + $hashes = $this->redis->hgetall($txId); + if ($hashes !== false) { + $flipped = array_flip($hashes); + if (array_key_exists($path, $flipped)) { + return $flipped[$path]; + } + } + } + return false; + } + + /** + * {@inheritdoc} + */ + public function extend($txId, $expire = 3600) + { + if ($this->isConnected()) { + return $this->redis->expire($txId, $expire); + } + } + + /** + * {@inheritdoc} + */ + public function delete($txId) + { + if ($this->isConnected()) { + return $this->redis->del($txId); + } + } +} diff --git a/test/KeyCache/UuidCacheTest.php b/test/KeyCache/UuidCacheTest.php new file mode 100644 index 0000000..cd22155 --- /dev/null +++ b/test/KeyCache/UuidCacheTest.php @@ -0,0 +1,110 @@ +redis = \Mockery::mock('\Redis'); + $this->redis->shouldReceive('expire')->andReturn(1); + $this->redis->shouldReceive('ping')->andReturn("+PONG"); + $this->redis->shouldReceive('close')->andReturn(null); + + $this->uuid_gen = new UuidGenerator(); + } + + /** + * @covers RedisKeyCache::set + * @group UnitTest + */ + public function testAddUuidPair() + { + // Need both to account for OS X and Linux differences. + $this->redis->shouldReceive('hSetNx')->andReturn(1); + $this->redis->shouldReceive('hsetnx')->andReturn(1); + $redis_cache = new RedisKeyCache($this->redis, 'localhost', 6379); + + $transId = "tx:" . $this->uuid_gen->generateV4(); + $uuid = $this->uuid_gen->generateV4(); + $path = "http://localhost:8080/fcrepo/rest/object1"; + + $this->assertEquals(1, $redis_cache->set($transId, $uuid, $path), "Error setting uuid->path hash"); + } + + /** + * @covers RedisKeyCache::getByUuid + * @group UnitTest + */ + public function testGetByUuid() + { + $txID = $transId = "tx:" . $this->uuid_gen->generateV4(); + $uuid = $this->uuid_gen->generateV4(); + $path = "http://localhost:8080/fcrepo/rest/object1"; + + // Need both to account for OS X and Linux differences. + $this->redis->shouldReceive('hGet')->with($txID, $uuid)->andReturn($path); + $this->redis->shouldReceive('hget')->with($txID, $uuid)->andReturn($path); + + $redis_cache = new RedisKeyCache($this->redis, 'localhost', 6379); + + $this->assertEquals($path, $redis_cache->getByUuid($txID, $uuid), "Error getting by Uuid"); + } + + /** + * @covers RedisKeyCache::getByPath + * @group UnitTest + */ + public function testGetByPath() + { + $txID = $transId = "tx:" . $this->uuid_gen->generateV4(); + + $uuid1 = $this->uuid_gen->generateV4(); + $path1 = "http://localhost:8080/fcrepo/rest/object1"; + + $uuid2 = $this->uuid_gen->generateV4(); + $path2 = "http://localhost:8080/fcrepo/rest/object2"; + + $hashes = array( + $uuid1 => $path1, + $uuid2 => $path2, + ); + + // Need both to account for OS X and Linux differences. + $this->redis->shouldReceive('hGetAll')->with($txID)->andReturn($hashes); + $this->redis->shouldReceive('hgetall')->with($txID)->andReturn($hashes); + + $redis_cache = new RedisKeyCache($this->redis, 'localhost', 6379); + + $this->assertEquals($uuid2, $redis_cache->getByPath($txID, $path2), "Error getting by Path"); + } + + /** + * @covers RedisKeyCache::delete + * @group UnitTest + */ + public function testDelete() + { + $txID = $transId = "tx:" . $this->uuid_gen->generateV4(); + + $this->redis->shouldReceive('del')->with($txID)->andReturn(1); + + $redis_cache = new RedisKeyCache($this->redis, 'localhost', 6379); + + $this->assertEquals(1, $redis_cache->delete($txID), "Error deleting transaction ID."); + } + + public function tearDown() + { + \Mockery::close(); + } +} diff --git a/test/UuidGenerator/UuidTest.php b/test/UuidGenerator/UuidTest.php index 063e21e..0227aae 100644 --- a/test/UuidGenerator/UuidTest.php +++ b/test/UuidGenerator/UuidTest.php @@ -1,6 +1,6 @@