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 (IOException 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) throws IOException {
172        Bundle b = Framework.getRuntime().getBundle(bundleName);
173        URL loc = b.getEntry(scriptName + ".lua");
174        InputStream is = loc.openStream();
175        final StrBuilder builder = new StrBuilder();
176        for (String line : IOUtils.readLines(is)) {
177            builder.appendln(line);
178        }
179        return executor.execute(new RedisCallable<String>() {
180            @Override
181            public String call(Jedis jedis) {
182                return jedis.scriptLoad(builder.toString());
183            }
184        });
185    }
186
187    @Override
188    public <T> T getAdapter(Class<T> adapter) {
189        if (adapter.isAssignableFrom(RedisExecutor.class)) {
190            return adapter.cast(executor);
191        }
192        return super.getAdapter(adapter);
193    }
194
195    @Override
196    public String namespace(String... names) {
197        RedisPoolDescriptor config = getConfig();
198        String prefix = config == null ? null : config.prefix;
199        if (StringUtils.isBlank(prefix)) {
200            prefix = DEFAULT_PREFIX;
201        }
202        StringBuilder builder = new StringBuilder(prefix);
203        for (String name : names) {
204            builder.append(name).append(":");
205        }
206        return builder.toString();
207    }
208
209}