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 org.apache.activemq.ActiveMQMessageAudit; 020import org.apache.activemq.broker.ConnectionContext; 021import org.apache.activemq.command.ActiveMQDestination; 022import org.apache.activemq.command.Message; 023import org.apache.activemq.command.MessageAck; 024import org.apache.activemq.command.MessageId; 025import org.apache.activemq.command.XATransactionId; 026import org.apache.activemq.store.AbstractMessageStore; 027import org.apache.activemq.store.IndexListener; 028import org.apache.activemq.store.MessageRecoveryListener; 029import org.apache.activemq.util.ByteSequence; 030import org.apache.activemq.util.ByteSequenceData; 031import org.apache.activemq.util.IOExceptionSupport; 032import org.apache.activemq.wireformat.WireFormat; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036import java.io.IOException; 037import java.sql.SQLException; 038import java.util.Arrays; 039import java.util.LinkedList; 040 041/** 042 * 043 */ 044public class JDBCMessageStore extends AbstractMessageStore { 045 046 class Duration { 047 static final int LIMIT = 100; 048 final long start = System.currentTimeMillis(); 049 final String name; 050 051 Duration(String name) { 052 this.name = name; 053 } 054 void end() { 055 end(null); 056 } 057 void end(Object o) { 058 long duration = System.currentTimeMillis() - start; 059 060 if (duration > LIMIT) { 061 System.err.println(name + " took a long time: " + duration + "ms " + o); 062 } 063 } 064 } 065 private static final Logger LOG = LoggerFactory.getLogger(JDBCMessageStore.class); 066 protected final WireFormat wireFormat; 067 protected final JDBCAdapter adapter; 068 protected final JDBCPersistenceAdapter persistenceAdapter; 069 protected ActiveMQMessageAudit audit; 070 protected final LinkedList<Long> pendingAdditions = new LinkedList<Long>(); 071 final long[] perPriorityLastRecovered = new long[10]; 072 073 public JDBCMessageStore(JDBCPersistenceAdapter persistenceAdapter, JDBCAdapter adapter, WireFormat wireFormat, ActiveMQDestination destination, ActiveMQMessageAudit audit) throws IOException { 074 super(destination); 075 this.persistenceAdapter = persistenceAdapter; 076 this.adapter = adapter; 077 this.wireFormat = wireFormat; 078 this.audit = audit; 079 080 if (destination.isQueue() && persistenceAdapter.getBrokerService().shouldRecordVirtualDestination(destination)) { 081 recordDestinationCreation(destination); 082 } 083 resetBatching(); 084 } 085 086 private void recordDestinationCreation(ActiveMQDestination destination) throws IOException { 087 TransactionContext c = persistenceAdapter.getTransactionContext(); 088 try { 089 if (adapter.doGetLastAckedDurableSubscriberMessageId(c, destination, destination.getQualifiedName(), destination.getQualifiedName()) < 0) { 090 adapter.doRecordDestination(c, destination); 091 } 092 } catch (SQLException e) { 093 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 094 throw IOExceptionSupport.create("Failed to record destination: " + destination + ". Reason: " + e, e); 095 } finally { 096 c.close(); 097 } 098 } 099 100 @Override 101 public void addMessage(final ConnectionContext context, final Message message) throws IOException { 102 MessageId messageId = message.getMessageId(); 103 if (audit != null && audit.isDuplicate(message)) { 104 if (LOG.isDebugEnabled()) { 105 LOG.debug(destination.getPhysicalName() 106 + " ignoring duplicated (add) message, already stored: " 107 + messageId); 108 } 109 return; 110 } 111 112 // if xaXid present - this is a prepare - so we don't yet have an outcome 113 final XATransactionId xaXid = context != null ? context.getXid() : null; 114 115 // Serialize the Message.. 116 byte data[]; 117 try { 118 ByteSequence packet = wireFormat.marshal(message); 119 data = ByteSequenceData.toByteArray(packet); 120 } catch (IOException e) { 121 throw IOExceptionSupport.create("Failed to broker message: " + messageId + " in container: " + e, e); 122 } 123 124 // Get a connection and insert the message into the DB. 125 TransactionContext c = persistenceAdapter.getTransactionContext(context); 126 long sequenceId; 127 synchronized (pendingAdditions) { 128 sequenceId = persistenceAdapter.getNextSequenceId(); 129 final long sequence = sequenceId; 130 message.getMessageId().setEntryLocator(sequence); 131 132 if (xaXid == null) { 133 pendingAdditions.add(sequence); 134 135 c.onCompletion(new Runnable() { 136 @Override 137 public void run() { 138 // jdbc close or jms commit - while futureOrSequenceLong==null ordered 139 // work will remain pending on the Queue 140 message.getMessageId().setFutureOrSequenceLong(sequence); 141 } 142 }); 143 144 if (indexListener != null) { 145 indexListener.onAdd(new IndexListener.MessageContext(context, message, new Runnable() { 146 @Override 147 public void run() { 148 // cursor add complete 149 synchronized (pendingAdditions) { pendingAdditions.remove(sequence); } 150 } 151 })); 152 } else { 153 pendingAdditions.remove(sequence); 154 } 155 } 156 } 157 try { 158 adapter.doAddMessage(c, sequenceId, messageId, destination, data, message.getExpiration(), 159 this.isPrioritizedMessages() ? message.getPriority() : 0, xaXid); 160 } catch (SQLException e) { 161 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 162 throw IOExceptionSupport.create("Failed to broker message: " + messageId + " in container: " + e, e); 163 } finally { 164 c.close(); 165 } 166 if (xaXid == null) { 167 onAdd(message, sequenceId, message.getPriority()); 168 } 169 } 170 171 // jdbc commit order is random with concurrent connections - limit scan to lowest pending 172 private long minPendingSequeunceId() { 173 synchronized (pendingAdditions) { 174 if (!pendingAdditions.isEmpty()) { 175 return pendingAdditions.get(0); 176 } else { 177 // nothing pending, ensure scan is limited to current state 178 return persistenceAdapter.sequenceGenerator.getLastSequenceId() + 1; 179 } 180 } 181 } 182 183 @Override 184 public void updateMessage(Message message) throws IOException { 185 TransactionContext c = persistenceAdapter.getTransactionContext(); 186 try { 187 adapter.doUpdateMessage(c, destination, message.getMessageId(), ByteSequenceData.toByteArray(wireFormat.marshal(message))); 188 } catch (SQLException e) { 189 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 190 throw IOExceptionSupport.create("Failed to update message: " + message.getMessageId() + " in container: " + e, e); 191 } finally { 192 c.close(); 193 } 194 } 195 196 protected void onAdd(Message message, long sequenceId, byte priority) {} 197 198 public void addMessageReference(ConnectionContext context, MessageId messageId, long expirationTime, String messageRef) throws IOException { 199 // Get a connection and insert the message into the DB. 200 TransactionContext c = persistenceAdapter.getTransactionContext(context); 201 try { 202 adapter.doAddMessageReference(c, persistenceAdapter.getNextSequenceId(), messageId, destination, expirationTime, messageRef); 203 } catch (SQLException e) { 204 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 205 throw IOExceptionSupport.create("Failed to broker message: " + messageId + " in container: " + e, e); 206 } finally { 207 c.close(); 208 } 209 } 210 211 @Override 212 public Message getMessage(MessageId messageId) throws IOException { 213 // Get a connection and pull the message out of the DB 214 TransactionContext c = persistenceAdapter.getTransactionContext(); 215 try { 216 byte data[] = adapter.doGetMessage(c, messageId); 217 if (data == null) { 218 return null; 219 } 220 221 Message answer = (Message)wireFormat.unmarshal(new ByteSequence(data)); 222 return answer; 223 } catch (IOException e) { 224 throw IOExceptionSupport.create("Failed to broker message: " + messageId + " in container: " + e, e); 225 } catch (SQLException e) { 226 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 227 throw IOExceptionSupport.create("Failed to broker message: " + messageId + " in container: " + e, e); 228 } finally { 229 c.close(); 230 } 231 } 232 233 public String getMessageReference(MessageId messageId) throws IOException { 234 long id = messageId.getBrokerSequenceId(); 235 236 // Get a connection and pull the message out of the DB 237 TransactionContext c = persistenceAdapter.getTransactionContext(); 238 try { 239 return adapter.doGetMessageReference(c, id); 240 } catch (IOException e) { 241 throw IOExceptionSupport.create("Failed to broker message: " + messageId + " in container: " + e, e); 242 } catch (SQLException e) { 243 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 244 throw IOExceptionSupport.create("Failed to broker message: " + messageId + " in container: " + e, e); 245 } finally { 246 c.close(); 247 } 248 } 249 250 @Override 251 public void removeMessage(ConnectionContext context, MessageAck ack) throws IOException { 252 253 long seq = ack.getLastMessageId().getFutureOrSequenceLong() != null ? 254 (Long) ack.getLastMessageId().getFutureOrSequenceLong() : 255 persistenceAdapter.getStoreSequenceIdForMessageId(context, ack.getLastMessageId(), destination)[0]; 256 257 // Get a connection and remove the message from the DB 258 TransactionContext c = persistenceAdapter.getTransactionContext(context); 259 try { 260 adapter.doRemoveMessage(c, seq, context != null ? context.getXid() : null); 261 } catch (SQLException e) { 262 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 263 throw IOExceptionSupport.create("Failed to broker message: " + ack.getLastMessageId() + " in container: " + e, e); 264 } finally { 265 c.close(); 266 } 267 } 268 269 @Override 270 public void recover(final MessageRecoveryListener listener) throws Exception { 271 272 // Get all the Message ids out of the database. 273 TransactionContext c = persistenceAdapter.getTransactionContext(); 274 try { 275 adapter.doRecover(c, destination, new JDBCMessageRecoveryListener() { 276 @Override 277 public boolean recoverMessage(long sequenceId, byte[] data) throws Exception { 278 if (listener.hasSpace()) { 279 Message msg = (Message) wireFormat.unmarshal(new ByteSequence(data)); 280 msg.getMessageId().setBrokerSequenceId(sequenceId); 281 return listener.recoverMessage(msg); 282 } else { 283 if (LOG.isTraceEnabled()) { 284 LOG.trace("Message recovery limit reached for MessageRecoveryListener"); 285 } 286 return false; 287 } 288 } 289 290 @Override 291 public boolean recoverMessageReference(String reference) throws Exception { 292 if (listener.hasSpace()) { 293 return listener.recoverMessageReference(new MessageId(reference)); 294 } else { 295 if (LOG.isTraceEnabled()) { 296 LOG.trace("Message recovery limit reached for MessageRecoveryListener"); 297 } 298 return false; 299 } 300 } 301 }); 302 } catch (SQLException e) { 303 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 304 throw IOExceptionSupport.create("Failed to recover container. Reason: " + e, e); 305 } finally { 306 c.close(); 307 } 308 } 309 310 /** 311 * @see org.apache.activemq.store.MessageStore#removeAllMessages(ConnectionContext) 312 */ 313 @Override 314 public void removeAllMessages(ConnectionContext context) throws IOException { 315 // Get a connection and remove the message from the DB 316 TransactionContext c = persistenceAdapter.getTransactionContext(context); 317 try { 318 adapter.doRemoveAllMessages(c, destination); 319 } catch (SQLException e) { 320 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 321 throw IOExceptionSupport.create("Failed to broker remove all messages: " + e, e); 322 } finally { 323 c.close(); 324 } 325 } 326 327 @Override 328 public int getMessageCount() throws IOException { 329 int result = 0; 330 TransactionContext c = persistenceAdapter.getTransactionContext(); 331 try { 332 333 result = adapter.doGetMessageCount(c, destination); 334 335 } catch (SQLException e) { 336 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 337 throw IOExceptionSupport.create("Failed to get Message Count: " + destination + ". Reason: " + e, e); 338 } finally { 339 c.close(); 340 } 341 return result; 342 } 343 344 /** 345 * @param maxReturned 346 * @param listener 347 * @throws Exception 348 * @see org.apache.activemq.store.MessageStore#recoverNextMessages(int, 349 * org.apache.activemq.store.MessageRecoveryListener) 350 */ 351 @Override 352 public void recoverNextMessages(int maxReturned, final MessageRecoveryListener listener) throws Exception { 353 TransactionContext c = persistenceAdapter.getTransactionContext(); 354 try { 355 if (LOG.isTraceEnabled()) { 356 LOG.trace(this + " recoverNext lastRecovered:" + Arrays.toString(perPriorityLastRecovered) + ", minPending:" + minPendingSequeunceId()); 357 } 358 adapter.doRecoverNextMessages(c, destination, perPriorityLastRecovered, minPendingSequeunceId(), 359 maxReturned, isPrioritizedMessages(), new JDBCMessageRecoveryListener() { 360 361 @Override 362 public boolean recoverMessage(long sequenceId, byte[] data) throws Exception { 363 Message msg = (Message)wireFormat.unmarshal(new ByteSequence(data)); 364 msg.getMessageId().setBrokerSequenceId(sequenceId); 365 msg.getMessageId().setFutureOrSequenceLong(sequenceId); 366 listener.recoverMessage(msg); 367 trackLastRecovered(sequenceId, msg.getPriority()); 368 return true; 369 } 370 371 @Override 372 public boolean recoverMessageReference(String reference) throws Exception { 373 if (listener.hasSpace()) { 374 listener.recoverMessageReference(new MessageId(reference)); 375 return true; 376 } 377 return false; 378 } 379 380 }); 381 } catch (SQLException e) { 382 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 383 } finally { 384 c.close(); 385 } 386 387 } 388 389 private void trackLastRecovered(long sequenceId, int priority) { 390 perPriorityLastRecovered[isPrioritizedMessages() ? priority : 0] = sequenceId; 391 } 392 393 /** 394 * @see org.apache.activemq.store.MessageStore#resetBatching() 395 */ 396 @Override 397 public void resetBatching() { 398 if (LOG.isTraceEnabled()) { 399 LOG.trace(this + " resetBatching. last recovered: " + Arrays.toString(perPriorityLastRecovered)); 400 } 401 setLastRecovered(-1); 402 } 403 404 private void setLastRecovered(long val) { 405 for (int i=0;i<perPriorityLastRecovered.length;i++) { 406 perPriorityLastRecovered[i] = val; 407 } 408 } 409 410 411 @Override 412 public void setBatch(MessageId messageId) { 413 if (LOG.isTraceEnabled()) { 414 LOG.trace(this + " setBatch: last recovered: " + Arrays.toString(perPriorityLastRecovered)); 415 } 416 try { 417 long[] storedValues = persistenceAdapter.getStoreSequenceIdForMessageId(null, messageId, destination); 418 setLastRecovered(storedValues[0]); 419 } catch (IOException ignoredAsAlreadyLogged) { 420 resetBatching(); 421 } 422 if (LOG.isTraceEnabled()) { 423 LOG.trace(this + " setBatch: new last recovered: " + Arrays.toString(perPriorityLastRecovered)); 424 } 425 } 426 427 428 @Override 429 public void setPrioritizedMessages(boolean prioritizedMessages) { 430 super.setPrioritizedMessages(prioritizedMessages); 431 } 432 433 @Override 434 public String toString() { 435 return destination.getPhysicalName() + ",pendingSize:" + pendingAdditions.size(); 436 } 437 438}