/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2000-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.bridge.service.jms.tx.log;
import java.util.List;
import java.util.Vector;
import java.util.Properties;
import java.util.logging.Logger;
import java.util.logging.Level;
import com.sun.messaging.jmq.io.Status;
import com.sun.messaging.jmq.util.timer.WakeupableTimer;
import com.sun.messaging.jmq.util.timer.TimerEventHandler;
import com.sun.messaging.bridge.service.jms.tx.GlobalXid;
import com.sun.messaging.bridge.service.jms.tx.BranchXid;
import com.sun.messaging.bridge.service.BridgeException;
/**
*
* @author amyk
*/
public abstract class TxLog {
public static final String FILETYPE = "file";
public static final String JDBCTYPE = "jdbc";
public static final String FILECLASS = "com.sun.messaging.bridge.service.jms.tx.log.FileTxLogImpl";
public static final String JDBCCLASS = "com.sun.messaging.bridge.service.jms.tx.log.JDBCTxLogImpl";
public static final int DEFAULT_REAPLIMIT = 50;
public static final int DEFAULT_REAPINTERVAL = 60; //1min
Logger _logger = null;
private boolean _closed = false;
private Object _closedLock = new Object();
private int _inprogressCount = 0;
private Object _inprogressLock = new Object();
private TransactionReaper _reaper = null;
private int _reapLimit = DEFAULT_REAPLIMIT;
private int _reapInterval = DEFAULT_REAPINTERVAL;
protected String _tmname = null;
protected String _jmsbridge = null;
public void setLogger(Logger logger) {
_logger = logger;
}
public Logger getLogger() {
return _logger;
}
/**
* @param props The properties is guaranteed to contain
* "txlogDir", "txlogSuffix", "txlogMaxBranches"
* @param reset true to reset the txlog
*/
public void init(Properties props, boolean reset) throws Exception {
_jmsbridge = props.getProperty("jmsbridge");
if (_jmsbridge == null) {
throw new IllegalArgumentException("Property 'jmsbridge' not set");
}
_tmname = props.getProperty("tmname");
if (_tmname == null) {
throw new IllegalStateException("Property 'tmname' not set");
}
String v = props.getProperty("txlogReapLimit");
if (v != null) {
_reapLimit = Integer.parseInt(v);
}
v = props.getProperty("txlogReapInterval");
if (v != null) {
_reapInterval = Integer.parseInt(v);
}
_reaper = new TransactionReaper(this, _reapLimit, _reapInterval);
}
public String getTMName() {
return _tmname;
}
public abstract String getType();
/**
* @param lr the LogRecord to log
*/
public abstract void logGlobalDecision(LogRecord lr) throws Exception;
/**
* @param lr the LogRecord to identify the record to update
* @param bxid the branch xid to update
*/
public abstract void logHeuristicBranch(BranchXid bxid, LogRecord lr) throws Exception;
/**
*
* @param gxid the GlobalXid
* @return a copy of LogRecord corresponding gxid or null if not exist
*/
public abstract LogRecord getLogRecord(GlobalXid gxid) throws Exception;
/**
* @return a list of all log records
*/
public abstract List getAllLogRecords() throws Exception;
/**
* @param gxid the global xid record to remove
*/
public void remove(GlobalXid gxid) throws Exception {
_reaper.addTransaction(gxid);
}
/**
* @param gxid the global xid record to remove
*/
public abstract void reap(String gxid) throws Exception;
public boolean isClosed() {
return _closed;
}
/**
* subclass override
*/
public void close() throws Exception {
synchronized(_closedLock) {
if (!isClosed()) _closed = true ;
}
if (_reaper != null) _reaper.destroy();
}
//The following methods are similar as in Store.java
protected void setClosedAndWait() {
synchronized (_closedLock) {
_closed = true;
}
synchronized (_inprogressLock) {
while (_inprogressCount > 0) {
try {
_inprogressLock.wait();
} catch (Exception e) {}
}
}
}
protected void checkClosedAndSetInProgress() throws Exception {
synchronized (_closedLock) {
if (_closed) {
String emsg = "Accessing TM txLog after store closed";
throw new BridgeException(emsg, Status.UNAVAILABLE);
} else {
setInProgress(true);
}
}
}
protected void setInProgress(boolean flag) {
synchronized (_inprogressLock) {
if (flag) {
_inprogressCount++;
} else {
_inprogressCount--;
}
if (_inprogressCount == 0) {
_inprogressLock.notify();
}
}
}
}
class TransactionReaper implements Runnable, TimerEventHandler
{
private TxLog _txlog = null;
private Logger _logger = null;
private Vector removes = new Vector();
private WakeupableTimer reapTimer = null;
private int _reapLimit = 1;
private long _reapInterval = 1000; //in millisecs
/*
* @param reapInterval in secs
*/
public TransactionReaper(TxLog txlog, int reapLimit, int reapInterval) {
_txlog = txlog;
_logger = _txlog.getLogger();
_reapLimit = reapLimit;
if (_reapInterval <= 0) {
_reapInterval = TxLog.DEFAULT_REAPINTERVAL;
}
_reapInterval = reapInterval * 1000L;
}
public void addTransaction(GlobalXid gxid) {
removes.add(gxid);
createTimer();
if (removes.size() > _reapLimit) reapTimer.wakeup();
}
private synchronized void createTimer() {
if (reapTimer == null) {
try {
String startString = "Transaction reaper thread has started for TM "+_txlog.getTMName();
String exitString = "Transaction reaper thread for TM "+_txlog.getTMName()+" is exiting";
reapTimer = new WakeupableTimer("JMSBridgeTMTransactionReaper-"+_txlog.getTMName(),
this,
_reapInterval, _reapInterval,
startString, exitString, this);
} catch (Throwable ex) {
_logger.log(Level.WARNING,
"Unable to start transaction reaper thread for TM "+_txlog.getTMName(), ex);
try {
_txlog.close();
} catch (Exception e) {}
}
}
}
/***************************************************
* Methods for TimerEventHandler for WakeupableTimer
***************************************************/
public void handleOOMError(Throwable e) {
}
public void handleLogInfo(String msg) {
if (_logger == null) return;
_logger.log(Level.INFO, msg);
}
public void handleLogWarn(String msg, Throwable e) {
if (_logger == null) return;
_logger.log(Level.WARNING, msg, e);
}
public void handleLogError(String msg, Throwable e) {
if (_logger == null) return;
_logger.log(Level.SEVERE, msg, e);
}
public void handleTimerExit(Throwable e) {
if (reapTimer == null || _txlog.isClosed()) return;
_logger.log(Level.SEVERE,
"["+_txlog.getTMName()+"]Unexpected transaction log reaper thread exit", e);
try {
_txlog.close();
} catch (Exception ex) {}
}
public synchronized void destroy() {
if (reapTimer != null) {
reapTimer.cancel();
reapTimer = null;
}
removes.clear();
}
public void run() {
GlobalXid[] gxids = (GlobalXid[])removes.toArray(new GlobalXid[0]);
int cnt = gxids.length - _reapLimit;
for (int i = 0; i < cnt; i++) {
String key = gxids[i].toString();
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, "Cleaning up global transaction "+key);
}
try {
_txlog.reap(key);
removes.remove(gxids[i]);
} catch (Exception e) {
_logger.log(Level.WARNING,
"Failed to cleanup global transaction "+key, e);
}
}
}
}