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}