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.memory; 018 019import org.apache.activemq.broker.ConnectionContext; 020import org.apache.activemq.command.Message; 021import org.apache.activemq.command.MessageAck; 022import org.apache.activemq.command.MessageId; 023import org.apache.activemq.command.TransactionId; 024import org.apache.activemq.command.XATransactionId; 025import org.apache.activemq.store.InlineListenableFuture; 026import org.apache.activemq.store.ListenableFuture; 027import org.apache.activemq.store.MessageStore; 028import org.apache.activemq.store.PersistenceAdapter; 029import org.apache.activemq.store.ProxyMessageStore; 030import org.apache.activemq.store.ProxyTopicMessageStore; 031import org.apache.activemq.store.TopicMessageStore; 032import org.apache.activemq.store.TransactionRecoveryListener; 033import org.apache.activemq.store.TransactionStore; 034 035import java.io.IOException; 036import java.util.ArrayList; 037import java.util.Collections; 038import java.util.Iterator; 039import java.util.LinkedHashMap; 040import java.util.Map; 041import java.util.concurrent.ConcurrentHashMap; 042 043/** 044 * Provides a TransactionStore implementation that can create transaction aware 045 * MessageStore objects from non transaction aware MessageStore objects. 046 * 047 * 048 */ 049public class MemoryTransactionStore implements TransactionStore { 050 051 protected ConcurrentHashMap<Object, Tx> inflightTransactions = new ConcurrentHashMap<Object, Tx>(); 052 protected Map<TransactionId, Tx> preparedTransactions = Collections.synchronizedMap(new LinkedHashMap<TransactionId, Tx>()); 053 protected final PersistenceAdapter persistenceAdapter; 054 055 private boolean doingRecover; 056 057 public class Tx { 058 public ArrayList<AddMessageCommand> messages = new ArrayList<AddMessageCommand>(); 059 060 public final ArrayList<RemoveMessageCommand> acks = new ArrayList<RemoveMessageCommand>(); 061 062 public void add(AddMessageCommand msg) { 063 messages.add(msg); 064 } 065 066 public void add(RemoveMessageCommand ack) { 067 acks.add(ack); 068 } 069 070 public Message[] getMessages() { 071 Message rc[] = new Message[messages.size()]; 072 int count = 0; 073 for (Iterator<AddMessageCommand> iter = messages.iterator(); iter.hasNext();) { 074 AddMessageCommand cmd = iter.next(); 075 rc[count++] = cmd.getMessage(); 076 } 077 return rc; 078 } 079 080 public MessageAck[] getAcks() { 081 MessageAck rc[] = new MessageAck[acks.size()]; 082 int count = 0; 083 for (Iterator<RemoveMessageCommand> iter = acks.iterator(); iter.hasNext();) { 084 RemoveMessageCommand cmd = iter.next(); 085 rc[count++] = cmd.getMessageAck(); 086 } 087 return rc; 088 } 089 090 /** 091 * @throws IOException 092 */ 093 public void commit() throws IOException { 094 ConnectionContext ctx = new ConnectionContext(); 095 persistenceAdapter.beginTransaction(ctx); 096 try { 097 098 // Do all the message adds. 099 for (Iterator<AddMessageCommand> iter = messages.iterator(); iter.hasNext();) { 100 AddMessageCommand cmd = iter.next(); 101 cmd.run(ctx); 102 } 103 // And removes.. 104 for (Iterator<RemoveMessageCommand> iter = acks.iterator(); iter.hasNext();) { 105 RemoveMessageCommand cmd = iter.next(); 106 cmd.run(ctx); 107 } 108 109 } catch ( IOException e ) { 110 persistenceAdapter.rollbackTransaction(ctx); 111 throw e; 112 } 113 persistenceAdapter.commitTransaction(ctx); 114 } 115 } 116 117 public interface AddMessageCommand { 118 Message getMessage(); 119 120 MessageStore getMessageStore(); 121 122 void run(ConnectionContext context) throws IOException; 123 124 void setMessageStore(MessageStore messageStore); 125 } 126 127 public interface RemoveMessageCommand { 128 MessageAck getMessageAck(); 129 130 void run(ConnectionContext context) throws IOException; 131 132 MessageStore getMessageStore(); 133 } 134 135 public MemoryTransactionStore(PersistenceAdapter persistenceAdapter) { 136 this.persistenceAdapter=persistenceAdapter; 137 } 138 139 public MessageStore proxy(MessageStore messageStore) { 140 ProxyMessageStore proxyMessageStore = new ProxyMessageStore(messageStore) { 141 @Override 142 public void addMessage(ConnectionContext context, final Message send) throws IOException { 143 MemoryTransactionStore.this.addMessage(getDelegate(), send); 144 } 145 146 @Override 147 public void addMessage(ConnectionContext context, final Message send, boolean canOptimize) throws IOException { 148 MemoryTransactionStore.this.addMessage(getDelegate(), send); 149 } 150 151 @Override 152 public ListenableFuture<Object> asyncAddQueueMessage(ConnectionContext context, Message message) throws IOException { 153 MemoryTransactionStore.this.addMessage(getDelegate(), message); 154 return new InlineListenableFuture(); 155 } 156 157 @Override 158 public ListenableFuture<Object> asyncAddQueueMessage(ConnectionContext context, Message message, boolean canoptimize) throws IOException { 159 MemoryTransactionStore.this.addMessage(getDelegate(), message); 160 return new InlineListenableFuture(); 161 } 162 163 @Override 164 public void removeMessage(ConnectionContext context, final MessageAck ack) throws IOException { 165 MemoryTransactionStore.this.removeMessage(getDelegate(), ack); 166 } 167 168 @Override 169 public void removeAsyncMessage(ConnectionContext context, MessageAck ack) throws IOException { 170 MemoryTransactionStore.this.removeMessage(getDelegate(), ack); 171 } 172 }; 173 onProxyQueueStore(proxyMessageStore); 174 return proxyMessageStore; 175 } 176 177 protected void onProxyQueueStore(ProxyMessageStore proxyMessageStore) { 178 } 179 180 public TopicMessageStore proxy(TopicMessageStore messageStore) { 181 ProxyTopicMessageStore proxyTopicMessageStore = new ProxyTopicMessageStore(messageStore) { 182 @Override 183 public void addMessage(ConnectionContext context, final Message send) throws IOException { 184 MemoryTransactionStore.this.addMessage(getDelegate(), send); 185 } 186 187 @Override 188 public void addMessage(ConnectionContext context, final Message send, boolean canOptimize) throws IOException { 189 MemoryTransactionStore.this.addMessage(getDelegate(), send); 190 } 191 192 @Override 193 public ListenableFuture<Object> asyncAddTopicMessage(ConnectionContext context, Message message) throws IOException { 194 MemoryTransactionStore.this.addMessage(getDelegate(), message); 195 return new InlineListenableFuture(); 196 } 197 198 @Override 199 public ListenableFuture<Object> asyncAddTopicMessage(ConnectionContext context, Message message, boolean canOptimize) throws IOException { 200 MemoryTransactionStore.this.addMessage(getDelegate(), message); 201 return new InlineListenableFuture(); 202 } 203 204 @Override 205 public void removeMessage(ConnectionContext context, final MessageAck ack) throws IOException { 206 MemoryTransactionStore.this.removeMessage(getDelegate(), ack); 207 } 208 209 @Override 210 public void removeAsyncMessage(ConnectionContext context, MessageAck ack) throws IOException { 211 MemoryTransactionStore.this.removeMessage(getDelegate(), ack); 212 } 213 214 @Override 215 public void acknowledge(ConnectionContext context, String clientId, String subscriptionName, 216 MessageId messageId, MessageAck ack) throws IOException { 217 MemoryTransactionStore.this.acknowledge((TopicMessageStore)getDelegate(), clientId, 218 subscriptionName, messageId, ack); 219 } 220 }; 221 onProxyTopicStore(proxyTopicMessageStore); 222 return proxyTopicMessageStore; 223 } 224 225 protected void onProxyTopicStore(ProxyTopicMessageStore proxyTopicMessageStore) { 226 } 227 228 /** 229 * @see org.apache.activemq.store.TransactionStore#prepare(TransactionId) 230 */ 231 public void prepare(TransactionId txid) throws IOException { 232 Tx tx = inflightTransactions.remove(txid); 233 if (tx == null) { 234 return; 235 } 236 preparedTransactions.put(txid, tx); 237 } 238 239 public Tx getTx(Object txid) { 240 Tx tx = inflightTransactions.get(txid); 241 if (tx == null) { 242 tx = new Tx(); 243 inflightTransactions.put(txid, tx); 244 } 245 return tx; 246 } 247 248 public Tx getPreparedTx(TransactionId txid) { 249 Tx tx = preparedTransactions.get(txid); 250 if (tx == null) { 251 tx = new Tx(); 252 preparedTransactions.put(txid, tx); 253 } 254 return tx; 255 } 256 257 public void commit(TransactionId txid, boolean wasPrepared, Runnable preCommit,Runnable postCommit) throws IOException { 258 if (preCommit != null) { 259 preCommit.run(); 260 } 261 Tx tx; 262 if (wasPrepared) { 263 tx = preparedTransactions.remove(txid); 264 } else { 265 tx = inflightTransactions.remove(txid); 266 } 267 268 if (tx != null) { 269 tx.commit(); 270 } 271 if (postCommit != null) { 272 postCommit.run(); 273 } 274 } 275 276 /** 277 * @see org.apache.activemq.store.TransactionStore#rollback(TransactionId) 278 */ 279 public void rollback(TransactionId txid) throws IOException { 280 preparedTransactions.remove(txid); 281 inflightTransactions.remove(txid); 282 } 283 284 public void start() throws Exception { 285 } 286 287 public void stop() throws Exception { 288 } 289 290 public synchronized void recover(TransactionRecoveryListener listener) throws IOException { 291 // All the inflight transactions get rolled back.. 292 inflightTransactions.clear(); 293 this.doingRecover = true; 294 try { 295 for (Iterator<TransactionId> iter = preparedTransactions.keySet().iterator(); iter.hasNext();) { 296 Object txid = iter.next(); 297 Tx tx = preparedTransactions.get(txid); 298 listener.recover((XATransactionId)txid, tx.getMessages(), tx.getAcks()); 299 onRecovered(tx); 300 } 301 } finally { 302 this.doingRecover = false; 303 } 304 } 305 306 protected void onRecovered(Tx tx) { 307 } 308 309 /** 310 * @param message 311 * @throws IOException 312 */ 313 void addMessage(final MessageStore destination, final Message message) throws IOException { 314 315 if (doingRecover) { 316 return; 317 } 318 319 if (message.getTransactionId() != null) { 320 Tx tx = getTx(message.getTransactionId()); 321 tx.add(new AddMessageCommand() { 322 MessageStore messageStore = destination; 323 public Message getMessage() { 324 return message; 325 } 326 327 @Override 328 public MessageStore getMessageStore() { 329 return destination; 330 } 331 332 public void run(ConnectionContext ctx) throws IOException { 333 destination.addMessage(ctx, message); 334 } 335 336 @Override 337 public void setMessageStore(MessageStore messageStore) { 338 this.messageStore = messageStore; 339 } 340 341 }); 342 } else { 343 destination.addMessage(null, message); 344 } 345 } 346 347 /** 348 * @param ack 349 * @throws IOException 350 */ 351 final void removeMessage(final MessageStore destination, final MessageAck ack) throws IOException { 352 if (doingRecover) { 353 return; 354 } 355 356 if (ack.isInTransaction()) { 357 Tx tx = getTx(ack.getTransactionId()); 358 tx.add(new RemoveMessageCommand() { 359 public MessageAck getMessageAck() { 360 return ack; 361 } 362 363 public void run(ConnectionContext ctx) throws IOException { 364 destination.removeMessage(ctx, ack); 365 } 366 367 @Override 368 public MessageStore getMessageStore() { 369 return destination; 370 } 371 }); 372 } else { 373 destination.removeMessage(null, ack); 374 } 375 } 376 377 public void acknowledge(final TopicMessageStore destination, final String clientId, final String subscriptionName, 378 final MessageId messageId, final MessageAck ack) throws IOException { 379 if (doingRecover) { 380 return; 381 } 382 383 if (ack.isInTransaction()) { 384 Tx tx = getTx(ack.getTransactionId()); 385 tx.add(new RemoveMessageCommand() { 386 public MessageAck getMessageAck() { 387 return ack; 388 } 389 390 public void run(ConnectionContext ctx) throws IOException { 391 destination.acknowledge(ctx, clientId, subscriptionName, messageId, ack); 392 } 393 394 @Override 395 public MessageStore getMessageStore() { 396 return destination; 397 } 398 }); 399 } else { 400 destination.acknowledge(null, clientId, subscriptionName, messageId, ack); 401 } 402 } 403 404 405 public void delete() { 406 inflightTransactions.clear(); 407 preparedTransactions.clear(); 408 doingRecover = false; 409 } 410 411}