001/*
002 * (C) Copyright 2013-2014 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Florent Guillaume
016 */
017package org.nuxeo.ecm.core.redis;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.net.URL;
022import java.util.Arrays;
023import java.util.List;
024
025import org.apache.commons.io.IOUtils;
026import org.apache.commons.lang.StringUtils;
027import org.apache.commons.lang.text.StrBuilder;
028import org.nuxeo.ecm.core.api.NuxeoException;
029import org.nuxeo.runtime.RuntimeServiceEvent;
030import org.nuxeo.runtime.RuntimeServiceListener;
031import org.nuxeo.runtime.api.Framework;
032import org.nuxeo.runtime.model.ComponentContext;
033import org.nuxeo.runtime.model.ComponentInstance;
034import org.nuxeo.runtime.model.DefaultComponent;
035import org.nuxeo.runtime.model.SimpleContributionRegistry;
036import org.osgi.framework.Bundle;
037
038import redis.clients.jedis.Jedis;
039
040/**
041 * Implementation of the Redis Service holding the configured Jedis pool.
042 *
043 * @since 5.8
044 */
045public class RedisComponent extends DefaultComponent implements RedisAdmin {
046
047    private static final String DEFAULT_PREFIX = "nuxeo:";
048
049    protected volatile RedisExecutor executor = RedisExecutor.NOOP;
050
051    protected RedisPoolDescriptorRegistry registry = new RedisPoolDescriptorRegistry();
052
053    public static class RedisPoolDescriptorRegistry extends SimpleContributionRegistry<RedisPoolDescriptor> {
054
055        protected RedisPoolDescriptor config;
056
057        @Override
058        public String getContributionId(RedisPoolDescriptor contrib) {
059            return "main";
060        }
061
062        @Override
063        public void contributionUpdated(String id, RedisPoolDescriptor contrib, RedisPoolDescriptor newOrigContrib) {
064            config = contrib;
065        }
066
067        @Override
068        public void contributionRemoved(String id, RedisPoolDescriptor origContrib) {
069            config = null;
070        }
071
072        public RedisPoolDescriptor getConfig() {
073            return config;
074        }
075
076        public void clear() {
077            config = null;
078        }
079    };
080
081    protected String delsha;
082
083    @Override
084    public void activate(ComponentContext context) {
085        super.activate(context);
086        registry.clear();
087    }
088
089    @Override
090    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
091        if (contribution instanceof RedisPoolDescriptor) {
092            registerRedisPoolDescriptor((RedisPoolDescriptor) contribution);
093        } else {
094            throw new NuxeoException("Unknown contribution class: " + contribution);
095        }
096    }
097
098    @Override
099    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
100        if (contribution instanceof RedisPoolDescriptor) {
101            unregisterRedisPoolDescriptor((RedisPoolDescriptor) contribution);
102        }
103    }
104
105    public void registerRedisPoolDescriptor(RedisPoolDescriptor contrib) {
106        registry.addContribution(contrib);
107    }
108
109    public void unregisterRedisPoolDescriptor(RedisPoolDescriptor contrib) {
110        registry.removeContribution(contrib);
111    }
112
113    public RedisPoolDescriptor getConfig() {
114        return registry.getConfig();
115    }
116
117    @Override
118    public void applicationStarted(ComponentContext context) {
119        RedisPoolDescriptor config = getConfig();
120        if (config == null || config.disabled) {
121            return;
122        }
123        Framework.addListener(new RuntimeServiceListener() {
124
125            @Override
126            public void handleEvent(RuntimeServiceEvent event) {
127                if (event.id != RuntimeServiceEvent.RUNTIME_ABOUT_TO_STOP) {
128                    return;
129                }
130                Framework.removeListener(this);
131                try {
132                    executor.getPool().destroy();
133                } finally {
134                    executor = null;
135                }
136            }
137        });
138        handleNewExecutor(config.newExecutor());
139    }
140
141    @Override
142    public int getApplicationStartedOrder() {
143        return ((DefaultComponent) Framework.getRuntime().getComponentInstance("org.nuxeo.ecm.core.work.service").getInstance()).getApplicationStartedOrder() - 1;
144    }
145
146    public void handleNewExecutor(RedisExecutor executor) {
147        this.executor = executor;
148        try {
149            delsha = load("org.nuxeo.ecm.core.redis", "del-keys");
150        } catch (IOException cause) {
151            executor = null;
152            throw new NuxeoException("Cannot activate redis executor", cause);
153        }
154    }
155
156    @Override
157    public Long clear(final String pattern) {
158        return executor.execute(new RedisCallable<Long>() {
159            @Override
160            public Long call(Jedis jedis) {
161                List<String> keys = Arrays.asList(pattern);
162                List<String> args = Arrays.asList();
163                return (Long) jedis.evalsha(delsha, keys, args);
164            }
165        });
166    }
167
168    @Override
169    public String load(String bundleName, String scriptName) throws IOException {
170        Bundle b = Framework.getRuntime().getBundle(bundleName);
171        URL loc = b.getEntry(scriptName + ".lua");
172        InputStream is = loc.openStream();
173        final StrBuilder builder = new StrBuilder();
174        for (String line : IOUtils.readLines(is)) {
175            builder.appendln(line);
176        }
177        return executor.execute(new RedisCallable<String>() {
178            @Override
179            public String call(Jedis jedis) {
180                return jedis.scriptLoad(builder.toString());
181            }
182        });
183    }
184
185    @Override
186    public <T> T getAdapter(Class<T> adapter) {
187        if (adapter.isAssignableFrom(RedisExecutor.class)) {
188            return adapter.cast(executor);
189        }
190        return super.getAdapter(adapter);
191    }
192
193    @Override
194    public String namespace(String... names) {
195        RedisPoolDescriptor config = getConfig();
196        String prefix = config == null ? null : config.prefix;
197        if (StringUtils.isBlank(prefix)) {
198            prefix = DEFAULT_PREFIX;
199        }
200        StringBuilder builder = new StringBuilder(prefix);
201        for (String name : names) {
202            builder.append(name).append(":");
203        }
204        return builder.toString();
205    }
206
207}