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.jdbc; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.HashMap; 022import java.util.Iterator; 023import org.apache.activemq.broker.ConnectionContext; 024import org.apache.activemq.command.ActiveMQDestination; 025import org.apache.activemq.command.Message; 026import org.apache.activemq.command.MessageAck; 027import org.apache.activemq.command.MessageId; 028import org.apache.activemq.command.TransactionId; 029import org.apache.activemq.command.XATransactionId; 030import org.apache.activemq.store.MessageStore; 031import org.apache.activemq.store.ProxyMessageStore; 032import org.apache.activemq.store.ProxyTopicMessageStore; 033import org.apache.activemq.store.TopicMessageStore; 034import org.apache.activemq.store.TransactionRecoveryListener; 035import org.apache.activemq.store.memory.MemoryTransactionStore; 036import org.apache.activemq.util.ByteSequence; 037import org.apache.activemq.util.DataByteArrayInputStream; 038 039/** 040 * respect 2pc prepare 041 * uses local transactions to maintain prepared state 042 * xid column provides transaction flag for additions and removals 043 * a commit clears that context and completes the work 044 * a rollback clears the flag and removes the additions 045 * Essentially a prepare is an insert &| update transaction 046 * commit|rollback is an update &| remove 047 */ 048public class JdbcMemoryTransactionStore extends MemoryTransactionStore { 049 050 051 private HashMap<ActiveMQDestination, MessageStore> topicStores = new HashMap<ActiveMQDestination, MessageStore>(); 052 private HashMap<ActiveMQDestination, MessageStore> queueStores = new HashMap<ActiveMQDestination, MessageStore>(); 053 054 public JdbcMemoryTransactionStore(JDBCPersistenceAdapter jdbcPersistenceAdapter) { 055 super(jdbcPersistenceAdapter); 056 } 057 058 @Override 059 public void prepare(TransactionId txid) throws IOException { 060 Tx tx = inflightTransactions.remove(txid); 061 if (tx == null) { 062 return; 063 } 064 065 ConnectionContext ctx = new ConnectionContext(); 066 // setting the xid modifies the add/remove to be pending transaction outcome 067 ctx.setXid((XATransactionId) txid); 068 persistenceAdapter.beginTransaction(ctx); 069 try { 070 071 // Do all the message adds. 072 for (Iterator<AddMessageCommand> iter = tx.messages.iterator(); iter.hasNext();) { 073 AddMessageCommand cmd = iter.next(); 074 cmd.run(ctx); 075 } 076 // And removes.. 077 for (Iterator<RemoveMessageCommand> iter = tx.acks.iterator(); iter.hasNext();) { 078 RemoveMessageCommand cmd = iter.next(); 079 cmd.run(ctx); 080 } 081 082 } catch ( IOException e ) { 083 persistenceAdapter.rollbackTransaction(ctx); 084 throw e; 085 } 086 persistenceAdapter.commitTransaction(ctx); 087 088 ctx.setXid(null); 089 // setup for commit outcome 090 ArrayList<AddMessageCommand> updateFromPreparedStateCommands = new ArrayList<AddMessageCommand>(); 091 for (Iterator<AddMessageCommand> iter = tx.messages.iterator(); iter.hasNext();) { 092 final AddMessageCommand addMessageCommand = iter.next(); 093 updateFromPreparedStateCommands.add(new AddMessageCommand() { 094 @Override 095 public Message getMessage() { 096 return addMessageCommand.getMessage(); 097 } 098 099 @Override 100 public MessageStore getMessageStore() { 101 return addMessageCommand.getMessageStore(); 102 } 103 104 @Override 105 public void run(ConnectionContext context) throws IOException { 106 JDBCPersistenceAdapter jdbcPersistenceAdapter = (JDBCPersistenceAdapter) persistenceAdapter; 107 Message message = addMessageCommand.getMessage(); 108 jdbcPersistenceAdapter.commitAdd(context, message.getMessageId()); 109 ((JDBCMessageStore)addMessageCommand.getMessageStore()).onAdd( 110 message, 111 (Long)message.getMessageId().getFutureOrSequenceLong(), 112 message.getPriority()); 113 114 } 115 116 @Override 117 public void setMessageStore(MessageStore messageStore) { 118 throw new RuntimeException("MessageStore already known"); 119 } 120 }); 121 } 122 tx.messages = updateFromPreparedStateCommands; 123 preparedTransactions.put(txid, tx); 124 125 } 126 127 128 @Override 129 public void rollback(TransactionId txid) throws IOException { 130 131 Tx tx = inflightTransactions.remove(txid); 132 if (tx == null) { 133 tx = preparedTransactions.remove(txid); 134 if (tx != null) { 135 // undo prepare work 136 ConnectionContext ctx = new ConnectionContext(); 137 persistenceAdapter.beginTransaction(ctx); 138 try { 139 140 for (Iterator<AddMessageCommand> iter = tx.messages.iterator(); iter.hasNext(); ) { 141 final Message message = iter.next().getMessage(); 142 // need to delete the row 143 ((JDBCPersistenceAdapter) persistenceAdapter).commitRemove(ctx, new MessageAck(message, MessageAck.STANDARD_ACK_TYPE, 1)); 144 } 145 146 for (Iterator<RemoveMessageCommand> iter = tx.acks.iterator(); iter.hasNext(); ) { 147 RemoveMessageCommand removeMessageCommand = iter.next(); 148 if (removeMessageCommand instanceof LastAckCommand ) { 149 ((LastAckCommand)removeMessageCommand).rollback(ctx); 150 } else { 151 // need to unset the txid flag on the existing row 152 ((JDBCPersistenceAdapter) persistenceAdapter).commitAdd(ctx, 153 removeMessageCommand.getMessageAck().getLastMessageId()); 154 } 155 } 156 } catch (IOException e) { 157 persistenceAdapter.rollbackTransaction(ctx); 158 throw e; 159 } 160 persistenceAdapter.commitTransaction(ctx); 161 } 162 } 163 } 164 165 @Override 166 public void recover(TransactionRecoveryListener listener) throws IOException { 167 ((JDBCPersistenceAdapter)persistenceAdapter).recover(this); 168 super.recover(listener); 169 } 170 171 public void recoverAdd(long id, byte[] messageBytes) throws IOException { 172 final Message message = (Message) ((JDBCPersistenceAdapter)persistenceAdapter).getWireFormat().unmarshal(new ByteSequence(messageBytes)); 173 message.getMessageId().setFutureOrSequenceLong(id); 174 Tx tx = getPreparedTx(message.getTransactionId()); 175 tx.add(new AddMessageCommand() { 176 MessageStore messageStore; 177 @Override 178 public Message getMessage() { 179 return message; 180 } 181 182 @Override 183 public MessageStore getMessageStore() { 184 return messageStore; 185 } 186 187 @Override 188 public void run(ConnectionContext context) throws IOException { 189 ((JDBCPersistenceAdapter)persistenceAdapter).commitAdd(null, message.getMessageId()); 190 ((JDBCMessageStore)messageStore).onAdd(message, ((Long)message.getMessageId().getFutureOrSequenceLong()).longValue(), message.getPriority()); 191 } 192 193 @Override 194 public void setMessageStore(MessageStore messageStore) { 195 this.messageStore = messageStore; 196 } 197 198 }); 199 } 200 201 public void recoverAck(long id, byte[] xid, byte[] message) throws IOException { 202 Message msg = (Message) ((JDBCPersistenceAdapter)persistenceAdapter).getWireFormat().unmarshal(new ByteSequence(message)); 203 msg.getMessageId().setFutureOrSequenceLong(id); 204 Tx tx = getPreparedTx(new XATransactionId(xid)); 205 final MessageAck ack = new MessageAck(msg, MessageAck.STANDARD_ACK_TYPE, 1); 206 tx.add(new RemoveMessageCommand() { 207 @Override 208 public MessageAck getMessageAck() { 209 return ack; 210 } 211 212 @Override 213 public void run(ConnectionContext context) throws IOException { 214 ((JDBCPersistenceAdapter)persistenceAdapter).commitRemove(context, ack); 215 } 216 217 @Override 218 public MessageStore getMessageStore() { 219 return null; 220 } 221 222 }); 223 224 } 225 226 interface LastAckCommand extends RemoveMessageCommand { 227 void rollback(ConnectionContext context) throws IOException; 228 229 String getClientId(); 230 231 String getSubName(); 232 233 long getSequence(); 234 235 byte getPriority(); 236 237 void setMessageStore(JDBCTopicMessageStore jdbcTopicMessageStore); 238 } 239 240 public void recoverLastAck(byte[] encodedXid, final ActiveMQDestination destination, final String subName, final String clientId) throws IOException { 241 Tx tx = getPreparedTx(new XATransactionId(encodedXid)); 242 DataByteArrayInputStream inputStream = new DataByteArrayInputStream(encodedXid); 243 inputStream.skipBytes(1); // +|- 244 final long lastAck = inputStream.readLong(); 245 final byte priority = inputStream.readByte(); 246 final MessageAck ack = new MessageAck(); 247 ack.setDestination(destination); 248 tx.add(new LastAckCommand() { 249 JDBCTopicMessageStore jdbcTopicMessageStore; 250 251 @Override 252 public MessageAck getMessageAck() { 253 return ack; 254 } 255 256 @Override 257 public MessageStore getMessageStore() { 258 return jdbcTopicMessageStore; 259 } 260 261 @Override 262 public void run(ConnectionContext context) throws IOException { 263 ((JDBCPersistenceAdapter)persistenceAdapter).commitLastAck(context, lastAck, priority, destination, subName, clientId); 264 jdbcTopicMessageStore.complete(clientId, subName); 265 } 266 267 @Override 268 public void rollback(ConnectionContext context) throws IOException { 269 ((JDBCPersistenceAdapter)persistenceAdapter).rollbackLastAck(context, priority, jdbcTopicMessageStore.getDestination(), subName, clientId); 270 jdbcTopicMessageStore.complete(clientId, subName); 271 } 272 273 @Override 274 public String getClientId() { 275 return clientId; 276 } 277 278 @Override 279 public String getSubName() { 280 return subName; 281 } 282 283 @Override 284 public long getSequence() { 285 return lastAck; 286 } 287 288 @Override 289 public byte getPriority() { 290 return priority; 291 } 292 293 @Override 294 public void setMessageStore(JDBCTopicMessageStore jdbcTopicMessageStore) { 295 this.jdbcTopicMessageStore = jdbcTopicMessageStore; 296 } 297 }); 298 299 } 300 301 @Override 302 protected void onProxyTopicStore(ProxyTopicMessageStore proxyTopicMessageStore) { 303 topicStores.put(proxyTopicMessageStore.getDestination(), proxyTopicMessageStore.getDelegate()); 304 } 305 306 @Override 307 protected void onProxyQueueStore(ProxyMessageStore proxyQueueMessageStore) { 308 queueStores.put(proxyQueueMessageStore.getDestination(), proxyQueueMessageStore.getDelegate()); 309 } 310 311 @Override 312 protected void onRecovered(Tx tx) { 313 for (RemoveMessageCommand removeMessageCommand: tx.acks) { 314 if (removeMessageCommand instanceof LastAckCommand) { 315 LastAckCommand lastAckCommand = (LastAckCommand) removeMessageCommand; 316 JDBCTopicMessageStore jdbcTopicMessageStore = (JDBCTopicMessageStore) topicStores.get(lastAckCommand.getMessageAck().getDestination()); 317 jdbcTopicMessageStore.pendingCompletion(lastAckCommand.getClientId(), lastAckCommand.getSubName(), lastAckCommand.getSequence(), lastAckCommand.getPriority()); 318 lastAckCommand.setMessageStore(jdbcTopicMessageStore); 319 } else { 320 // when reading the store we ignore messages with non null XIDs but should include those with XIDS starting in - (pending acks in an xa transaction), 321 // but the sql is non portable to match BLOB with LIKE etc 322 // so we make up for it when we recover the ack 323 ((JDBCPersistenceAdapter)persistenceAdapter).getBrokerService().getRegionBroker().getDestinationMap().get(removeMessageCommand.getMessageAck().getDestination()).getDestinationStatistics().getMessages().increment(); 324 } 325 } 326 for (AddMessageCommand addMessageCommand : tx.messages) { 327 ActiveMQDestination destination = addMessageCommand.getMessage().getDestination(); 328 addMessageCommand.setMessageStore(destination.isQueue() ? queueStores.get(destination) : topicStores.get(destination)); 329 } 330 } 331 332 @Override 333 public void acknowledge(final TopicMessageStore topicMessageStore, final String clientId, final String subscriptionName, 334 final MessageId messageId, final MessageAck ack) throws IOException { 335 336 if (ack.isInTransaction()) { 337 Tx tx = getTx(ack.getTransactionId()); 338 tx.add(new LastAckCommand() { 339 public MessageAck getMessageAck() { 340 return ack; 341 } 342 343 public void run(ConnectionContext ctx) throws IOException { 344 topicMessageStore.acknowledge(ctx, clientId, subscriptionName, messageId, ack); 345 } 346 347 @Override 348 public MessageStore getMessageStore() { 349 return topicMessageStore; 350 } 351 352 @Override 353 public void rollback(ConnectionContext context) throws IOException { 354 JDBCTopicMessageStore jdbcTopicMessageStore = (JDBCTopicMessageStore)topicMessageStore; 355 ((JDBCPersistenceAdapter)persistenceAdapter).rollbackLastAck(context, 356 jdbcTopicMessageStore, 357 ack, 358 subscriptionName, clientId); 359 jdbcTopicMessageStore.complete(clientId, subscriptionName); 360 } 361 362 363 @Override 364 public String getClientId() { 365 return clientId; 366 } 367 368 @Override 369 public String getSubName() { 370 return subscriptionName; 371 } 372 373 @Override 374 public long getSequence() { 375 throw new IllegalStateException("Sequence id must be inferred from ack"); 376 } 377 378 @Override 379 public byte getPriority() { 380 throw new IllegalStateException("Priority must be inferred from ack or row"); 381 } 382 383 @Override 384 public void setMessageStore(JDBCTopicMessageStore jdbcTopicMessageStore) { 385 throw new IllegalStateException("message store already known!"); 386 } 387 }); 388 } else { 389 topicMessageStore.acknowledge(null, clientId, subscriptionName, messageId, ack); 390 } 391 } 392 393}