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