001/* 002 * (C) Copyright 2014-2018 Nuxeo (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.lang3.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 String prefix; 054 055 protected String scriptSetSha; 056 057 protected String scriptRemoveSha; 058 059 /** 060 * Creates a lock manager for the given repository. 061 * <p> 062 * {@link #closeLockManager()} must be called when done with the lock manager. 063 */ 064 public RedisLockManager(String repositoryName) { 065 this.repositoryName = repositoryName; 066 RedisAdmin redisAdmin = Framework.getService(RedisAdmin.class); 067 redisNamespace = redisAdmin.namespace("lock", repositoryName); 068 try { 069 scriptSetSha = redisAdmin.load("org.nuxeo.ecm.core.redis", "set-lock"); 070 scriptRemoveSha = redisAdmin.load("org.nuxeo.ecm.core.redis", "remove-lock"); 071 } catch (IOException cause) { 072 throw new NuxeoException("Cannot load lock scripts in redis", cause); 073 } 074 } 075 076 protected String stringFromLock(Lock lock) { 077 if (lock == null) { 078 throw new NullPointerException("null lock"); 079 } 080 return lock.getOwner() + ":" + lock.getCreated().getTimeInMillis(); 081 } 082 083 protected Lock lockFromString(String lockString) { 084 if (lockString == null) { 085 return null; 086 } 087 String[] split = lockString.split(":"); 088 if (split.length != 2 || !StringUtils.isNumeric(split[1])) { 089 throw new IllegalArgumentException( 090 "Invalid Redis lock : " + lockString + ", should be " + redisNamespace + "<id>"); 091 } 092 Calendar created = Calendar.getInstance(); 093 created.setTimeInMillis(Long.parseLong(split[1])); 094 return new Lock(split[0], created); 095 } 096 097 @Override 098 public Lock getLock(final String id) { 099 RedisExecutor redisExecutor = Framework.getService(RedisExecutor.class); 100 return redisExecutor.execute(new RedisCallable<Lock>() { 101 @Override 102 public Lock call(Jedis jedis) { 103 String lockString = jedis.get(redisNamespace + id); 104 return lockFromString(lockString); 105 } 106 }); 107 } 108 109 @Override 110 public Lock setLock(final String id, final Lock lock) { 111 RedisExecutor redisExecutor = Framework.getService(RedisExecutor.class); 112 List<String> keys = Collections.singletonList(redisNamespace + id); 113 List<String> args = Collections.singletonList(stringFromLock(lock)); 114 String lockString = (String) redisExecutor.evalsha(scriptSetSha, keys, args); 115 return lockFromString(lockString); // existing lock 116 } 117 118 @Override 119 public Lock removeLock(final String id, final String owner) { 120 RedisExecutor redisExecutor = Framework.getService(RedisExecutor.class); 121 List<String> keys = Collections.singletonList(redisNamespace + id); 122 List<String> args = Collections.singletonList(owner == null ? "" : owner); 123 String lockString = (String) redisExecutor.evalsha(scriptRemoveSha, keys, args); 124 Lock lock = lockFromString(lockString); 125 if (lock != null && owner != null && !owner.equals(lock.getOwner())) { 126 lock = new Lock(lock, true); // failed removal 127 } 128 return lock; 129 } 130 131 @Override 132 public void closeLockManager() { 133 } 134 135 @Override 136 public void clearLockManagerCaches() { 137 } 138 139 @Override 140 public String toString() { 141 return getClass().getSimpleName() + '(' + repositoryName + ')'; 142 } 143 144}