/*
* $Header: /home/cvs/jakarta-slide/src/stores/org/apache/slide/store/txfile/AbstractTxFileStoreService.java,v 1.7.2.2 2004/02/05 16:07:53 mholz Exp $
* $Revision: 1.7.2.2 $
* $Date: 2004/02/05 16:07:53 $
*
* ====================================================================
*
* Copyright 1999-2002 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.slide.store.txfile;
import org.apache.slide.common.*;
import org.apache.slide.macro.ConflictException;
import org.apache.slide.store.txfile.rm.ResourceManager;
import org.apache.slide.store.txfile.rm.ResourceManagerException;
import org.apache.slide.store.txfile.rm.impl.FileResourceManager;
import java.io.File;
import java.util.Hashtable;
import javax.transaction.Status;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.apache.slide.util.XidWrapper;
import org.apache.slide.util.logger.*;
import org.apache.slide.util.logger.Logger;
/**
* Abstract base for transactional file stores.
*
* Even though the <code>XAResource</code> interface is implemented,
* this code does not provide conformance to the JTA/JTS or XA spec.
*
* It is only intended for use inside of Slide's transaction manager.
*
* @author <a href="mailto:ozeigermann@c1-fse.de">Oliver Zeigermann</a>
*
*/
public abstract class AbstractTxFileStoreService extends AbstractServiceBase implements Status {
private static final String LOG_CHANNEL = AbstractTxFileStoreService.class.getName();
protected static final String STORE_DIR_PARAMETER = "rootpath";
protected static final String WORK_DIR_PARAMETER = "workpath";
protected static final String TIMEOUT_PARAMETER = "timeout";
protected static final String URLENCODE_PATH = "url-encode-path";
protected FileResourceManager rm;
protected boolean started = false;
protected String storeDir;
protected String workDir;
// there might be at least one active transaction branch per thread
protected ThreadLocal activeTransactionBranch = new ThreadLocal();
public void setParameters(Hashtable parameters)
throws ServiceParameterErrorException, ServiceParameterMissingException {
storeDir = (String) parameters.get(STORE_DIR_PARAMETER);
workDir = (String) parameters.get(WORK_DIR_PARAMETER);
if (storeDir == null) {
throw new ServiceParameterMissingException(this, STORE_DIR_PARAMETER);
}
if (workDir == null) {
throw new ServiceParameterMissingException(this, WORK_DIR_PARAMETER);
}
new File(storeDir).mkdirs();
new File(workDir).mkdirs();
boolean urlEncodePath = false;
String urlEncodePathString = (String) parameters.get(URLENCODE_PATH);
if (urlEncodePathString != null) {
urlEncodePath = "true".equals(urlEncodePathString);
}
rm =
new FileResourceManager(
storeDir,
workDir,
urlEncodePath,
new StoreLogger(getLogger(), FileResourceManager.class.getName()));
getLogger().log(
"File Store configured to " + storeDir + ", working directory " + workDir,
LOG_CHANNEL,
Logger.INFO);
String timeoutString = (String) parameters.get(TIMEOUT_PARAMETER);
if (timeoutString != null) {
try {
int timeout = Integer.parseInt(timeoutString);
rm.setDefaultTransactionTimeout(timeout * 1000);
getLogger().log("Set timeout to " + timeoutString, LOG_CHANNEL, Logger.INFO);
} catch (NumberFormatException nfe) {
getLogger().log(
"Can not set timeout, '" + timeoutString + "' must be an integer!",
LOG_CHANNEL,
Logger.WARNING);
}
}
}
public String toString() {
return "TxFileStore at " + storeDir + " working on " + workDir;
}
public void connect() throws ServiceConnectionFailedException {
try {
rm.start();
started = true;
} catch (ResourceManagerException e) {
throw new ServiceConnectionFailedException(this, e);
}
}
public void disconnect() throws ServiceDisconnectionFailedException {
try {
if (!rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL)) {
throw new ServiceDisconnectionFailedException(this, "Shut down timed out");
}
started = false;
} catch (ResourceManagerException e) {
throw new ServiceDisconnectionFailedException(this, e);
}
}
public boolean isConnected() throws ServiceAccessException {
return started;
}
public void reset() {
rm.reset();
}
public int getTransactionTimeout() throws XAException {
try {
long msecs = rm.getTransactionTimeout(getActiveTxId());
return Math.round(msecs / (float) 1000);
} catch (ResourceManagerException e) {
throwXAException(e);
}
// XXX just to make compiler quiet (throwXAException will always throw an exception, so this can never happen)
return -1;
}
public boolean setTransactionTimeout(int seconds) throws XAException {
try {
rm.setTransactionTimeout(getActiveTxId(), seconds * 1000);
} catch (ResourceManagerException e) {
throwXAException(e);
}
return true;
}
public boolean isSameRM(XAResource xares) throws XAException {
return (xares instanceof AbstractTxFileStoreService && ((AbstractTxFileStoreService) xares).rm.equals(this.rm));
}
public synchronized Xid[] recover(int flag) throws XAException {
// do not care as this never is called by Slide
return null;
}
public synchronized void forget(Xid xid) throws XAException {
// there is nothing we remember, so there is nothing to forget
}
public synchronized int prepare(Xid xid) throws XAException {
Object txId = XidWrapper.wrap(xid);
getLogger().log(
"Thread " + Thread.currentThread() + " prepares transaction branch " + txId,
LOG_CHANNEL,
Logger.DEBUG);
try {
int status = rm.prepareTransaction(txId);
switch (status) {
case ResourceManager.PREPARE_SUCCESS_READONLY :
return XA_RDONLY;
case ResourceManager.PREPARE_SUCCESS :
return XA_OK;
default :
throw new XAException(XAException.XA_RBROLLBACK);
}
} catch (ResourceManagerException e) {
throwXAException(e);
}
// XXX just to make compiler quiet (throwXAException will always throw an exception, so this can never happen)
return 0;
}
public synchronized void rollback(Xid xid) throws XAException {
Object txId = XidWrapper.wrap(xid);
getLogger().log(
"Thread " + Thread.currentThread() + " rolls back transaction branch " + txId,
LOG_CHANNEL,
Logger.DEBUG);
try {
rm.rollbackTransaction(txId);
activeTransactionBranch.set(null);
} catch (ResourceManagerException e) {
throwXAException(e);
}
}
public synchronized void commit(Xid xid, boolean onePhase) throws XAException {
Object txId = XidWrapper.wrap(xid);
getLogger().log(
"Thread " + Thread.currentThread() + " commits transaction branch " + txId,
LOG_CHANNEL,
Logger.DEBUG);
try {
if (!onePhase && rm.getTransactionState(txId) != STATUS_PREPARED) {
throw new XAException(XAException.XAER_INVAL);
}
rm.commitTransaction(txId);
activeTransactionBranch.set(null);
} catch (ResourceManagerException e) {
throwXAException(e);
}
}
public synchronized void end(Xid xid, int flags) throws XAException {
Object txId = XidWrapper.wrap(xid);
getLogger().log(
"Thread "
+ Thread.currentThread()
+ " ends work on behalf of transaction branch "
+ txId
+ " with flags "
+ flags,
LOG_CHANNEL,
Logger.DEBUG);
switch (flags) {
case TMSUSPEND :
getLogger().log(
"Thread of control suspends work on behalf of transaction branch",
LOG_CHANNEL,
Logger.DEBUG);
// XXX we do not resume, so we do not suspend
break;
case TMFAIL :
getLogger().log("Transaction branch failed", LOG_CHANNEL, Logger.DEBUG);
try {
rm.markTransactionForRollback(XidWrapper.wrap(xid));
} catch (ResourceManagerException e) {
throwXAException(e);
}
break;
case TMSUCCESS :
getLogger().log("Transaction branch successfully completed", LOG_CHANNEL, Logger.DEBUG);
// not ineresting for us
break;
}
}
public synchronized void start(Xid xid, int flags) throws XAException {
Object txId = XidWrapper.wrap(xid);
getLogger().log(
"Thread "
+ Thread.currentThread()
+ " starts work on behalf of transaction branch "
+ txId
+ " with flags "
+ flags,
LOG_CHANNEL,
Logger.DEBUG);
switch (flags) {
// a new transaction
case TMNOFLAGS :
getLogger().log("Starting new transaction branch", LOG_CHANNEL, Logger.DEBUG);
if (getActiveTxId() != null) {
throw new XAException(XAException.XAER_INVAL);
}
try {
rm.startTransaction(txId);
activeTransactionBranch.set(txId);
} catch (ResourceManagerException e) {
throwXAException(e);
}
break;
case TMJOIN :
getLogger().log("Thread of control joins known transaction branch", LOG_CHANNEL, Logger.DEBUG);
if (getActiveTxId() != null) {
throw new XAException(XAException.XAER_INVAL);
}
try {
if (rm.getTransactionState(txId) == STATUS_NO_TRANSACTION) {
throw new XAException(XAException.XAER_INVAL);
}
} catch (ResourceManagerException e) {
throwXAException(e);
}
activeTransactionBranch.set(txId);
break;
case TMRESUME :
getLogger().log("Thread of control resume work on known transaction branch", LOG_CHANNEL, Logger.DEBUG);
// XXX we do not suspend, so we do not resume
break;
}
}
public synchronized void throwInternalError(String cause) throws ServiceAccessException {
Object txId = getActiveTxId();
getLogger().log(
"Thread "
+ Thread.currentThread()
+ " marked transaction branch "
+ txId
+ " for rollback. Cause: "
+ cause,
LOG_CHANNEL,
Logger.WARNING);
try {
rm.markTransactionForRollback(txId);
} catch (ResourceManagerException re) {
throw new ServiceAccessException(this, re);
}
throw new ServiceAccessException(this, cause);
}
public synchronized void throwInternalError(Throwable cause) throws ServiceAccessException {
Object txId = getActiveTxId();
getLogger().log(
"Thread "
+ Thread.currentThread()
+ " marked transaction branch "
+ txId
+ " for rollback. Cause: "
+ cause,
LOG_CHANNEL,
Logger.WARNING);
try {
rm.markTransactionForRollback(txId);
} catch (ResourceManagerException re) {
throw new ServiceAccessException(this, re);
}
throw new ServiceAccessException(this, cause);
}
// TODO if error is caused by lock that could not be acquired
// we should try deadlock detection instead of simply rolling back
// if no deadlock is detected, retrying for lock would be preferred method
public synchronized void throwInternalError(Throwable cause, String uri) throws ServiceAccessException {
Object txId = getActiveTxId();
if ((cause instanceof ResourceManagerException)
&& ((ResourceManagerException) cause).getStatus() == ResourceManagerException.ERR_NO_LOCK) {
// XXX strictly speaking, this is incorrect, as we actually did not chck for deadlock,
// but silently assume a deadlock must have been the cause for the failed lock
try {
rm.markTransactionForRollback(txId);
} catch (ResourceManagerException re) {
throw new ServiceAccessException(this, re);
}
getLogger().log(
"DEADLOCK VICTIM: Thread "
+ Thread.currentThread()
+ " marked transaction branch "
+ txId
+ " for rollback",
LOG_CHANNEL,
Logger.INFO);
throw new ServiceAccessException(this, new ConflictException(uri));
} else {
try {
rm.markTransactionForRollback(txId);
} catch (ResourceManagerException re) {
throw new ServiceAccessException(this, re);
}
getLogger().log(
"Thread " + Thread.currentThread() + " marked transaction branch " + txId + " for rollback",
LOG_CHANNEL,
Logger.WARNING);
throw new ServiceAccessException(this, cause);
}
}
protected Object getActiveTxId() {
Object txId = activeTransactionBranch.get();
return txId;
}
protected void throwXAException(ResourceManagerException e) throws XAException {
if (e.getStatus() == ResourceManagerException.ERR_DUP_TX) {
throw new XAException(XAException.XAER_DUPID);
} else if (e.getStatus() == ResourceManagerException.ERR_TXID_INVALID) {
throw new XAException(XAException.XAER_NOTA);
} else {
throw new XAException(e.toString());
}
}
}