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