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