/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.activemq.store.memory; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.activemq.broker.ConnectionContext; import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.Message; import org.apache.activemq.command.MessageAck; import org.apache.activemq.command.MessageId; import org.apache.activemq.command.SubscriptionInfo; import org.apache.activemq.store.MessageRecoveryListener; import org.apache.activemq.store.MessageStoreStatistics; import org.apache.activemq.store.MessageStoreSubscriptionStatistics; import org.apache.activemq.store.TopicMessageStore; import org.apache.activemq.util.LRUCache; import org.apache.activemq.util.SubscriptionKey; public class MemoryTopicMessageStore extends MemoryMessageStore implements TopicMessageStore { private Map<SubscriptionKey, SubscriptionInfo> subscriberDatabase; private Map<SubscriptionKey, MemoryTopicSub> topicSubMap; private final Map<MessageId, Message> originalMessageTable; public MemoryTopicMessageStore(ActiveMQDestination destination) { this(destination, new MemoryTopicMessageStoreLRUCache(100, 100, 0.75f, false), makeSubscriptionInfoMap()); // Set the messageStoreStatistics after the super class is initialized // so that the stats can be properly updated on cache eviction MemoryTopicMessageStoreLRUCache cache = (MemoryTopicMessageStoreLRUCache) originalMessageTable; cache.setMessageStoreStatistics(messageStoreStatistics); } public MemoryTopicMessageStore(ActiveMQDestination destination, Map<MessageId, Message> messageTable, Map<SubscriptionKey, SubscriptionInfo> subscriberDatabase) { super(destination, messageTable); this.subscriberDatabase = subscriberDatabase; this.topicSubMap = makeSubMap(); // this is only necessary so that messageStoreStatistics can be set if // necessary We need the original reference since messageTable is wrapped // in a synchronized map in the parent class this.originalMessageTable = messageTable; } protected static Map<SubscriptionKey, SubscriptionInfo> makeSubscriptionInfoMap() { return Collections.synchronizedMap(new HashMap<SubscriptionKey, SubscriptionInfo>()); } protected static Map<SubscriptionKey, MemoryTopicSub> makeSubMap() { return Collections.synchronizedMap(new HashMap<SubscriptionKey, MemoryTopicSub>()); } @Override public synchronized void addMessage(ConnectionContext context, Message message) throws IOException { super.addMessage(context, message); for (MemoryTopicSub sub : topicSubMap.values()) { sub.addMessage(message.getMessageId(), message); } } @Override public synchronized void acknowledge(ConnectionContext context, String clientId, String subscriptionName, MessageId messageId, MessageAck ack) throws IOException { super.removeMessage(messageId); SubscriptionKey key = new SubscriptionKey(clientId, subscriptionName); MemoryTopicSub sub = topicSubMap.get(key); if (sub != null) { sub.removeMessage(messageId); } } @Override public synchronized SubscriptionInfo lookupSubscription(String clientId, String subscriptionName) throws IOException { return subscriberDatabase.get(new SubscriptionKey(clientId, subscriptionName)); } @Override public synchronized void addSubscription(SubscriptionInfo info, boolean retroactive) throws IOException { SubscriptionKey key = new SubscriptionKey(info); MemoryTopicSub sub = new MemoryTopicSub(key); topicSubMap.put(key, sub); if (retroactive) { for (Map.Entry<MessageId, Message> entry : messageTable.entrySet()) { sub.addMessage(entry.getKey(), entry.getValue()); } } subscriberDatabase.put(key, info); } @Override public synchronized void deleteSubscription(String clientId, String subscriptionName) { SubscriptionKey key = new SubscriptionKey(clientId, subscriptionName); subscriberDatabase.remove(key); MemoryTopicSub subscription = topicSubMap.get(key); if (subscription != null) { List<Message> storedMessages = subscription.getStoredMessages(); for (Message message : storedMessages) { try { acknowledge(null, key.getClientId(), key.getSubscriptionName(), message.getMessageId(), null); } catch (IOException e) { } } } subscriberDatabase.remove(key); topicSubMap.remove(key); } @Override public synchronized void recoverSubscription(String clientId, String subscriptionName, MessageRecoveryListener listener) throws Exception { MemoryTopicSub sub = topicSubMap.get(new SubscriptionKey(clientId, subscriptionName)); if (sub != null) { sub.recoverSubscription(listener); } } @Override public synchronized void delete() { super.delete(); subscriberDatabase.clear(); topicSubMap.clear(); } @Override public SubscriptionInfo[] getAllSubscriptions() throws IOException { return subscriberDatabase.values().toArray(new SubscriptionInfo[subscriberDatabase.size()]); } @Override public synchronized int getMessageCount(String clientId, String subscriberName) throws IOException { int result = 0; MemoryTopicSub sub = topicSubMap.get(new SubscriptionKey(clientId, subscriberName)); if (sub != null) { result = sub.size(); } return result; } @Override public synchronized long getMessageSize(String clientId, String subscriberName) throws IOException { long result = 0; MemoryTopicSub sub = topicSubMap.get(new SubscriptionKey(clientId, subscriberName)); if (sub != null) { result = sub.messageSize(); } return result; } @Override public synchronized void recoverNextMessages(String clientId, String subscriptionName, int maxReturned, MessageRecoveryListener listener) throws Exception { MemoryTopicSub sub = this.topicSubMap.get(new SubscriptionKey(clientId, subscriptionName)); if (sub != null) { sub.recoverNextMessages(maxReturned, listener); } } @Override public void resetBatching(String clientId, String subscriptionName) { MemoryTopicSub sub = topicSubMap.get(new SubscriptionKey(clientId, subscriptionName)); if (sub != null) { sub.resetBatching(); } } // Disabled for the memory store, can be enabled later if necessary private final MessageStoreSubscriptionStatistics stats = new MessageStoreSubscriptionStatistics(false); @Override public MessageStoreSubscriptionStatistics getMessageStoreSubStatistics() { return stats; } /** * Since we initialize the store with a LRUCache in some cases, we need to * account for cache evictions when computing the message store statistics. * */ private static class MemoryTopicMessageStoreLRUCache extends LRUCache<MessageId, Message> { private static final long serialVersionUID = -342098639681884413L; private MessageStoreStatistics messageStoreStatistics; public MemoryTopicMessageStoreLRUCache(int initialCapacity, int maximumCacheSize, float loadFactor, boolean accessOrder) { super(initialCapacity, maximumCacheSize, loadFactor, accessOrder); } public void setMessageStoreStatistics(MessageStoreStatistics messageStoreStatistics) { this.messageStoreStatistics = messageStoreStatistics; } @Override protected void onCacheEviction(Map.Entry<MessageId, Message> eldest) { decMessageStoreStatistics(messageStoreStatistics, eldest.getValue()); // We aren't tracking this anymore so remove our reference to it. eldest.getValue().decrementReferenceCount(); } } }