Java tutorial
/** * 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(); } }