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