survey.data.RepositoryFactory.java Source code

Java tutorial

Introduction

Here is the source code for survey.data.RepositoryFactory.java

Source

/**
 * Copyright 2015 PDP Solutions, L.L.C. (http://pdpsolutions.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package survey.data;

import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPubSub;

public class RepositoryFactory {

    private static final String _CHANNEL_NAME = "channel";
    private final JedisPool _jedisPool;
    private final LocalCache _localCache;
    private final Logger _logger;
    private final Properties _properties;
    private final ExecutorService _subscribeService;

    public RepositoryFactory(Properties properties) {
        assert properties != null;

        _properties = properties;
        _localCache = new LocalCache(properties);
        String redisHost = _properties == null ? null : _properties.getProperty("redis.host");
        if (redisHost == null) {
            redisHost = "localhost";
        }
        int redisPort;
        try {
            redisPort = Integer.parseInt(_properties.getProperty("redis.port"));
        } catch (Throwable e) {
            redisPort = 6379;
        }
        // Anedotally, the web service will eventually fail due to a broken
        // connection to Redis, unless a pool is used.
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setBlockWhenExhausted(false); // Fail if out of sockets.
        poolConfig.setLifo(true);
        poolConfig.setMaxIdle(50); // All 50 sockets can be briefly idle.
        poolConfig.setMaxTotal(50); // After 50 sockets used then fail.
        poolConfig.setMinEvictableIdleTimeMillis(600000); // 10 minutes even if few sockets are idle.
        poolConfig.setMinIdle(2); // Keep 2 sockets ready.
        poolConfig.setSoftMinEvictableIdleTimeMillis(120000); // 2 minutes unless insufficient sockets are idle.
        poolConfig.setTestOnBorrow(true);
        poolConfig.setTestOnReturn(false);
        poolConfig.setTestWhileIdle(false);
        poolConfig.setTimeBetweenEvictionRunsMillis(300000); // Check for idle sockets every 5 minutes.
        _jedisPool = new JedisPool(poolConfig, redisHost, redisPort);

        // Monitor changes in Redis.
        _subscribeService = Executors.newSingleThreadExecutor();

        final JedisPubSub jedisPubSub = new JedisPubSub() {
            @Override
            public void onMessage(String channelName, String notificationKey) {
                // _logger.info(String.format("onMessage(%s, %s)", channelName, notificationKey));
                // See the publish() method below.
                String[] splitKey = notificationKey.split(":", 4);
                assert splitKey.length >= 4;
                String magic = splitKey[0];
                assert "survey".equals(magic);
                String namespaceName = splitKey[1];
                String surveyPath = splitKey[2];
                long turnout;
                try {
                    turnout = Long.parseLong(splitKey[3]);
                } catch (NumberFormatException e) {
                    turnout = 0;
                }
                onVoteAdded(namespaceName, surveyPath, turnout);
            }
        };

        _subscribeService.submit(new Runnable() {
            @Override
            public void run() {
                _logger.info("subscribe thread running");
                int sleepMillis = 0;
                for (;;) {
                    Jedis jedis = null;
                    try {
                        if (sleepMillis != 0) {
                            Thread.sleep(sleepMillis);
                        }
                        jedis = _jedisPool.getResource();
                        jedis.subscribe(jedisPubSub, _CHANNEL_NAME);
                    } catch (InterruptedException e) {
                        _logger.info("subscribe thread interrupted");
                        break;
                    } catch (Throwable e) {
                        // e.g. if there is a Jedis exception, sleep 1 minute and try again.
                        sleepMillis = 60000;
                    } finally {
                        if (jedis != null) {
                            _jedisPool.returnResourceObject(jedis);
                        }
                    }
                }
            }
        });

        _logger = LoggerFactory.getLogger(RepositoryFactory.class);
        _logger.info("created repository factory (singleton)");
    }

    public NamespaceStorage createNamespaceStorage(String namespaceName) {
        return new NamespaceStorage(this, namespaceName, _jedisPool, _properties);
    }

    // Called from NamespaceStorage.java
    protected LocalCache getLocalCache() {
        return _localCache;
    }

    /**
     * Called whenever a message is saved, even if it was saved by a different process.
     */
    public void onVoteAdded(String namespaceName, String surveyPath, long turnout) {
        // May be overridden
        _logger.info(String.format("skip onVoteAdded(%s, %s:%d)", namespaceName, surveyPath, turnout));
    }

    // Only called by VoteRepository.java
    protected void publish(String namespaceName, String surveyPath, long turnout) {
        String key = String.format("survey:%s:%s:%08d", namespaceName, surveyPath, turnout);
        // _logger.info(String.format("publish %s", key));
        Jedis jedis = _jedisPool.getResource();
        try {
            jedis.publish(_CHANNEL_NAME, key);
        } finally {
            _jedisPool.returnResourceObject(jedis);
        }
    }

    public void shutdownBackgroundThreads() {
        _logger.info("shutdown subscribe service");
        _localCache.shutdownBackgroundThreads();
        _subscribeService.shutdownNow();
    }

}