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