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