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