001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Florent Guillaume 016 */ 017package org.nuxeo.ecm.core.redis.contribs; 018 019import java.io.IOException; 020import java.util.Arrays; 021import java.util.Calendar; 022 023import org.apache.commons.lang.StringUtils; 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.nuxeo.ecm.core.api.Lock; 027import org.nuxeo.ecm.core.api.NuxeoException; 028import org.nuxeo.ecm.core.model.LockManager; 029import org.nuxeo.ecm.core.redis.RedisAdmin; 030import org.nuxeo.ecm.core.redis.RedisCallable; 031import org.nuxeo.ecm.core.redis.RedisExecutor; 032import org.nuxeo.runtime.api.Framework; 033 034import redis.clients.jedis.Jedis; 035 036/** 037 * Redis-based lock manager. 038 * 039 * @since 6.0 040 */ 041public class RedisLockManager implements LockManager { 042 043 @SuppressWarnings("unused") 044 private static final Log log = LogFactory.getLog(RedisLockManager.class); 045 046 protected final String redisNamespace; 047 048 protected final String repositoryName; 049 050 protected RedisExecutor redisExecutor; 051 052 protected String prefix; 053 054 protected String scriptSetSha; 055 056 protected String scriptRemoveSha; 057 058 protected RedisAdmin redisAdmin; 059 060 /** 061 * Creates a lock manager for the given repository. 062 * <p> 063 * {@link #close} must be called when done with the lock manager. 064 */ 065 public RedisLockManager(String repositoryName) { 066 this.repositoryName = repositoryName; 067 redisExecutor = Framework.getService(RedisExecutor.class); 068 redisAdmin = Framework.getService(RedisAdmin.class); 069 redisNamespace = redisAdmin.namespace("lock", repositoryName); 070 loadScripts(); 071 } 072 073 public void loadScripts() { 074 try { 075 scriptSetSha = redisAdmin.load("org.nuxeo.ecm.core.redis", "set-lock"); 076 scriptRemoveSha = redisAdmin.load("org.nuxeo.ecm.core.redis", "remove-lock"); 077 } catch (IOException cause) { 078 throw new NuxeoException("Cannot load lock scripts in redis", cause); 079 } 080 } 081 082 protected String stringFromLock(Lock lock) { 083 if (lock == null) { 084 throw new NullPointerException("null lock"); 085 } 086 return lock.getOwner() + ":" + lock.getCreated().getTimeInMillis(); 087 } 088 089 protected Lock lockFromString(String lockString) { 090 if (lockString == null) { 091 return null; 092 } 093 String[] split = lockString.split(":"); 094 if (split.length != 2 || !StringUtils.isNumeric(split[1])) { 095 throw new IllegalArgumentException("Invalid Redis lock : " + lockString + ", should be " + redisNamespace 096 + "<id>"); 097 } 098 Calendar created = Calendar.getInstance(); 099 created.setTimeInMillis(Long.parseLong(split[1])); 100 return new Lock(split[0], created); 101 } 102 103 @Override 104 public Lock getLock(final String id) { 105 return redisExecutor.execute(new RedisCallable<Lock>() { 106 @Override 107 public Lock call(Jedis jedis) { 108 String lockString = jedis.get(redisNamespace + id); 109 return lockFromString(lockString); 110 } 111 }); 112 } 113 114 @Override 115 public Lock setLock(final String id, final Lock lock) { 116 return redisExecutor.execute(new RedisCallable<Lock>() { 117 @Override 118 public Lock call(Jedis jedis) { 119 String lockString = (String) jedis.evalsha(scriptSetSha, Arrays.asList(redisNamespace + id), 120 Arrays.asList(stringFromLock(lock))); 121 return lockFromString(lockString); // existing lock 122 } 123 }); 124 } 125 126 @Override 127 public Lock removeLock(final String id, final String owner) { 128 return redisExecutor.execute(new RedisCallable<Lock>() { 129 @Override 130 public Lock call(Jedis jedis) { 131 String lockString = (String) jedis.evalsha(scriptRemoveSha, Arrays.asList(redisNamespace + id), 132 Arrays.asList(owner == null ? "" : owner)); 133 Lock lock = lockFromString(lockString); 134 if (lock != null && owner != null && !owner.equals(lock.getOwner())) { 135 lock = new Lock(lock, true); // failed removal 136 } 137 return lock; 138 } 139 }); 140 } 141 142 @Override 143 public void closeLockManager() { 144 } 145 146 @Override 147 public void clearLockManagerCaches() { 148 } 149 150 @Override 151 public String toString() { 152 return getClass().getSimpleName() + '(' + repositoryName + ')'; 153 } 154 155}