001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.store.journal; 018 019import java.io.IOException; 020import java.util.HashMap; 021import java.util.Iterator; 022 023import org.apache.activeio.journal.RecordLocation; 024import org.apache.activemq.broker.ConnectionContext; 025import org.apache.activemq.command.ActiveMQTopic; 026import org.apache.activemq.command.JournalTopicAck; 027import org.apache.activemq.command.Message; 028import org.apache.activemq.command.MessageAck; 029import org.apache.activemq.command.MessageId; 030import org.apache.activemq.command.SubscriptionInfo; 031import org.apache.activemq.store.MessageRecoveryListener; 032import org.apache.activemq.store.TopicMessageStore; 033import org.apache.activemq.transaction.Synchronization; 034import org.apache.activemq.util.Callback; 035import org.apache.activemq.util.SubscriptionKey; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039/** 040 * A MessageStore that uses a Journal to store it's messages. 041 * 042 * 043 */ 044public class JournalTopicMessageStore extends JournalMessageStore implements TopicMessageStore { 045 046 private static final Logger LOG = LoggerFactory.getLogger(JournalTopicMessageStore.class); 047 048 private TopicMessageStore longTermStore; 049 private HashMap<SubscriptionKey, MessageId> ackedLastAckLocations = new HashMap<SubscriptionKey, MessageId>(); 050 051 public JournalTopicMessageStore(JournalPersistenceAdapter adapter, TopicMessageStore checkpointStore, 052 ActiveMQTopic destinationName) { 053 super(adapter, checkpointStore, destinationName); 054 this.longTermStore = checkpointStore; 055 } 056 057 @Override 058 public void recoverSubscription(String clientId, String subscriptionName, MessageRecoveryListener listener) 059 throws Exception { 060 this.peristenceAdapter.checkpoint(true, true); 061 longTermStore.recoverSubscription(clientId, subscriptionName, listener); 062 } 063 064 @Override 065 public void recoverNextMessages(String clientId, String subscriptionName, int maxReturned, 066 MessageRecoveryListener listener) throws Exception { 067 this.peristenceAdapter.checkpoint(true, true); 068 longTermStore.recoverNextMessages(clientId, subscriptionName, maxReturned, listener); 069 070 } 071 072 @Override 073 public SubscriptionInfo lookupSubscription(String clientId, String subscriptionName) throws IOException { 074 return longTermStore.lookupSubscription(clientId, subscriptionName); 075 } 076 077 @Override 078 public void addSubscription(SubscriptionInfo subscriptionInfo, boolean retroactive) throws IOException { 079 this.peristenceAdapter.checkpoint(true, true); 080 longTermStore.addSubscription(subscriptionInfo, retroactive); 081 } 082 083 @Override 084 public void addMessage(ConnectionContext context, Message message) throws IOException { 085 super.addMessage(context, message); 086 } 087 088 /** 089 */ 090 @Override 091 public void acknowledge(ConnectionContext context, String clientId, String subscriptionName, 092 final MessageId messageId, MessageAck originalAck) throws IOException { 093 final boolean debug = LOG.isDebugEnabled(); 094 095 JournalTopicAck ack = new JournalTopicAck(); 096 ack.setDestination(destination); 097 ack.setMessageId(messageId); 098 ack.setMessageSequenceId(messageId.getBrokerSequenceId()); 099 ack.setSubscritionName(subscriptionName); 100 ack.setClientId(clientId); 101 ack.setTransactionId(context.getTransaction() != null 102 ? context.getTransaction().getTransactionId() : null); 103 final RecordLocation location = peristenceAdapter.writeCommand(ack, false); 104 105 final SubscriptionKey key = new SubscriptionKey(clientId, subscriptionName); 106 if (!context.isInTransaction()) { 107 if (debug) { 108 LOG.debug("Journalled acknowledge for: " + messageId + ", at: " + location); 109 } 110 acknowledge(messageId, location, key); 111 } else { 112 if (debug) { 113 LOG.debug("Journalled transacted acknowledge for: " + messageId + ", at: " + location); 114 } 115 synchronized (this) { 116 inFlightTxLocations.add(location); 117 } 118 transactionStore.acknowledge(this, ack, location); 119 context.getTransaction().addSynchronization(new Synchronization() { 120 @Override 121 public void afterCommit() throws Exception { 122 if (debug) { 123 LOG.debug("Transacted acknowledge commit for: " + messageId + ", at: " + location); 124 } 125 synchronized (JournalTopicMessageStore.this) { 126 inFlightTxLocations.remove(location); 127 acknowledge(messageId, location, key); 128 } 129 } 130 131 @Override 132 public void afterRollback() throws Exception { 133 if (debug) { 134 LOG.debug("Transacted acknowledge rollback for: " + messageId + ", at: " + location); 135 } 136 synchronized (JournalTopicMessageStore.this) { 137 inFlightTxLocations.remove(location); 138 } 139 } 140 }); 141 } 142 143 } 144 145 public void replayAcknowledge(ConnectionContext context, String clientId, String subscritionName, 146 MessageId messageId) { 147 try { 148 SubscriptionInfo sub = longTermStore.lookupSubscription(clientId, subscritionName); 149 if (sub != null) { 150 longTermStore.acknowledge(context, clientId, subscritionName, messageId, null); 151 } 152 } catch (Throwable e) { 153 LOG.debug("Could not replay acknowledge for message '" + messageId 154 + "'. Message may have already been acknowledged. reason: " + e); 155 } 156 } 157 158 /** 159 * @param messageId 160 * @param location 161 * @param key 162 */ 163 protected void acknowledge(MessageId messageId, RecordLocation location, SubscriptionKey key) { 164 synchronized (this) { 165 lastLocation = location; 166 ackedLastAckLocations.put(key, messageId); 167 } 168 } 169 170 @Override 171 public RecordLocation checkpoint() throws IOException { 172 173 final HashMap<SubscriptionKey, MessageId> cpAckedLastAckLocations; 174 175 // swap out the hash maps.. 176 synchronized (this) { 177 cpAckedLastAckLocations = this.ackedLastAckLocations; 178 this.ackedLastAckLocations = new HashMap<SubscriptionKey, MessageId>(); 179 } 180 181 return super.checkpoint(new Callback() { 182 @Override 183 public void execute() throws Exception { 184 185 // Checkpoint the acknowledged messages. 186 Iterator<SubscriptionKey> iterator = cpAckedLastAckLocations.keySet().iterator(); 187 while (iterator.hasNext()) { 188 SubscriptionKey subscriptionKey = iterator.next(); 189 MessageId identity = cpAckedLastAckLocations.get(subscriptionKey); 190 longTermStore.acknowledge(transactionTemplate.getContext(), subscriptionKey.clientId, 191 subscriptionKey.subscriptionName, identity, null); 192 } 193 194 } 195 }); 196 197 } 198 199 /** 200 * @return Returns the longTermStore. 201 */ 202 public TopicMessageStore getLongTermTopicMessageStore() { 203 return longTermStore; 204 } 205 206 @Override 207 public void deleteSubscription(String clientId, String subscriptionName) throws IOException { 208 longTermStore.deleteSubscription(clientId, subscriptionName); 209 } 210 211 @Override 212 public SubscriptionInfo[] getAllSubscriptions() throws IOException { 213 return longTermStore.getAllSubscriptions(); 214 } 215 216 @Override 217 public int getMessageCount(String clientId, String subscriberName) throws IOException { 218 this.peristenceAdapter.checkpoint(true, true); 219 return longTermStore.getMessageCount(clientId, subscriberName); 220 } 221 222 @Override 223 public long getMessageSize(String clientId, String subscriberName) throws IOException { 224 this.peristenceAdapter.checkpoint(true, true); 225 return longTermStore.getMessageSize(clientId, subscriberName); 226 } 227 228 @Override 229 public void resetBatching(String clientId, String subscriptionName) { 230 longTermStore.resetBatching(clientId, subscriptionName); 231 } 232 233}