Java tutorial
/* * $Header: /var/chroot/cvs/cvs/factsheetDesigner/extern/jakarta-slide-server-src-2.1-iPlus Edit/src/stores/org/apache/slide/store/txfile/AbstractTxFileStoreService.java,v 1.3 2006-04-19 15:06:55 peter-cvs Exp $ * $Revision: 1.3 $ * $Date: 2006-04-19 15:06:55 $ * * ==================================================================== * * 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.commons.transaction.file.FileResourceManager; import org.apache.commons.transaction.file.ResourceManager; import org.apache.commons.transaction.file.ResourceManagerException; import org.apache.slide.common.*; import org.apache.slide.macro.ConflictException; 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.logger.TxLogger; import org.apache.slide.util.logger.Logger; import org.apache.slide.transaction.SlideXidWrapper; /** * 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. * * */ public abstract class AbstractTxFileStoreService extends AbstractServiceBase implements Status { protected static final int DEBUG_LEVEL = Logger.DEBUG; 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_PARAMETER = "path-encode-path"; protected static final String DEBUG_MODE_PARAMETER = "debug"; 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 debug = false; String debugString = (String) parameters.get(DEBUG_MODE_PARAMETER); if (debugString != null) { debug = "true".equals(debugString); } boolean urlEncodePath = false; String urlEncodePathString = (String) parameters.get(URLENCODE_PATH_PARAMETER); if (urlEncodePathString != null) { urlEncodePath = "true".equals(urlEncodePathString); } rm = new FileResourceManager(storeDir, workDir, urlEncodePath, new TxLogger(getLogger(), FileResourceManager.class.getName()), debug); getLogger().log("File Store configured to " + storeDir + ", working directory " + workDir, getLogChannel(), 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, getLogChannel(), Logger.INFO); } catch (NumberFormatException nfe) { getLogger().log("Can not set timeout, '" + timeoutString + "' must be an integer!", getLogChannel(), 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) { throw createXAException(e); } } public boolean setTransactionTimeout(int seconds) throws XAException { try { rm.setTransactionTimeout(getActiveTxId(), seconds * 1000); return true; } catch (ResourceManagerException e) { throw createXAException(e); } } 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 = wrap(xid); Thread currentThread = Thread.currentThread(); getLogger().log("Thread " + currentThread + " prepares transaction branch " + txId, getLogChannel(), DEBUG_LEVEL); 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) { getLogger().log("Thread " + currentThread + " failed to prepare transaction branch " + txId, e, getLogChannel(), Logger.CRITICAL); throw createXAException(e); } } public synchronized void rollback(Xid xid) throws XAException { Object txId = wrap(xid); Thread currentThread = Thread.currentThread(); getLogger().log("Thread " + currentThread + " rolls back transaction branch " + txId, getLogChannel(), DEBUG_LEVEL); try { rm.rollbackTransaction(txId); activeTransactionBranch.set(null); } catch (ResourceManagerException e) { getLogger().log("Thread " + currentThread + " failed to roll back transaction branch " + txId, e, getLogChannel(), Logger.CRITICAL); throw createXAException(e); } } public synchronized void commit(Xid xid, boolean onePhase) throws XAException { Object txId = wrap(xid); Thread currentThread = Thread.currentThread(); getLogger().log("Thread " + currentThread + " commits transaction branch " + txId, getLogChannel(), DEBUG_LEVEL); try { if (!onePhase && rm.getTransactionState(txId) != STATUS_PREPARED) { throw new XAException(XAException.XAER_INVAL); } rm.commitTransaction(txId); activeTransactionBranch.set(null); } catch (ResourceManagerException e) { getLogger().log("Thread " + currentThread + " failed to commit transaction branch " + txId, e, getLogChannel(), Logger.CRITICAL); throw createXAException(e); } } public synchronized void end(Xid xid, int flags) throws XAException { Object txId = wrap(xid); Thread currentThread = Thread.currentThread(); getLogger().log("Thread " + currentThread + (flags == TMSUSPEND ? " suspends" : flags == TMFAIL ? " fails" : " ends") + " work on behalf of transaction branch " + txId, getLogChannel(), DEBUG_LEVEL); switch (flags) { case TMSUSPEND: activeTransactionBranch.set(null); break; case TMFAIL: try { rm.markTransactionForRollback(wrap(xid)); } catch (ResourceManagerException e) { throw createXAException(e); } break; case TMSUCCESS: // not ineresting for us break; } } public synchronized void start(Xid xid, int flags) throws XAException { Object txId = wrap(xid); Thread currentThread = Thread.currentThread(); getLogger().log("Thread " + currentThread + (flags == TMNOFLAGS ? " starts" : flags == TMJOIN ? " joins" : " resumes") + " work on behalf of transaction branch " + txId, getLogChannel(), DEBUG_LEVEL); switch (flags) { // a new transaction case TMNOFLAGS: if (getActiveTxId() != null) { throw new XAException(XAException.XAER_INVAL); } try { rm.startTransaction(txId); activeTransactionBranch.set(txId); } catch (ResourceManagerException e) { throw createXAException(e); } break; case TMJOIN: 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) { throw createXAException(e); } activeTransactionBranch.set(txId); break; case TMRESUME: activeTransactionBranch.set(txId); 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, getLogChannel(), 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, getLogChannel(), 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 if (txId != null) { 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", getLogChannel(), Logger.INFO); throw new ServiceAccessException(this, new ConflictException(uri)); } else { getLogger().log( "Could not process URI '" + uri + "'! Thread " + Thread.currentThread() + " marking transaction branch " + txId + " for rollback", cause, getLogChannel(), Logger.WARNING); if (txId != null) { try { rm.markTransactionForRollback(txId); } catch (ResourceManagerException re) { throw new ServiceAccessException(this, re); } } throw new ServiceAccessException(this, cause); } } protected Object getActiveTxId() { Object txId = activeTransactionBranch.get(); return txId; } protected XAException createXAException(ResourceManagerException e) { if (e.getStatus() == ResourceManagerException.ERR_DUP_TX) { return new XAException(XAException.XAER_DUPID); } else if (e.getStatus() == ResourceManagerException.ERR_TXID_INVALID) { return new XAException(XAException.XAER_NOTA); } else { return new XAException(e.toString()); } } protected String wrap(Xid xid) { String sxid = SlideXidWrapper.wrap(xid).toString(); // XXX for tx file store replace :, ', \ and / which might be part of the identifier with chars not // offensive to any file system specific chars sxid = sxid.replace('\'', '.').replace('"', '.').replace(':', '.').replace('/', '.').replace('\\', '.'); return sxid; } abstract protected String getLogChannel(); }