001/* 002 * (C) Copyright 2013-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; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.net.URL; 024import java.util.Arrays; 025import java.util.List; 026 027import org.apache.commons.io.IOUtils; 028import org.apache.commons.lang.StringUtils; 029import org.apache.commons.lang.text.StrBuilder; 030import org.nuxeo.ecm.core.api.NuxeoException; 031import org.nuxeo.runtime.RuntimeServiceEvent; 032import org.nuxeo.runtime.RuntimeServiceListener; 033import org.nuxeo.runtime.api.Framework; 034import org.nuxeo.runtime.model.ComponentContext; 035import org.nuxeo.runtime.model.ComponentInstance; 036import org.nuxeo.runtime.model.DefaultComponent; 037import org.nuxeo.runtime.model.SimpleContributionRegistry; 038import org.osgi.framework.Bundle; 039 040import redis.clients.jedis.Jedis; 041 042/** 043 * Implementation of the Redis Service holding the configured Jedis pool. 044 * 045 * @since 5.8 046 */ 047public class RedisComponent extends DefaultComponent implements RedisAdmin { 048 049 private static final String DEFAULT_PREFIX = "nuxeo:"; 050 051 protected volatile RedisExecutor executor = RedisExecutor.NOOP; 052 053 protected RedisPoolDescriptorRegistry registry = new RedisPoolDescriptorRegistry(); 054 055 public static class RedisPoolDescriptorRegistry extends SimpleContributionRegistry<RedisPoolDescriptor> { 056 057 protected RedisPoolDescriptor config; 058 059 @Override 060 public String getContributionId(RedisPoolDescriptor contrib) { 061 return "main"; 062 } 063 064 @Override 065 public void contributionUpdated(String id, RedisPoolDescriptor contrib, RedisPoolDescriptor newOrigContrib) { 066 config = contrib; 067 } 068 069 @Override 070 public void contributionRemoved(String id, RedisPoolDescriptor origContrib) { 071 config = null; 072 } 073 074 public RedisPoolDescriptor getConfig() { 075 return config; 076 } 077 078 public void clear() { 079 config = null; 080 } 081 }; 082 083 protected String delsha; 084 085 @Override 086 public void activate(ComponentContext context) { 087 super.activate(context); 088 registry.clear(); 089 } 090 091 @Override 092 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 093 if (contribution instanceof RedisPoolDescriptor) { 094 registerRedisPoolDescriptor((RedisPoolDescriptor) contribution); 095 } else { 096 throw new NuxeoException("Unknown contribution class: " + contribution); 097 } 098 } 099 100 @Override 101 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 102 if (contribution instanceof RedisPoolDescriptor) { 103 unregisterRedisPoolDescriptor((RedisPoolDescriptor) contribution); 104 } 105 } 106 107 public void registerRedisPoolDescriptor(RedisPoolDescriptor contrib) { 108 registry.addContribution(contrib); 109 } 110 111 public void unregisterRedisPoolDescriptor(RedisPoolDescriptor contrib) { 112 registry.removeContribution(contrib); 113 } 114 115 public RedisPoolDescriptor getConfig() { 116 return registry.getConfig(); 117 } 118 119 @Override 120 public void applicationStarted(ComponentContext context) { 121 RedisPoolDescriptor config = getConfig(); 122 if (config == null || config.disabled) { 123 return; 124 } 125 Framework.addListener(new RuntimeServiceListener() { 126 127 @Override 128 public void handleEvent(RuntimeServiceEvent event) { 129 if (event.id != RuntimeServiceEvent.RUNTIME_ABOUT_TO_STOP) { 130 return; 131 } 132 Framework.removeListener(this); 133 try { 134 executor.getPool().destroy(); 135 } finally { 136 executor = null; 137 } 138 } 139 }); 140 handleNewExecutor(config.newExecutor()); 141 } 142 143 @Override 144 public int getApplicationStartedOrder() { 145 return ((DefaultComponent) Framework.getRuntime().getComponentInstance("org.nuxeo.ecm.core.work.service").getInstance()).getApplicationStartedOrder() - 1; 146 } 147 148 public void handleNewExecutor(RedisExecutor executor) { 149 this.executor = executor; 150 try { 151 delsha = load("org.nuxeo.ecm.core.redis", "del-keys"); 152 } catch (RuntimeException cause) { 153 executor = null; 154 throw new NuxeoException("Cannot activate redis executor", cause); 155 } 156 } 157 158 @Override 159 public Long clear(final String pattern) { 160 return executor.execute(new RedisCallable<Long>() { 161 @Override 162 public Long call(Jedis jedis) { 163 List<String> keys = Arrays.asList(pattern); 164 List<String> args = Arrays.asList(); 165 return (Long) jedis.evalsha(delsha, keys, args); 166 } 167 }); 168 } 169 170 @Override 171 public String load(String bundleName, String scriptName) { 172 Bundle b = Framework.getRuntime().getBundle(bundleName); 173 URL loc = b.getEntry(scriptName + ".lua"); 174 if (loc == null) { 175 throw new RuntimeException("Fail to load lua script: " + scriptName); 176 } 177 InputStream is = null; 178 final StrBuilder builder; 179 try { 180 is = loc.openStream(); 181 builder = new StrBuilder(); 182 for (String line : IOUtils.readLines(is)) { 183 builder.appendln(line); 184 } 185 } catch (IOException e) { 186 throw new RuntimeException("Fail to load lua script: " + scriptName, e); 187 } 188 189 return executor.execute(new RedisCallable<String>() { 190 @Override 191 public String call(Jedis jedis) { 192 return jedis.scriptLoad(builder.toString()); 193 } 194 }); 195 } 196 197 @Override 198 public <T> T getAdapter(Class<T> adapter) { 199 if (adapter.isAssignableFrom(RedisExecutor.class)) { 200 return adapter.cast(executor); 201 } 202 return super.getAdapter(adapter); 203 } 204 205 @Override 206 public String namespace(String... names) { 207 RedisPoolDescriptor config = getConfig(); 208 String prefix = config == null ? null : config.prefix; 209 if (StringUtils.isBlank(prefix)) { 210 prefix = DEFAULT_PREFIX; 211 } 212 StringBuilder builder = new StringBuilder(prefix); 213 for (String name : names) { 214 builder.append(name).append(":"); 215 } 216 return builder.toString(); 217 } 218 219}