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.File; 020import java.io.IOException; 021import java.sql.Connection; 022import java.sql.SQLException; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.Locale; 026import java.util.Set; 027import java.util.concurrent.ScheduledFuture; 028import java.util.concurrent.ScheduledThreadPoolExecutor; 029import java.util.concurrent.ThreadFactory; 030import java.util.concurrent.TimeUnit; 031 032import javax.sql.DataSource; 033 034import org.apache.activemq.ActiveMQMessageAudit; 035import org.apache.activemq.broker.BrokerService; 036import org.apache.activemq.broker.ConnectionContext; 037import org.apache.activemq.broker.Locker; 038import org.apache.activemq.broker.scheduler.JobSchedulerStore; 039import org.apache.activemq.command.ActiveMQDestination; 040import org.apache.activemq.command.ActiveMQQueue; 041import org.apache.activemq.command.ActiveMQTopic; 042import org.apache.activemq.command.Message; 043import org.apache.activemq.command.MessageAck; 044import org.apache.activemq.command.MessageId; 045import org.apache.activemq.command.ProducerId; 046import org.apache.activemq.openwire.OpenWireFormat; 047import org.apache.activemq.store.MessageStore; 048import org.apache.activemq.store.PersistenceAdapter; 049import org.apache.activemq.store.TopicMessageStore; 050import org.apache.activemq.store.TransactionStore; 051import org.apache.activemq.store.jdbc.adapter.DefaultJDBCAdapter; 052import org.apache.activemq.store.memory.MemoryTransactionStore; 053import org.apache.activemq.usage.SystemUsage; 054import org.apache.activemq.util.ByteSequence; 055import org.apache.activemq.util.FactoryFinder; 056import org.apache.activemq.util.IOExceptionSupport; 057import org.apache.activemq.util.LongSequenceGenerator; 058import org.apache.activemq.util.ServiceStopper; 059import org.apache.activemq.util.ThreadPoolUtils; 060import org.apache.activemq.wireformat.WireFormat; 061import org.slf4j.Logger; 062import org.slf4j.LoggerFactory; 063 064/** 065 * A {@link PersistenceAdapter} implementation using JDBC for persistence 066 * storage. 067 * 068 * This persistence adapter will correctly remember prepared XA transactions, 069 * but it will not keep track of local transaction commits so that operations 070 * performed against the Message store are done as a single uow. 071 * 072 * @org.apache.xbean.XBean element="jdbcPersistenceAdapter" 073 * 074 */ 075public class JDBCPersistenceAdapter extends DataSourceServiceSupport implements PersistenceAdapter { 076 077 private static final Logger LOG = LoggerFactory.getLogger(JDBCPersistenceAdapter.class); 078 private static FactoryFinder adapterFactoryFinder = new FactoryFinder( 079 "META-INF/services/org/apache/activemq/store/jdbc/"); 080 private static FactoryFinder lockFactoryFinder = new FactoryFinder( 081 "META-INF/services/org/apache/activemq/store/jdbc/lock/"); 082 083 public static final long DEFAULT_LOCK_KEEP_ALIVE_PERIOD = 30 * 1000; 084 085 private WireFormat wireFormat = new OpenWireFormat(); 086 private Statements statements; 087 private JDBCAdapter adapter; 088 private final JdbcMemoryTransactionStore transactionStore = new JdbcMemoryTransactionStore(this); 089 private ScheduledFuture<?> cleanupTicket; 090 private int cleanupPeriod = 1000 * 60 * 5; 091 private boolean useExternalMessageReferences; 092 private boolean createTablesOnStartup = true; 093 private DataSource lockDataSource; 094 private int transactionIsolation; 095 private File directory; 096 private boolean changeAutoCommitAllowed = true; 097 private int queryTimeout = -1; 098 private int networkTimeout = -1; 099 100 protected int maxProducersToAudit=1024; 101 protected int maxAuditDepth=1000; 102 protected boolean enableAudit=false; 103 protected int auditRecoveryDepth = 1024; 104 protected ActiveMQMessageAudit audit; 105 106 protected LongSequenceGenerator sequenceGenerator = new LongSequenceGenerator(); 107 protected int maxRows = DefaultJDBCAdapter.MAX_ROWS; 108 protected final HashMap<ActiveMQDestination, MessageStore> storeCache = new HashMap<>(); 109 110 { 111 setLockKeepAlivePeriod(DEFAULT_LOCK_KEEP_ALIVE_PERIOD); 112 } 113 114 public JDBCPersistenceAdapter() { 115 } 116 117 public JDBCPersistenceAdapter(DataSource ds, WireFormat wireFormat) { 118 super(ds); 119 this.wireFormat = wireFormat; 120 } 121 122 @Override 123 public Set<ActiveMQDestination> getDestinations() { 124 TransactionContext c = null; 125 try { 126 c = getTransactionContext(); 127 return getAdapter().doGetDestinations(c); 128 } catch (IOException e) { 129 return emptyDestinationSet(); 130 } catch (SQLException e) { 131 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 132 return emptyDestinationSet(); 133 } finally { 134 if (c != null) { 135 try { 136 c.close(); 137 } catch (Throwable e) { 138 } 139 } 140 } 141 } 142 143 @SuppressWarnings("unchecked") 144 private Set<ActiveMQDestination> emptyDestinationSet() { 145 return Collections.EMPTY_SET; 146 } 147 148 protected void createMessageAudit() { 149 if (enableAudit && audit == null) { 150 audit = new ActiveMQMessageAudit(maxAuditDepth,maxProducersToAudit); 151 TransactionContext c = null; 152 153 try { 154 c = getTransactionContext(); 155 getAdapter().doMessageIdScan(c, auditRecoveryDepth, new JDBCMessageIdScanListener() { 156 @Override 157 public void messageId(MessageId id) { 158 audit.isDuplicate(id); 159 } 160 }); 161 } catch (Exception e) { 162 LOG.error("Failed to reload store message audit for JDBC persistence adapter", e); 163 } finally { 164 if (c != null) { 165 try { 166 c.close(); 167 } catch (Throwable e) { 168 } 169 } 170 } 171 } 172 } 173 174 public void initSequenceIdGenerator() { 175 TransactionContext c = null; 176 try { 177 c = getTransactionContext(); 178 getAdapter().doMessageIdScan(c, auditRecoveryDepth, new JDBCMessageIdScanListener() { 179 @Override 180 public void messageId(MessageId id) { 181 audit.isDuplicate(id); 182 } 183 }); 184 } catch (Exception e) { 185 LOG.error("Failed to reload store message audit for JDBC persistence adapter", e); 186 } finally { 187 if (c != null) { 188 try { 189 c.close(); 190 } catch (Throwable e) { 191 } 192 } 193 } 194 } 195 196 @Override 197 public MessageStore createQueueMessageStore(ActiveMQQueue destination) throws IOException { 198 MessageStore rc = storeCache.get(destination); 199 if (rc == null) { 200 MessageStore store = transactionStore.proxy(new JDBCMessageStore(this, getAdapter(), wireFormat, destination, audit)); 201 rc = storeCache.putIfAbsent(destination, store); 202 if (rc == null) { 203 rc = store; 204 } 205 } 206 return rc; 207 } 208 209 @Override 210 public TopicMessageStore createTopicMessageStore(ActiveMQTopic destination) throws IOException { 211 TopicMessageStore rc = (TopicMessageStore) storeCache.get(destination); 212 if (rc == null) { 213 TopicMessageStore store = transactionStore.proxy(new JDBCTopicMessageStore(this, getAdapter(), wireFormat, destination, audit)); 214 rc = (TopicMessageStore) storeCache.putIfAbsent(destination, store); 215 if (rc == null) { 216 rc = store; 217 } 218 } 219 return rc; 220 } 221 222 /** 223 * Cleanup method to remove any state associated with the given destination 224 * @param destination Destination to forget 225 */ 226 @Override 227 public void removeQueueMessageStore(ActiveMQQueue destination) { 228 if (destination.isQueue() && getBrokerService().shouldRecordVirtualDestination(destination)) { 229 try { 230 removeConsumerDestination(destination); 231 } catch (IOException ioe) { 232 LOG.error("Failed to remove consumer destination: " + destination, ioe); 233 } 234 } 235 storeCache.remove(destination); 236 } 237 238 private void removeConsumerDestination(ActiveMQQueue destination) throws IOException { 239 TransactionContext c = getTransactionContext(); 240 try { 241 String id = destination.getQualifiedName(); 242 getAdapter().doDeleteSubscription(c, destination, id, id); 243 } catch (SQLException e) { 244 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 245 throw IOExceptionSupport.create("Failed to remove consumer destination: " + destination, e); 246 } finally { 247 c.close(); 248 } 249 } 250 251 /** 252 * Cleanup method to remove any state associated with the given destination 253 * No state retained.... nothing to do 254 * 255 * @param destination Destination to forget 256 */ 257 @Override 258 public void removeTopicMessageStore(ActiveMQTopic destination) { 259 storeCache.remove(destination); 260 } 261 262 @Override 263 public TransactionStore createTransactionStore() throws IOException { 264 return this.transactionStore; 265 } 266 267 @Override 268 public long getLastMessageBrokerSequenceId() throws IOException { 269 TransactionContext c = getTransactionContext(); 270 try { 271 long seq = getAdapter().doGetLastMessageStoreSequenceId(c); 272 sequenceGenerator.setLastSequenceId(seq); 273 long brokerSeq = 0; 274 if (seq != 0) { 275 byte[] msg = getAdapter().doGetMessageById(c, seq); 276 if (msg != null) { 277 Message last = (Message)wireFormat.unmarshal(new ByteSequence(msg)); 278 brokerSeq = last.getMessageId().getBrokerSequenceId(); 279 } else { 280 LOG.warn("Broker sequence id wasn't recovered properly, possible duplicates!"); 281 } 282 } 283 return brokerSeq; 284 } catch (SQLException e) { 285 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 286 throw IOExceptionSupport.create("Failed to get last broker message id: " + e, e); 287 } finally { 288 c.close(); 289 } 290 } 291 292 @Override 293 public long getLastProducerSequenceId(ProducerId id) throws IOException { 294 TransactionContext c = getTransactionContext(); 295 try { 296 return getAdapter().doGetLastProducerSequenceId(c, id); 297 } catch (SQLException e) { 298 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 299 throw IOExceptionSupport.create("Failed to get last broker message id: " + e, e); 300 } finally { 301 c.close(); 302 } 303 } 304 305 @Override 306 public void allowIOResumption() {} 307 308 @Override 309 public void init() throws Exception { 310 getAdapter().setUseExternalMessageReferences(isUseExternalMessageReferences()); 311 312 if (isCreateTablesOnStartup()) { 313 TransactionContext transactionContext = getTransactionContext(); 314 transactionContext.getExclusiveConnection(); 315 transactionContext.begin(); 316 try { 317 try { 318 getAdapter().doCreateTables(transactionContext); 319 } catch (SQLException e) { 320 LOG.warn("Cannot create tables due to: " + e); 321 JDBCPersistenceAdapter.log("Failure Details: ", e); 322 } 323 } finally { 324 transactionContext.commit(); 325 } 326 } 327 } 328 329 @Override 330 public void doStart() throws Exception { 331 332 if( brokerService!=null ) { 333 wireFormat.setVersion(brokerService.getStoreOpenWireVersion()); 334 } 335 336 // Cleanup the db periodically. 337 if (cleanupPeriod > 0) { 338 cleanupTicket = getScheduledThreadPoolExecutor().scheduleWithFixedDelay(new Runnable() { 339 @Override 340 public void run() { 341 cleanup(); 342 } 343 }, 0, cleanupPeriod, TimeUnit.MILLISECONDS); 344 } 345 createMessageAudit(); 346 } 347 348 @Override 349 public synchronized void doStop(ServiceStopper stopper) throws Exception { 350 if (cleanupTicket != null) { 351 cleanupTicket.cancel(true); 352 cleanupTicket = null; 353 } 354 closeDataSource(getDataSource()); 355 } 356 357 public void cleanup() { 358 TransactionContext c = null; 359 try { 360 LOG.debug("Cleaning up old messages."); 361 c = getTransactionContext(); 362 c.getExclusiveConnection(); 363 getAdapter().doDeleteOldMessages(c); 364 } catch (IOException e) { 365 LOG.warn("Old message cleanup failed due to: " + e, e); 366 } catch (SQLException e) { 367 LOG.warn("Old message cleanup failed due to: " + e); 368 JDBCPersistenceAdapter.log("Failure Details: ", e); 369 } finally { 370 if (c != null) { 371 try { 372 c.close(); 373 } catch (Throwable e) { 374 } 375 } 376 LOG.debug("Cleanup done."); 377 } 378 } 379 380 @Override 381 public ScheduledThreadPoolExecutor getScheduledThreadPoolExecutor() { 382 if (clockDaemon == null) { 383 clockDaemon = new ScheduledThreadPoolExecutor(5, new ThreadFactory() { 384 @Override 385 public Thread newThread(Runnable runnable) { 386 Thread thread = new Thread(runnable, "ActiveMQ JDBC PA Scheduled Task"); 387 thread.setDaemon(true); 388 return thread; 389 } 390 }); 391 } 392 return clockDaemon; 393 } 394 395 public JDBCAdapter getAdapter() throws IOException { 396 if (adapter == null) { 397 setAdapter(createAdapter()); 398 } 399 return adapter; 400 } 401 402 /** 403 * @deprecated as of 5.7.0, replaced by {@link #getLocker()} 404 */ 405 @Deprecated 406 public Locker getDatabaseLocker() throws IOException { 407 return getLocker(); 408 } 409 410 /** 411 * Sets the database locker strategy to use to lock the database on startup 412 * @throws IOException 413 * 414 * @deprecated as of 5.7.0, replaced by {@link #setLocker(org.apache.activemq.broker.Locker)} 415 */ 416 @Deprecated 417 public void setDatabaseLocker(Locker locker) throws IOException { 418 setLocker(locker); 419 } 420 421 public DataSource getLockDataSource() throws IOException { 422 if (lockDataSource == null) { 423 lockDataSource = getDataSource(); 424 if (lockDataSource == null) { 425 throw new IllegalArgumentException( 426 "No dataSource property has been configured"); 427 } 428 } 429 return lockDataSource; 430 } 431 432 public void setLockDataSource(DataSource dataSource) { 433 this.lockDataSource = dataSource; 434 LOG.info("Using a separate dataSource for locking: " 435 + lockDataSource); 436 } 437 438 @Override 439 public BrokerService getBrokerService() { 440 return brokerService; 441 } 442 443 /** 444 * @throws IOException 445 */ 446 protected JDBCAdapter createAdapter() throws IOException { 447 448 adapter = (JDBCAdapter) loadAdapter(adapterFactoryFinder, "adapter"); 449 450 // Use the default JDBC adapter if the 451 // Database type is not recognized. 452 if (adapter == null) { 453 adapter = new DefaultJDBCAdapter(); 454 LOG.debug("Using default JDBC Adapter: " + adapter); 455 } 456 return adapter; 457 } 458 459 private Object loadAdapter(FactoryFinder finder, String kind) throws IOException { 460 Object adapter = null; 461 TransactionContext c = getTransactionContext(); 462 try { 463 try { 464 // Make the filename file system safe. 465 String dirverName = c.getConnection().getMetaData().getDriverName(); 466 dirverName = dirverName.replaceAll("[^a-zA-Z0-9\\-]", "_").toLowerCase(Locale.ENGLISH); 467 468 try { 469 adapter = finder.newInstance(dirverName); 470 LOG.info("Database " + kind + " driver override recognized for : [" + dirverName + "] - adapter: " + adapter.getClass()); 471 } catch (Throwable e) { 472 LOG.info("Database " + kind + " driver override not found for : [" + dirverName 473 + "]. Will use default implementation."); 474 } 475 } catch (SQLException e) { 476 LOG.warn("JDBC error occurred while trying to detect database type for overrides. Will use default implementations: " 477 + e.getMessage()); 478 JDBCPersistenceAdapter.log("Failure Details: ", e); 479 } 480 } finally { 481 c.close(); 482 } 483 return adapter; 484 } 485 486 public void setAdapter(JDBCAdapter adapter) { 487 this.adapter = adapter; 488 this.adapter.setStatements(getStatements()); 489 this.adapter.setMaxRows(getMaxRows()); 490 } 491 492 public WireFormat getWireFormat() { 493 return wireFormat; 494 } 495 496 public void setWireFormat(WireFormat wireFormat) { 497 this.wireFormat = wireFormat; 498 } 499 500 public TransactionContext getTransactionContext(ConnectionContext context) throws IOException { 501 if (context == null || isBrokerContext(context)) { 502 return getTransactionContext(); 503 } else { 504 TransactionContext answer = (TransactionContext)context.getLongTermStoreContext(); 505 if (answer == null) { 506 answer = getTransactionContext(); 507 context.setLongTermStoreContext(answer); 508 } 509 return answer; 510 } 511 } 512 513 private boolean isBrokerContext(ConnectionContext context) { 514 return context.getSecurityContext() != null && context.getSecurityContext().isBrokerContext(); 515 } 516 517 public TransactionContext getTransactionContext() throws IOException { 518 TransactionContext answer = new TransactionContext(this, networkTimeout, queryTimeout); 519 if (transactionIsolation > 0) { 520 answer.setTransactionIsolation(transactionIsolation); 521 } 522 return answer; 523 } 524 525 @Override 526 public void beginTransaction(ConnectionContext context) throws IOException { 527 TransactionContext transactionContext = getTransactionContext(context); 528 transactionContext.begin(); 529 } 530 531 @Override 532 public void commitTransaction(ConnectionContext context) throws IOException { 533 TransactionContext transactionContext = getTransactionContext(context); 534 transactionContext.commit(); 535 } 536 537 @Override 538 public void rollbackTransaction(ConnectionContext context) throws IOException { 539 TransactionContext transactionContext = getTransactionContext(context); 540 transactionContext.rollback(); 541 } 542 543 public int getCleanupPeriod() { 544 return cleanupPeriod; 545 } 546 547 /** 548 * Sets the number of milliseconds until the database is attempted to be 549 * cleaned up for durable topics 550 */ 551 public void setCleanupPeriod(int cleanupPeriod) { 552 this.cleanupPeriod = cleanupPeriod; 553 } 554 555 public boolean isChangeAutoCommitAllowed() { 556 return changeAutoCommitAllowed; 557 } 558 559 /** 560 * Whether the JDBC driver allows to set the auto commit. 561 * Some drivers does not allow changing the auto commit. The default value is true. 562 * 563 * @param changeAutoCommitAllowed true to change, false to not change. 564 */ 565 public void setChangeAutoCommitAllowed(boolean changeAutoCommitAllowed) { 566 this.changeAutoCommitAllowed = changeAutoCommitAllowed; 567 } 568 569 public int getNetworkTimeout() { 570 return networkTimeout; 571 } 572 573 /** 574 * Define the JDBC connection network timeout. 575 * 576 * @param networkTimeout the connection network timeout (in milliseconds). 577 */ 578 public void setNetworkTimeout(int networkTimeout) { 579 this.networkTimeout = networkTimeout; 580 } 581 582 public int getQueryTimeout() { 583 return queryTimeout; 584 } 585 586 /** 587 * Define the JDBC statement query timeout. 588 * 589 * @param queryTimeout the statement query timeout (in seconds). 590 */ 591 public void setQueryTimeout(int queryTimeout) { 592 this.queryTimeout = queryTimeout; 593 } 594 595 @Override 596 public void deleteAllMessages() throws IOException { 597 TransactionContext c = getTransactionContext(); 598 c.getExclusiveConnection(); 599 try { 600 getAdapter().doDropTables(c); 601 getAdapter().setUseExternalMessageReferences(isUseExternalMessageReferences()); 602 getAdapter().doCreateTables(c); 603 LOG.info("Persistence store purged."); 604 } catch (SQLException e) { 605 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 606 throw IOExceptionSupport.create(e); 607 } finally { 608 c.close(); 609 } 610 } 611 612 public boolean isUseExternalMessageReferences() { 613 return useExternalMessageReferences; 614 } 615 616 public void setUseExternalMessageReferences(boolean useExternalMessageReferences) { 617 this.useExternalMessageReferences = useExternalMessageReferences; 618 } 619 620 public boolean isCreateTablesOnStartup() { 621 return createTablesOnStartup; 622 } 623 624 /** 625 * Sets whether or not tables are created on startup 626 */ 627 public void setCreateTablesOnStartup(boolean createTablesOnStartup) { 628 this.createTablesOnStartup = createTablesOnStartup; 629 } 630 631 /** 632 * @deprecated use {@link #setUseLock(boolean)} instead 633 * 634 * Sets whether or not an exclusive database lock should be used to enable 635 * JDBC Master/Slave. Enabled by default. 636 */ 637 @Deprecated 638 public void setUseDatabaseLock(boolean useDatabaseLock) { 639 setUseLock(useDatabaseLock); 640 } 641 642 public static void log(String msg, SQLException e) { 643 String s = msg + e.getMessage(); 644 while (e.getNextException() != null) { 645 e = e.getNextException(); 646 s += ", due to: " + e.getMessage(); 647 } 648 LOG.warn(s, e); 649 } 650 651 public Statements getStatements() { 652 if (statements == null) { 653 statements = new Statements(); 654 } 655 return statements; 656 } 657 658 public void setStatements(Statements statements) { 659 this.statements = statements; 660 if (adapter != null) { 661 this.adapter.setStatements(getStatements()); 662 } 663 } 664 665 /** 666 * @param usageManager The UsageManager that is controlling the 667 * destination's memory usage. 668 */ 669 @Override 670 public void setUsageManager(SystemUsage usageManager) { 671 } 672 673 @Override 674 public Locker createDefaultLocker() throws IOException { 675 Locker locker = (Locker) loadAdapter(lockFactoryFinder, "lock"); 676 if (locker == null) { 677 locker = new DefaultDatabaseLocker(); 678 LOG.debug("Using default JDBC Locker: " + locker); 679 } 680 locker.configure(this); 681 return locker; 682 } 683 684 @Override 685 public void setBrokerName(String brokerName) { 686 } 687 688 @Override 689 public String toString() { 690 return "JDBCPersistenceAdapter(" + super.toString() + ")"; 691 } 692 693 @Override 694 public void setDirectory(File dir) { 695 this.directory=dir; 696 } 697 698 @Override 699 public File getDirectory(){ 700 if (this.directory==null && brokerService != null){ 701 this.directory=brokerService.getBrokerDataDirectory(); 702 } 703 return this.directory; 704 } 705 706 // interesting bit here is proof that DB is ok 707 @Override 708 public void checkpoint(boolean sync) throws IOException { 709 // by pass TransactionContext to avoid IO Exception handler 710 Connection connection = null; 711 try { 712 connection = getDataSource().getConnection(); 713 if (!connection.isValid(10)) { 714 throw new IOException("isValid(10) failed for: " + connection); 715 } 716 } catch (SQLException e) { 717 LOG.debug("Could not get JDBC connection for checkpoint: " + e, e); 718 throw IOExceptionSupport.create(e); 719 } finally { 720 if (connection != null) { 721 try { 722 connection.close(); 723 } catch (Throwable ignored) { 724 } 725 } 726 } 727 } 728 729 @Override 730 public long size(){ 731 return 0; 732 } 733 734 /** 735 * @deprecated use {@link Locker#setLockAcquireSleepInterval(long)} instead 736 * 737 * millisecond interval between lock acquire attempts, applied to newly created DefaultDatabaseLocker 738 * not applied if DataBaseLocker is injected. 739 * 740 */ 741 @Deprecated 742 public void setLockAcquireSleepInterval(long lockAcquireSleepInterval) throws IOException { 743 getLocker().setLockAcquireSleepInterval(lockAcquireSleepInterval); 744 } 745 746 /** 747 * set the Transaction isolation level to something other that TRANSACTION_READ_UNCOMMITTED 748 * This allowable dirty isolation level may not be achievable in clustered DB environments 749 * so a more restrictive and expensive option may be needed like TRANSACTION_REPEATABLE_READ 750 * see isolation level constants in {@link java.sql.Connection} 751 * @param transactionIsolation the isolation level to use 752 */ 753 public void setTransactionIsolation(int transactionIsolation) { 754 this.transactionIsolation = transactionIsolation; 755 } 756 757 public int getMaxProducersToAudit() { 758 return maxProducersToAudit; 759 } 760 761 public void setMaxProducersToAudit(int maxProducersToAudit) { 762 this.maxProducersToAudit = maxProducersToAudit; 763 } 764 765 public int getMaxAuditDepth() { 766 return maxAuditDepth; 767 } 768 769 public void setMaxAuditDepth(int maxAuditDepth) { 770 this.maxAuditDepth = maxAuditDepth; 771 } 772 773 public boolean isEnableAudit() { 774 return enableAudit; 775 } 776 777 public void setEnableAudit(boolean enableAudit) { 778 this.enableAudit = enableAudit; 779 } 780 781 public int getAuditRecoveryDepth() { 782 return auditRecoveryDepth; 783 } 784 785 public void setAuditRecoveryDepth(int auditRecoveryDepth) { 786 this.auditRecoveryDepth = auditRecoveryDepth; 787 } 788 789 public long getNextSequenceId() { 790 return sequenceGenerator.getNextSequenceId(); 791 } 792 793 public int getMaxRows() { 794 return maxRows; 795 } 796 797 /* 798 * the max rows return from queries, with sparse selectors this may need to be increased 799 */ 800 public void setMaxRows(int maxRows) { 801 this.maxRows = maxRows; 802 } 803 804 public void recover(JdbcMemoryTransactionStore jdbcMemoryTransactionStore) throws IOException { 805 TransactionContext c = getTransactionContext(); 806 try { 807 getAdapter().doRecoverPreparedOps(c, jdbcMemoryTransactionStore); 808 } catch (SQLException e) { 809 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 810 throw IOExceptionSupport.create("Failed to recover from: " + jdbcMemoryTransactionStore + ". Reason: " + e,e); 811 } finally { 812 c.close(); 813 } 814 } 815 816 public void commitAdd(ConnectionContext context, final MessageId messageId, final long preparedSequenceId, final long newSequence) throws IOException { 817 TransactionContext c = getTransactionContext(context); 818 try { 819 getAdapter().doCommitAddOp(c, preparedSequenceId, newSequence); 820 } catch (SQLException e) { 821 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 822 throw IOExceptionSupport.create("Failed to commit add: " + messageId + ". Reason: " + e, e); 823 } finally { 824 c.close(); 825 } 826 } 827 828 public void commitRemove(ConnectionContext context, MessageAck ack) throws IOException { 829 TransactionContext c = getTransactionContext(context); 830 try { 831 if (c != null && ack != null && ack.getLastMessageId() != null && ack.getLastMessageId().getEntryLocator() != null) { 832 getAdapter().doRemoveMessage(c, (Long) ack.getLastMessageId().getEntryLocator(), null); 833 } 834 } catch (SQLException e) { 835 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 836 throw IOExceptionSupport.create("Failed to commit last ack: " + ack + ". Reason: " + e,e); 837 } finally { 838 c.close(); 839 } 840 } 841 842 public void commitLastAck(ConnectionContext context, long xidLastAck, long priority, ActiveMQDestination destination, String subName, String clientId) throws IOException { 843 TransactionContext c = getTransactionContext(context); 844 try { 845 getAdapter().doSetLastAck(c, destination, null, clientId, subName, xidLastAck, priority); 846 } catch (SQLException e) { 847 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 848 throw IOExceptionSupport.create("Failed to commit last ack with priority: " + priority + " on " + destination + " for " + subName + ":" + clientId + ". Reason: " + e,e); 849 } finally { 850 c.close(); 851 } 852 } 853 854 public void rollbackLastAck(ConnectionContext context, JDBCTopicMessageStore store, MessageAck ack, String subName, String clientId) throws IOException { 855 TransactionContext c = getTransactionContext(context); 856 try { 857 byte priority = (byte) store.getCachedStoreSequenceId(c, store.getDestination(), ack.getLastMessageId())[1]; 858 getAdapter().doClearLastAck(c, store.getDestination(), priority, clientId, subName); 859 } catch (SQLException e) { 860 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 861 throw IOExceptionSupport.create("Failed to rollback last ack: " + ack + " on " + store.getDestination() + " for " + subName + ":" + clientId + ". Reason: " + e,e); 862 } finally { 863 c.close(); 864 } 865 } 866 867 // after recovery there is no record of the original messageId for the ack 868 public void rollbackLastAck(ConnectionContext context, byte priority, ActiveMQDestination destination, String subName, String clientId) throws IOException { 869 TransactionContext c = getTransactionContext(context); 870 try { 871 getAdapter().doClearLastAck(c, destination, priority, clientId, subName); 872 } catch (SQLException e) { 873 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 874 throw IOExceptionSupport.create("Failed to rollback last ack with priority: " + priority + " on " + destination + " for " + subName + ":" + clientId + ". Reason: " + e, e); 875 } finally { 876 c.close(); 877 } 878 } 879 880 long[] getStoreSequenceIdForMessageId(ConnectionContext context, MessageId messageId, ActiveMQDestination destination) throws IOException { 881 long[] result = new long[]{-1, Byte.MAX_VALUE -1}; 882 TransactionContext c = getTransactionContext(context); 883 try { 884 result = adapter.getStoreSequenceId(c, destination, messageId); 885 } catch (SQLException e) { 886 JDBCPersistenceAdapter.log("JDBC Failure: ", e); 887 throw IOExceptionSupport.create("Failed to get store sequenceId for messageId: " + messageId +", on: " + destination + ". Reason: " + e, e); 888 } finally { 889 c.close(); 890 } 891 return result; 892 } 893 894 @Override 895 public JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException { 896 throw new UnsupportedOperationException(); 897 } 898}