/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.messaging.jmq.jmsserver.persist.file;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.sun.messaging.jmq.jmsserver.Globals;
import com.sun.messaging.jmq.jmsserver.data.BaseTransaction;
import com.sun.messaging.jmq.jmsserver.data.TransactionState;
import com.sun.messaging.jmq.jmsserver.data.TransactionUID;
import com.sun.messaging.jmq.jmsserver.data.TransactionWork;
import com.sun.messaging.jmq.jmsserver.data.TransactionWorkMessage;
import com.sun.messaging.jmq.jmsserver.persist.Store;
import com.sun.messaging.jmq.jmsserver.util.BrokerException;
import com.sun.messaging.jmq.util.log.Logger;
public abstract class BaseTransactionManager {
public static final Logger logger = Globals.getLogger();
PreparedTxnStore preparedTxnStore;
TransactionLogManager transactionLogManager;
Set<TransactionUID> playingToMessageStore = Collections.synchronizedSet(new HashSet<TransactionUID>());
Map<TransactionUID, BaseTransaction> completeStored = new Hashtable<TransactionUID, BaseTransaction>();
Map<TransactionUID, BaseTransaction> incompleteUnstored = new Hashtable<TransactionUID, BaseTransaction>();
Map<TransactionUID, BaseTransaction> incompleteStored = new Hashtable<TransactionUID, BaseTransaction>();
BaseTransactionManager(TransactionLogManager transactionLogManager) {
this.transactionLogManager = transactionLogManager;
preparedTxnStore = transactionLogManager.preparedTxnStore;
}
abstract String getPrefix();
public List<BaseTransaction> getAllIncompleteTransactions() {
List<BaseTransaction> result = new ArrayList<BaseTransaction>();
if (Store.getDEBUG()) {
String msg = getPrefix() + " getAllIncompleteTransactions "
+ " num incompleteUnstored = " + incompleteUnstored.size()
+ " num incompleteStored = " + incompleteStored.size();
logger.log(Logger.DEBUG, msg);
}
result.addAll(incompleteUnstored.values());
result.addAll(incompleteStored.values());
return result;
}
public HashMap getAllTransactionsMap() {
List<BaseTransaction> txns = getAllIncompleteTransactions();
HashMap map = new HashMap(txns.size());
Iterator<BaseTransaction> itr = txns.iterator();
while (itr.hasNext()) {
BaseTransaction txn = itr.next();
TransactionState txnState = new TransactionState(txn
.getTransactionState());
map.put(txn.getTid(), txnState);
}
return map;
}
public void addToCompleteStored(BaseTransaction baseTxn) {
completeStored.put(baseTxn.getTid(), baseTxn);
if (Store.getDEBUG()) {
String msg = getPrefix() + " adding " + baseTxn.getTid()
+ " to completeStored. Total = " + completeStored.size();
logger.log(Logger.DEBUG, msg);
}
}
public void removeFromCompleteStored(TransactionUID tid) {
completeStored.remove(tid);
if (Store.getDEBUG()) {
String msg = getPrefix() + " removing " + tid
+ " from completeStored. Total = " + completeStored.size();
logger.log(Logger.DEBUG, msg);
}
}
void addToIncompleteStored(BaseTransaction baseTxn) {
incompleteStored.put(baseTxn.getTid(), baseTxn);
if (Store.getDEBUG()) {
String msg = getPrefix()
+ " adding " + baseTxn.getTid()
+ " to incompleteStored. Total = "
+ incompleteStored.size();
logger.log(Logger.DEBUG, msg);
}
}
BaseTransaction removeFromIncompleteStored(TransactionUID tid) {
BaseTransaction result = incompleteStored.remove(tid);
if (Store.getDEBUG()) {
String msg = getPrefix()
+ " removing " + tid + " from incompleteStored. Total = "
+ incompleteStored.size();
logger.log(Logger.DEBUG, msg);
}
return result;
}
void addToIncompleteUnstored(BaseTransaction baseTxn) {
incompleteUnstored.put(baseTxn.getTid(), baseTxn);
if (Store.getDEBUG()) {
String msg = getPrefix()
+ " adding " + baseTxn.getTid()
+ " to incompleteUnstored. Total = "
+ incompleteUnstored.size();
logger.log(Logger.DEBUG, msg);
}
}
BaseTransaction removeFromIncompleteUnstored(TransactionUID tid) {
BaseTransaction result = incompleteUnstored.remove(tid);
if (Store.getDEBUG()) {
String msg = getPrefix()
+ " removing " + tid + " from incompleteUnstored. Total = "
+ incompleteUnstored.size();
logger.log(Logger.DEBUG, msg);
}
return result;
}
abstract void processStoredTxnOnStartup(BaseTransaction baseTxn);
public void waitForPlayingToMessageStoreCompletion() {
synchronized (playingToMessageStore) {
if (Store.getDEBUG()) {
String msg = getPrefix() + " num playingToMessageStore ="
+ playingToMessageStore.size();
logger.log(Logger.DEBUG, msg);
}
try {
while (playingToMessageStore.size() > 0) {
if (Store.getDEBUG()) {
String msg = getPrefix() + " waiting for "
+ playingToMessageStore.size()
+ " playingToMessageStore";
logger.log(Logger.DEBUG, msg);
}
playingToMessageStore.wait(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/*
* this method is called by transactionHandler to notify that
* the committed transaction has been written to the message store.
*
* Keeping track of committed transactions and when they have been written to the message store
* allows the TransactionlogManager to know when it is safe to do a checkpoint.
*/
public void playingToMessageStoreComplete(TransactionUID tid) {
if (Store.getDEBUG()) {
String msg = getPrefix() + " playingToMessageStoreComplete " + tid;
logger.log(Logger.DEBUG, msg);
}
synchronized (playingToMessageStore) {
Object found = playingToMessageStore.remove(tid);
if (found == null) {
String msg = getPrefix()
+ " playingToMessageStoreComplete(): could not find "
+ tid;
logger.log(Logger.WARNING, msg);
}
playingToMessageStore.notify();
}
if (Store.getDEBUG()) {
String msg = getPrefix()
+ " remove transaction from playingToMessageStore."
+ " tid=" + tid + " size = " + playingToMessageStore.size();
logger.log(Logger.DEBUG, msg);
}
}
// on checkpoint, we need to store any newly logged prepared transactions to the preparedTxnStore
// as the transaction log is about to be reset
void writePreparedTransactionsToPreparedTxnStoreOnCheckpoint()
throws BrokerException {
if (Store.getDEBUG()) {
String msg = getPrefix()
+ " writePreparedTransactionsToPreparedTxnStoreOnCheckpoint"
+ " num incompleteUnstored=" + incompleteUnstored.size();
logger.log(Logger.DEBUG, msg);
}
ArrayList<TransactionUID> incmps = null;
synchronized(incompleteUnstored) {
incmps = new ArrayList<TransactionUID>(incompleteUnstored.keySet());
}
TransactionUID tid = null;
BaseTransaction baseTxn = null;
Iterator<TransactionUID> iter = incmps.iterator();
while (iter.hasNext()) {
tid = iter.next();
if (!preparedTxnStore.containsTransaction(tid)) {
baseTxn = (BaseTransaction)incompleteUnstored.get(tid);
if (baseTxn == null) {
continue;
}
if (Store.getDEBUG()) {
String msg = getPrefix()
+ " transaction storing preparedTransaction "
+ baseTxn;
logger.log(Logger.DEBUG, msg);
}
try {
preparedTxnStore.storeTransaction(baseTxn, true);
addToIncompleteStored(baseTxn);
} catch (IOException ioe) {
throw new BrokerException(
"failed to store transaction in preparedTxnStore "
+ baseTxn, ioe);
}
} else {
String msg = getPrefix()
+ " transaction already exists in preparedTxnStore "
+ tid+"["+incompleteUnstored.get(tid)+"]";
logger.log(Logger.INFO, msg);
}
}
incompleteUnstored.clear();
}
void removeCompleteTransactionsAfterCheckpoint() throws BrokerException {
if (Store.getDEBUG()) {
String msg = getPrefix()
+ " removeCompleteTransactionsAfterCheckpoint"
+ "num completeStored=" + completeStored.size();
logger.log(Logger.DEBUG, msg);
}
ArrayList cmptids = null;
synchronized(completeStored) {
cmptids = new ArrayList(completeStored.keySet());
}
Iterator<TransactionUID> iter = cmptids.iterator();
while (iter.hasNext()) {
TransactionUID tid = iter.next();
if (preparedTxnStore.containsTransaction(tid)) {
preparedTxnStore.removeTransaction(tid, true);
if (Store.getDEBUG()) {
String msg = getPrefix() + " removed transaction " + tid;
logger.log(Logger.DEBUG, msg);
}
} else {
if (Store.getDEBUG()) {
String msg = getPrefix()
+ " Could not find transaction in preparedTxnStore "
+ tid;
logger.log(Logger.DEBUG, msg);
}
}
}
completeStored.clear();
}
void rollbackAllTransactions() {
if (Store.getDEBUG()) {
String msg = getPrefix()
+ " rollbackAllTransactions"
+ " num incompleteStored=" + incompleteStored.size()
+ " num incompleteUnstored=" + incompleteUnstored.size();
logger.log(Logger.DEBUG, msg);
}
Collection<BaseTransaction> storedTxns = incompleteStored.values();
Collection<BaseTransaction> storedTxnsCopy = new ArrayList<BaseTransaction>(
storedTxns);
rollbackTransactions(storedTxnsCopy);
Collection<BaseTransaction> unstoredTxns = incompleteUnstored.values();
Collection<BaseTransaction> unstoredTxnsCopy = new ArrayList<BaseTransaction>(
unstoredTxns);
rollbackTransactions(unstoredTxnsCopy);
}
void rollbackTransactions(Collection<BaseTransaction> txns) {
Iterator<BaseTransaction> iter = txns.iterator();
while (iter.hasNext()) {
BaseTransaction txn = iter.next();
try {
processTxnCompletion(txn.getTid(), TransactionState.ROLLEDBACK,
true);
} catch (IOException ioe) {
logger.log(Logger.ERROR, "could not rollback " + txn, ioe);
} catch (BrokerException be) {
logger.log(Logger.ERROR, "could not rollback " + txn, be);
}
}
}
abstract void processTxn(BaseTransaction baseTxn) throws IOException,
BrokerException;
abstract TransactionEvent generateEvent(BaseTransaction baseTxn, boolean completion)
throws IOException, BrokerException;
abstract BaseTransaction processTxnCompletion(TransactionUID tid, int state)
throws IOException, BrokerException;
BaseTransaction processTxnCompletion(TransactionUID tid, int state,
boolean fullyComplete) throws IOException, BrokerException {
// We are committing a prepared entry.
// Check if it is in the prepared transaction store
// If it is mark it as committed, so that it can be cleaned up after next checkpoint
if(state==TransactionState.COMMITTED)
{
this.playingToMessageStore.add(tid);
if (Store.getDEBUG()) {
String msg = getPrefix()
+ " add transaction to playingToMessageStore."
+ " tid=" + tid + " size = " + playingToMessageStore.size();
logger.log(Logger.DEBUG, msg);
}
}
BaseTransaction existingTxn = null;
boolean stored = false;
existingTxn = incompleteUnstored.get(tid);
if (existingTxn == null) {
existingTxn = incompleteStored.get(tid);
if (existingTxn != null) {
stored = true;
} else {
String msg = getPrefix() + " processTxnCompletion: Could not find txn for " + tid;
logger.log(Logger.WARNING, msg);
throw new BrokerException(msg);
}
}
existingTxn.getTransactionDetails().setState(state);
if (stored) {
// update the stored transaction from prepared to committed/rolledback state
//update state on file by writing at fixed offset
updateStoredState(tid, state);
if (fullyComplete) {
incompleteStored.remove(tid);
completeStored.put(tid, existingTxn);
}
} else {
if (fullyComplete) {
removeFromIncompleteUnstored(tid);
}
}
messageListLogged(existingTxn);
return existingTxn;
}
protected void messageListLogged(BaseTransaction baseTxn) {
TransactionWork txnWork = baseTxn.getTransactionWork();
if (txnWork != null) {
List<TransactionWorkMessage> sentMessages = txnWork
.getSentMessages();
if (sentMessages != null) {
transactionLogManager.loggedMessageHelper
.messageListLogged(sentMessages);
}
}
}
void updateStoredState(TransactionUID tid, int state) throws IOException,
BrokerException {
preparedTxnStore.updateTransactionState(tid, state, true);
if (Store.getDEBUG()) {
String msg = getPrefix()
+ " updated transaction state of stored prepared transaction to "
+ TransactionState.toString(state);
logger.log(Logger.DEBUG, msg);
}
}
void updateStoredCompletion(TransactionUID tid, boolean complete)
throws IOException, BrokerException {
preparedTxnStore.updateTransactionCompletion(tid, complete, true);
if (Store.getDEBUG()) {
String msg = getPrefix()
+ " updated completion status of stored transaction to "
+ complete;
logger.log(Logger.DEBUG, msg);
}
}
abstract void replayTransactionEvent(TransactionEvent localTxnEvent,
HashSet dstLoadedSet) throws BrokerException, IOException;
}