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