Java tutorial
/* * Copyright 2012 Janrain, Inc. * * 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 com.janrain.backplane.server.dao.redis; import com.janrain.backplane.server.BackplaneMessage; import com.janrain.backplane.server.BackplaneServerException; import com.janrain.backplane.server.dao.DAO; import com.janrain.commons.supersimpledb.SimpleDBException; import com.janrain.redis.Redis; import com.yammer.metrics.Metrics; import com.yammer.metrics.core.Histogram; import com.yammer.metrics.core.MetricName; import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang.SerializationUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import redis.clients.jedis.Jedis; import redis.clients.jedis.Pipeline; import redis.clients.jedis.Response; import redis.clients.jedis.Transaction; import redis.clients.jedis.exceptions.JedisConnectionException; import java.util.*; /** * @author Tom Raney */ public class RedisBackplaneMessageDAO extends DAO<BackplaneMessage> { final public static String V1_MESSAGE_QUEUE = "v1_message_queue"; final public static String V1_MESSAGES = "v1_messages"; public static byte[] getBusKey(String bus) { return ("v1_bus_idx_" + bus).getBytes(); } public static byte[] getChannelKey(String channel) { return ("v1_channel_idx_" + channel).getBytes(); } public static byte[] getKey(String key) { return ("v1_message_" + key).getBytes(); } /** * Add message to work queue - any node may add since it is an atomic operation * However, the message ID will be determined later by the message processor * @param message */ @Override public void persist(BackplaneMessage message) throws BackplaneServerException { Redis.getInstance().rpush(V1_MESSAGE_QUEUE.getBytes(), SerializationUtils.serialize(message)); } @Override public void delete(String id) throws BackplaneServerException { Jedis jedis = null; try { jedis = Redis.getInstance().getWriteJedis(); Date d = BackplaneMessage.getDateFromId(id); Set<String> sortedSetBytes = jedis.zrangeByScore(V1_MESSAGES, d.getTime(), d.getTime()); if (!sortedSetBytes.isEmpty()) { String key = sortedSetBytes.iterator().next(); Transaction t = jedis.multi(); Response<Long> del1 = t.zrem(V1_MESSAGES, key); String[] args = key.split(" "); Response<Long> del2 = t.lrem(getChannelKey(args[1]), 0, args[2].getBytes()); Response<Long> del3 = t.zrem(getBusKey(args[0]), args[2].getBytes()); Response<Long> del4 = t.del(getKey(id)); t.exec(); if (del1.get() == 0) { logger.warn("could not remove message " + id + " from " + V1_MESSAGES); } if (del2.get() == 0) { logger.warn("could not remove message " + id + " from " + new String(getChannelKey(args[1]))); } if (del3.get() == 0) { logger.warn("could not remove message " + id + " from " + new String(getBusKey(args[0]))); } if (del4.get() == 0) { logger.warn("could not remove message " + id + " from " + new String(getKey(id)) + " but it may have expired"); } logger.info("v1 message " + id + " deleted"); } else { logger.warn("v1 message " + id + " not found in " + V1_MESSAGES); } } finally { Redis.getInstance().releaseToPool(jedis); } } public void deleteExpiredMessages() { Jedis jedis = null; int cleanedUpCount = 0; try { logger.info("preparing to cleanup v1 messages"); jedis = Redis.getInstance().getWriteJedis(); Set<byte[]> messageMetaBytes = jedis.zrangeByScore(RedisBackplaneMessageDAO.V1_MESSAGES.getBytes(), 0, Double.MAX_VALUE); if (messageMetaBytes != null) { logger.info("scanning " + messageMetaBytes.size() + " v1 messages"); int messageCounter = 0; for (byte[] b : messageMetaBytes) { if (messageCounter++ % 100 == 0) { logger.info("still scanning v1 messages..."); } String metaData = new String(b); String[] segs = metaData.split(" "); String key = segs[2]; // if the message body is not found, it expired and should be removed from indexes if (!jedis.exists(getKey(key))) { delete(key); cleanedUpCount++; } } } } catch (JedisConnectionException jce) { logger.warn("exited message cleanup: " + jce.getMessage()); Redis.getInstance().releaseBrokenResourceToPool(jedis); jedis = null; } catch (Exception e) { logger.warn(e); } finally { logger.info("exiting v1 message cleanup, " + cleanedUpCount + " messages deleted"); Redis.getInstance().releaseToPool(jedis); } } @Override public BackplaneMessage get(String key) { byte[] messageBytes = Redis.getInstance().get(key.getBytes()); if (messageBytes != null) { return (BackplaneMessage) SerializationUtils.deserialize(messageBytes); } return null; } @Override public List<BackplaneMessage> getAll() throws BackplaneServerException { throw new NotImplementedException(); } public int getMessageCount(String bus, String channel) { return (int) Redis.getInstance().llen(getChannelKey(channel)); } /** * Fetch a list (possibly empty) of backplane messages that exist on the channel * and return them in order by message id * @param channel * @return */ public List<BackplaneMessage> getMessagesByChannel(String bus, String channel, String since, String sticky) throws SimpleDBException, BackplaneServerException { Jedis jedis = null; try { jedis = Redis.getInstance().getReadJedis(); double sinceInMs = 0; if (StringUtils.isNotBlank(since)) { sinceInMs = BackplaneMessage.getDateFromId(since).getTime(); } // every message has a unique timestamp - which serves as a key for indexing List<byte[]> messageIdBytes = jedis.lrange(getChannelKey(channel), 0, -1); List<BackplaneMessage> messages = new ArrayList<BackplaneMessage>(); if (!messageIdBytes.isEmpty()) { int i = 0; for (byte[] key : messageIdBytes) { messageIdBytes.set(i++, getKey(new String(key))); } List<byte[]> responses = jedis.mget(messageIdBytes.toArray(new byte[messageIdBytes.size()][])); for (byte[] response : responses) { if (response != null) { messages.add((BackplaneMessage) SerializationUtils.deserialize(response)); } } } filterAndSort(messages, since, sticky); return messages; } catch (JedisConnectionException jce) { logger.warn("connection broken on bus " + bus + " and channel " + channel); Redis.getInstance().releaseBrokenResourceToPool(jedis); jedis = null; throw new BackplaneServerException(jce.getMessage(), jce); } catch (Exception e) { logger.error("Exception on bus " + bus + " and channel " + channel, e); throw new BackplaneServerException(e.getMessage(), e); } finally { Redis.getInstance().releaseToPool(jedis); } } public List<String> getMessageIds(List<BackplaneMessage> messages) { List<String> ids = new ArrayList<String>(); for (BackplaneMessage message : messages) { ids.add(message.getIdValue()); } return ids; } public List<BackplaneMessage> getMessagesByBus(String bus, String since, String sticky) throws SimpleDBException, BackplaneServerException { Jedis jedis = null; try { jedis = Redis.getInstance().getReadJedis(); double sinceInMs = 0; if (StringUtils.isNotBlank(since)) { sinceInMs = BackplaneMessage.getDateFromId(since).getTime(); } // every message has a unique timestamp - which serves as a key for indexing Set<byte[]> messageIdBytes = Redis.getInstance().zrangebyscore(RedisBackplaneMessageDAO.getBusKey(bus), sinceInMs, Double.POSITIVE_INFINITY); List<BackplaneMessage> messages = new ArrayList<BackplaneMessage>(); Pipeline pipeline = jedis.pipelined(); List<Response<byte[]>> responses = new ArrayList<Response<byte[]>>(); if (messageIdBytes != null) { for (byte[] b : messageIdBytes) { responses.add(pipeline.get(getKey(new String(b)))); } pipeline.sync(); for (Response<byte[]> response : responses) { byte[] bytes = response.get(); if (bytes != null) { BackplaneMessage backplaneMessage = (BackplaneMessage) SerializationUtils .deserialize(bytes); messages.add(backplaneMessage); } } } filterAndSort(messages, since, sticky); return messages; } finally { Redis.getInstance().releaseToPool(jedis); } } // - PRIVATE private static final Logger logger = Logger.getLogger(RedisBackplaneMessageDAO.class); private final Histogram messagesPerChannel = Metrics.newHistogram( new MetricName("v1", this.getClass().getName().replace(".", "_"), "v1_messages_per_channel")); private void filterAndSort(List<BackplaneMessage> messages, String since, String sticky) { // filter per sticky flag if (StringUtils.isNotBlank(sticky)) { Iterator<BackplaneMessage> iterator = messages.iterator(); while (iterator.hasNext()) { BackplaneMessage message = iterator.next(); if (!message.get(BackplaneMessage.Field.STICKY.getFieldName()).equals(sticky)) { iterator.remove(); } } } // filter per since flag if (StringUtils.isNotBlank(since)) { Iterator<BackplaneMessage> iterator = messages.iterator(); while (iterator.hasNext()) { BackplaneMessage message = iterator.next(); if (message.getIdValue().compareTo(since) <= 0) { iterator.remove(); } } } Collections.sort(messages, new Comparator<BackplaneMessage>() { @Override public int compare(BackplaneMessage backplaneMessage, BackplaneMessage backplaneMessage1) { return backplaneMessage.getIdValue().compareTo(backplaneMessage1.getIdValue()); } }); } private String genBusKey(String key) { return "v1bus" + key; } private String genChannelKey(String key) { return "v1channel" + key; } }