001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Florent Guillaume 018 */ 019package org.nuxeo.ecm.core.redis.contribs; 020 021import java.io.IOException; 022import java.util.Calendar; 023import java.util.Collections; 024import java.util.List; 025 026import org.apache.commons.lang.StringUtils; 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029import org.nuxeo.ecm.core.api.Lock; 030import org.nuxeo.ecm.core.api.NuxeoException; 031import org.nuxeo.ecm.core.model.LockManager; 032import org.nuxeo.ecm.core.redis.RedisAdmin; 033import org.nuxeo.ecm.core.redis.RedisCallable; 034import org.nuxeo.ecm.core.redis.RedisExecutor; 035import org.nuxeo.runtime.api.Framework; 036 037import redis.clients.jedis.Jedis; 038 039/** 040 * Redis-based lock manager. 041 * 042 * @since 6.0 043 */ 044public class RedisLockManager implements LockManager { 045 046 @SuppressWarnings("unused") 047 private static final Log log = LogFactory.getLog(RedisLockManager.class); 048 049 protected final String redisNamespace; 050 051 protected final String repositoryName; 052 053 protected RedisExecutor redisExecutor; 054 055 protected String prefix; 056 057 protected String scriptSetSha; 058 059 protected String scriptRemoveSha; 060 061 protected RedisAdmin redisAdmin; 062 063 /** 064 * Creates a lock manager for the given repository. 065 * <p> 066 * {@link #close} must be called when done with the lock manager. 067 */ 068 public RedisLockManager(String repositoryName) { 069 this.repositoryName = repositoryName; 070 redisExecutor = Framework.getService(RedisExecutor.class); 071 redisAdmin = Framework.getService(RedisAdmin.class); 072 redisNamespace = redisAdmin.namespace("lock", repositoryName); 073 loadScripts(); 074 } 075 076 public void loadScripts() { 077 try { 078 scriptSetSha = redisAdmin.load("org.nuxeo.ecm.core.redis", "set-lock"); 079 scriptRemoveSha = redisAdmin.load("org.nuxeo.ecm.core.redis", "remove-lock"); 080 } catch (IOException cause) { 081 throw new NuxeoException("Cannot load lock scripts in redis", cause); 082 } 083 } 084 085 protected String stringFromLock(Lock lock) { 086 if (lock == null) { 087 throw new NullPointerException("null lock"); 088 } 089 return lock.getOwner() + ":" + lock.getCreated().getTimeInMillis(); 090 } 091 092 protected Lock lockFromString(String lockString) { 093 if (lockString == null) { 094 return null; 095 } 096 String[] split = lockString.split(":"); 097 if (split.length != 2 || !StringUtils.isNumeric(split[1])) { 098 throw new IllegalArgumentException("Invalid Redis lock : " + lockString + ", should be " + redisNamespace 099 + "<id>"); 100 } 101 Calendar created = Calendar.getInstance(); 102 created.setTimeInMillis(Long.parseLong(split[1])); 103 return new Lock(split[0], created); 104 } 105 106 @Override 107 public Lock getLock(final String id) { 108 return redisExecutor.execute(new RedisCallable<Lock>() { 109 @Override 110 public Lock call(Jedis jedis) { 111 String lockString = jedis.get(redisNamespace + id); 112 return lockFromString(lockString); 113 } 114 }); 115 } 116 117 @Override 118 public Lock setLock(final String id, final Lock lock) { 119 List<String> keys = Collections.singletonList(redisNamespace + id); 120 List<String> args = Collections.singletonList(stringFromLock(lock)); 121 String lockString = (String) redisExecutor.evalsha(scriptSetSha, keys, args); 122 return lockFromString(lockString); // existing lock 123 } 124 125 @Override 126 public Lock removeLock(final String id, final String owner) { 127 List<String> keys = Collections.singletonList(redisNamespace + id); 128 List<String> args = Collections.singletonList(owner == null ? "" : owner); 129 String lockString = (String) redisExecutor.evalsha(scriptRemoveSha, keys, args); 130 Lock lock = lockFromString(lockString); 131 if (lock != null && owner != null && !owner.equals(lock.getOwner())) { 132 lock = new Lock(lock, true); // failed removal 133 } 134 return lock; 135 } 136 137 @Override 138 public void closeLockManager() { 139 } 140 141 @Override 142 public void clearLockManagerCaches() { 143 } 144 145 @Override 146 public String toString() { 147 return getClass().getSimpleName() + '(' + repositoryName + ')'; 148 } 149 150}