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.Arrays; 023import java.util.Calendar; 024 025import org.apache.commons.lang.StringUtils; 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028import org.nuxeo.ecm.core.api.Lock; 029import org.nuxeo.ecm.core.api.NuxeoException; 030import org.nuxeo.ecm.core.model.LockManager; 031import org.nuxeo.ecm.core.redis.RedisAdmin; 032import org.nuxeo.ecm.core.redis.RedisCallable; 033import org.nuxeo.ecm.core.redis.RedisExecutor; 034import org.nuxeo.runtime.api.Framework; 035 036import redis.clients.jedis.Jedis; 037 038/** 039 * Redis-based lock manager. 040 * 041 * @since 6.0 042 */ 043public class RedisLockManager implements LockManager { 044 045 @SuppressWarnings("unused") 046 private static final Log log = LogFactory.getLog(RedisLockManager.class); 047 048 protected final String redisNamespace; 049 050 protected final String repositoryName; 051 052 protected RedisExecutor redisExecutor; 053 054 protected String prefix; 055 056 protected String scriptSetSha; 057 058 protected String scriptRemoveSha; 059 060 protected RedisAdmin redisAdmin; 061 062 /** 063 * Creates a lock manager for the given repository. 064 * <p> 065 * {@link #close} must be called when done with the lock manager. 066 */ 067 public RedisLockManager(String repositoryName) { 068 this.repositoryName = repositoryName; 069 redisExecutor = Framework.getService(RedisExecutor.class); 070 redisAdmin = Framework.getService(RedisAdmin.class); 071 redisNamespace = redisAdmin.namespace("lock", repositoryName); 072 loadScripts(); 073 } 074 075 public void loadScripts() { 076 try { 077 scriptSetSha = redisAdmin.load("org.nuxeo.ecm.core.redis", "set-lock"); 078 scriptRemoveSha = redisAdmin.load("org.nuxeo.ecm.core.redis", "remove-lock"); 079 } catch (IOException cause) { 080 throw new NuxeoException("Cannot load lock scripts in redis", cause); 081 } 082 } 083 084 protected String stringFromLock(Lock lock) { 085 if (lock == null) { 086 throw new NullPointerException("null lock"); 087 } 088 return lock.getOwner() + ":" + lock.getCreated().getTimeInMillis(); 089 } 090 091 protected Lock lockFromString(String lockString) { 092 if (lockString == null) { 093 return null; 094 } 095 String[] split = lockString.split(":"); 096 if (split.length != 2 || !StringUtils.isNumeric(split[1])) { 097 throw new IllegalArgumentException("Invalid Redis lock : " + lockString + ", should be " + redisNamespace 098 + "<id>"); 099 } 100 Calendar created = Calendar.getInstance(); 101 created.setTimeInMillis(Long.parseLong(split[1])); 102 return new Lock(split[0], created); 103 } 104 105 @Override 106 public Lock getLock(final String id) { 107 return redisExecutor.execute(new RedisCallable<Lock>() { 108 @Override 109 public Lock call(Jedis jedis) { 110 String lockString = jedis.get(redisNamespace + id); 111 return lockFromString(lockString); 112 } 113 }); 114 } 115 116 @Override 117 public Lock setLock(final String id, final Lock lock) { 118 return redisExecutor.execute(new RedisCallable<Lock>() { 119 @Override 120 public Lock call(Jedis jedis) { 121 String lockString = (String) jedis.evalsha(scriptSetSha, Arrays.asList(redisNamespace + id), 122 Arrays.asList(stringFromLock(lock))); 123 return lockFromString(lockString); // existing lock 124 } 125 }); 126 } 127 128 @Override 129 public Lock removeLock(final String id, final String owner) { 130 return redisExecutor.execute(new RedisCallable<Lock>() { 131 @Override 132 public Lock call(Jedis jedis) { 133 String lockString = (String) jedis.evalsha(scriptRemoveSha, Arrays.asList(redisNamespace + id), 134 Arrays.asList(owner == null ? "" : owner)); 135 Lock lock = lockFromString(lockString); 136 if (lock != null && owner != null && !owner.equals(lock.getOwner())) { 137 lock = new Lock(lock, true); // failed removal 138 } 139 return lock; 140 } 141 }); 142 } 143 144 @Override 145 public void closeLockManager() { 146 } 147 148 @Override 149 public void clearLockManagerCaches() { 150 } 151 152 @Override 153 public String toString() { 154 return getClass().getSimpleName() + '(' + repositoryName + ')'; 155 } 156 157}