I'm quite interested in this topic. The kind of integration I want is to
make Slide's transactions and the app server's transactions one and the
same.
I know that Slide's J2EEStore does the job in terms of database access.
However, it doesn't for Slide's internal machinery, since Namespace
instances use the internal transaction manager, not the app server's.
If I wanted to control transaction demarcation, I'd have to start(),
commit() and rollback() the app server's user transaction and the namespace
access token. Besides the dirty code I'd get, this approach won't work
because other parts of the application that don't know about Slide fail to
invoke the namespace token.
I've tried adding a transaction manager mutator method and subclassing
ExtendedStore (attached as AppServerStore.java) to allow for an extra
parameter that specifies the JNDI name of the transaction manager (I'm using
JBoss). During initialization this store replaces the namespace's default tm
by the app server's. To my surprise, it almost worked!
I had to alter AbstractService (attached as AbstractService.java). There are
a few comparisons of the form:
"currentContext.getGlobalTransactionId() !=
xid.getGlobalTransactionId()"
that don't work for JBoss' implementation of Xid, because
getGlobalTransactionId() returns a clone of the internal field value.
A comparison by value solves the problem:
"!Arrays.equals(currentContext.getGlobalTransactionId(),
xid.getGlobalTransactionId())"
With these arrangements, slide and the app server seem to co-work well.
However, upon failures, rollbacks don't always yield the expected results.
Does Oliver Zeigermann's recent work somehow related to this usage of
external transaction managers?
Regards,
Alejandro
/*
* Project: wf-data
* File: AppServerStore.java
* Created: 8/07/2004
*/
package mx.connecto.repository.sld.ent;
import java.util.Hashtable;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.TransactionManager;
import org.apache.slide.common.Namespace;
import org.apache.slide.common.ServiceParameterErrorException;
import org.apache.slide.common.ServiceParameterMissingException;
import org.apache.slide.store.ExtendedStore;
import org.apache.slide.util.logger.Logger;
/**
* Store that allows for transactional caching of data over JTA transactions
* provided by an application server.
* <br/>Project: wf-data
* <br/>Created: 8/07/2004
* @author Alejandro Gu�zar
* @version $Revision: 1.1 $ $Date: 2004/07/17 02:30:42 $
*/
public class AppServerStore extends ExtendedStore {
private TransactionManager txManager;
/**
* Parameter to specify the JNDI name of the transaction manager.
*/
public static final String TX_MANAGER_NAME_PARAMETER = "tx-manager-name";
/**
* Default value for the transaction manager name parameter.
*/
public static final String DEFAULT_TX_MANAGER_NAME = "java:/TransactionManager";
private static final String LOG_CHANNEL = AppServerStore.class.getName();
/**
* Default constructor.
*/
public AppServerStore() {
}
/* (non-Javadoc)
* @see org.apache.slide.common.Service#setParameters(java.util.Hashtable)
*/
public void setParameters(Hashtable parameters)
throws ServiceParameterErrorException, ServiceParameterMissingException {
super.setParameters(parameters);
String txManagerName = (String) parameters.get(TX_MANAGER_NAME_PARAMETER);
if (txManagerName == null) {
txManagerName = DEFAULT_TX_MANAGER_NAME;
}
getLogger().log("Setting transaction manager name for store " + getName() +
" to " + txManagerName, LOG_CHANNEL, Logger.INFO);
try {
txManager = (TransactionManager) new InitialContext().lookup(txManagerName);
}
catch (NamingException e) {
ServiceParameterErrorException spe =
new ServiceParameterErrorException(this, TX_MANAGER_NAME_PARAMETER);
spe.initCause(e);
throw spe;
}
}
/* (non-Javadoc)
* @see org.apache.slide.common.Service#setNamespace(
* org.apache.slide.common.Namespace)
*/
public void setNamespace(Namespace namespace) {
super.setNamespace(namespace);
namespace.initTransactionManager(txManager);
}
}
/*
* $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/common/AbstractService.java,v 1.8 2004/02/11 11:30:11 ozeigermann Exp $
* $Revision: 1.8 $
* $Date: 2004/02/11 11:30:11 $
*
* ====================================================================
*
* 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.common;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Arrays;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.apache.slide.transaction.SlideTransactionManager;
/**
* Slide Service abstract implementation with support for multiple
* simultaneous transaction context.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Remy Maucherat</a>
* @author Juergen Pill
* @version $Revision: 1.8 $
*/
public abstract class AbstractService extends AbstractServiceBase
implements Service {
// -------------------------------------------------------------- Constants
public static final int TX_IDLE = 0;
public static final int TX_PREPARED = 1;
public static final int TX_SUSPENDED = 2;
// ----------------------------------------------------- Instance Variables
/**
* Current transaction context.
*/
protected Hashtable currentContexts = new Hashtable();
// ----------------------------------------------------- XAResource Mathods
/**
* Commit the global transaction specified by xid.
*
* @param xid A global transaction identifier
* @param onePhase If true, the resource manager should use a one-phase
* commit protocol to commit the work done on behalf of xid.
* @exception XAException An error has occurred. Possible XAExceptions
* are XA_HEURHAZ, XA_HEURCOM, XA_HEURRB, XA_HEURMIX, XAER_RMERR,
* XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or XAER_PROTO. If the resource
* manager did not commit the transaction and the paramether onePhase is
* set to true, the resource manager may throw one of the XA_RB*
* exceptions. Upon return, the resource manager has rolled back the
* branch's work and has released all held resources.
*/
public void commit(Xid xid, boolean onePhase)
throws XAException {
ContextTuple currentContextTuple =
(ContextTuple) currentContexts.get(Thread.currentThread());
Xid currentContext =
currentContextTuple!=null?currentContextTuple.getXid():null;
if (currentContext == null)
throw new XAException(XAException.XAER_NOTA);
if (xid == null)
throw new XAException(XAException.XAER_INVAL);
if (!Arrays.equals(currentContext.getGlobalTransactionId(),
xid.getGlobalTransactionId()))
throw new XAException(XAException.XAER_PROTO);
if (!onePhase && currentContextTuple.getStatus() != TX_PREPARED)
throw new XAException(XAException.XAER_PROTO);
if (onePhase &&
(!((currentContextTuple.getStatus() == TX_IDLE) ||
(currentContextTuple.getStatus() == TX_SUSPENDED))))
throw new XAException(XAException.XAER_PROTO);
if (((ContextTuple) currentContexts.get(Thread.currentThread()))
.getRollbackOnly()) {
rollback(xid);
throw new XAException(XAException.XA_RBROLLBACK);
}
currentContexts.remove(Thread.currentThread());
}
/**
* Ends the work performed on behalf of a transaction branch. The
* resource manager disassociates the XA resource from the transaction
* branch specified and let the transaction be completed.
* If TMSUSPEND is specified in flags, the transaction branch is
* temporarily suspended in incomplete state. The transaction context is
* in suspened state and must be resumed via start with TMRESUME specified.
* If TMFAIL is specified, the portion of work has failed. The resource
* manager may mark the transaction as rollback-only.
* If TMSUCCESS is specified, the portion of work has completed
* successfully.
*
* @param xid A global transaction identifier that is the same as what
* was used previously in the start method.
* @param flags One of TMSUCCESS, TMFAIL, or TMSUSPEND
* @exception XAException An error has occurred. Possible XAException
* values are XAER_RMERR, XAER_RMFAILED, XAER_NOTA, XAER_INVAL,
* XAER_PROTO, or XA_RB*.
*/
public void end(Xid xid, int flags)
throws XAException {
ContextTuple currentContextTuple =
(ContextTuple) currentContexts.get(Thread.currentThread());
Xid currentContext =
currentContextTuple!=null?currentContextTuple.getXid():null;
if (currentContext == null)
throw new XAException(XAException.XAER_NOTA);
if (xid == null)
throw new XAException(XAException.XAER_INVAL);
if (!Arrays.equals(currentContext.getGlobalTransactionId(),
xid.getGlobalTransactionId()))
throw new XAException(XAException.XAER_PROTO);
if (flags == XAResource.TMSUSPEND)
currentContextTuple.setStatus(TX_SUSPENDED);
if (flags == XAResource.TMFAIL)
((ContextTuple) currentContexts.get(Thread.currentThread()))
.setRollbackOnly(true);
}
/**
* Tell the resource manager to forget about a heuristically completed
* transaction branch.
*
* @param xid A global transaction identifier
* @exception XAException An error has occurred. Possible exception values
* are XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or XAER_PROTO.
*/
public void forget(Xid xid)
throws XAException {
ContextTuple currentContextTuple =
(ContextTuple) currentContexts.get(Thread.currentThread());
Xid currentContext =
currentContextTuple!=null?currentContextTuple.getXid():null;
if (currentContext == null)
throw new XAException(XAException.XAER_NOTA);
if (xid == null)
throw new XAException(XAException.XAER_INVAL);
if (!Arrays.equals(currentContext.getGlobalTransactionId(),
xid.getGlobalTransactionId()))
throw new XAException(XAException.XAER_PROTO);
currentContexts.remove(Thread.currentThread());
}
/**
* Obtain the current transaction timeout value set for this XAResource
* instance. If XAResource.setTransactionTimeout was not use prior to
* invoking this method, the return value is the default timeout set for
* the resource manager; otherwise, the value used in the previous
* setTransactionTimeout call is returned.
*
* @return the transaction timeout value in seconds.
* @exception XAException An error has occurred. Possible exception
* values are XAER_RMERR, XAER_RMFAIL.
*/
public int getTransactionTimeout()
throws XAException {
return ((ContextTuple) currentContexts.get(Thread.currentThread()))
.getTransactionTimeout();
}
/**
* This method is called to determine if the resource manager instance
* represented by the target object is the same as the resouce manager
* instance represented by the parameter xares.
*
* @param xares An XAResource object whose resource manager instance is
* to be compared with the resource manager instance of the target object.
* @return true if it's the same RM instance; otherwise false.
* @exception XAException An error has occurred. Possible exception values
* are XAER_RMERR, XAER_RMFAIL.
*/
public boolean isSameRM(XAResource xares)
throws XAException {
if (xares == null)
return false;
return (this == xares);
}
/**
* Ask the resource manager to prepare for a transaction commit of the
* transaction specified in xid.
*
* @param xid A global transaction identifier
* @return A value indicating the resource manager's vote on the outcome
* of the transaction. The possible values are: XA_RDONLY or XA_OK. If
* the resource manager wants to roll back the transaction, it should do
* so by raising an appropriate XAException in the prepare method.
* @exception XAException An error has occurred. Possible exception
* values are: XA_RB*, XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL,
* or XAER_PROTO.
*/
public int prepare(Xid xid)
throws XAException {
ContextTuple currentContextTuple =
(ContextTuple) currentContexts.get(Thread.currentThread());
Xid currentContext =
currentContextTuple!=null?currentContextTuple.getXid():null;
if (currentContext == null)
throw new XAException(XAException.XAER_NOTA);
if (xid == null)
throw new XAException(XAException.XAER_INVAL);
if (!Arrays.equals(currentContext.getGlobalTransactionId(),
xid.getGlobalTransactionId()))
throw new XAException(XAException.XAER_PROTO);
if (!((currentContextTuple.getStatus() == TX_IDLE) ||
(currentContextTuple.getStatus() == TX_SUSPENDED)))
throw new XAException(XAException.XAER_PROTO);
if (((ContextTuple) currentContexts.get(Thread.currentThread()))
.getRollbackOnly()) {
// FIXME: Don't know if should be automatically rollbacked in that
// case
throw new XAException(XAException.XA_RBROLLBACK);
}
currentContextTuple.setStatus(TX_PREPARED);
return XAResource.XA_OK;
}
/**
* Obtain a list of prepared transaction branches from a resource manager.
* The transaction manager calls this method during recovery to obtain the
* list of transaction branches that are currently in prepared or
* heuristically completed states.
*
* @param flag One of TMSTARTRSCAN, TMENDRSCAN, TMNOFLAGS. TMNOFLAGS must
* be used when no other flags are set in flags.
* @return The resource manager returns zero or more XIDs for the
* transaction branches that are currently in a prepared or heuristically
* completed state. If an error occurs during the operation, the resource
* manager should throw the appropriate XAException.
* @exception XAException An error has occurred. Possible values are
* XAER_RMERR, XAER_RMFAIL, XAER_INVAL, and XAER_PROTO.
*/
public Xid[] recover(int flag)
throws XAException {
ContextTuple currentContextTuple =
(ContextTuple) currentContexts.get(Thread.currentThread());
Xid currentContext =
currentContextTuple!=null?currentContextTuple.getXid():null;
Vector list = new Vector();
if ((currentContextTuple.getStatus() == TX_PREPARED) &&
(currentContext != null))
list.addElement(currentContext);
return (Xid[]) list.toArray(new Xid[list.size()]);
}
/**
* Inform the resource manager to roll back work done on behalf of a
* transaction branch.
*
* @param xid A global transaction identifier
* @exception XAException An error has occurred
*/
public void rollback(Xid xid)
throws XAException {
ContextTuple currentContextTuple =
(ContextTuple) currentContexts.get(Thread.currentThread());
Xid currentContext =
currentContextTuple!=null?currentContextTuple.getXid():null;
if (currentContext == null)
throw new XAException(XAException.XAER_NOTA);
if (xid == null)
throw new XAException(XAException.XAER_INVAL);
if (!Arrays.equals(currentContext.getGlobalTransactionId(),
xid.getGlobalTransactionId()))
throw new XAException(XAException.XAER_PROTO);
currentContexts.remove(Thread.currentThread());
}
/**
* Set the current transaction timeout value for this XAResource instance.
*
* @param seconds the transaction timeout value in seconds.
* @return true if transaction timeout value is set successfully;
* otherwise false.
* @exception XAException An error has occurred. Possible exception
* values are XAER_RMERR, XAER_RMFAIL, or XAER_INVAL.
*/
public boolean setTransactionTimeout(int seconds)
throws XAException {
((ContextTuple) currentContexts.get(Thread.currentThread()))
.setTransactionTimeout(seconds);
return true;
}
/**
* Start work on behalf of a transaction branch specified in xid If
* TMJOIN is specified, the start is for joining a transaction previously
* seen by the resource manager. If TMRESUME is specified, the start is
* to resume a suspended transaction specified in the parameter xid. If
* neither TMJOIN nor TMRESUME is specified and the transaction specified
* by xid has previously been seen by the resource manager, the resource
* manager throws the XAException exception with XAER_DUPID error code.
*
* @param xid A global transaction identifier to be associated with the
* resource
* @param flags One of TMNOFLAGS, TMJOIN, or TMRESUME
* @exception XAException An error has occurred. Possible exceptions are
* XA_RB*, XAER_RMERR, XAER_RMFAIL, XAER_DUPID, XAER_OUTSIDE, XAER_NOTA,
* XAER_INVAL, or XAER_PROTO.
*/
public void start(Xid xid, int flags)
throws XAException {
ContextTuple currentContextTuple =
(ContextTuple) currentContexts.get(Thread.currentThread());
Xid currentContext =
currentContextTuple!=null?currentContextTuple.getXid():null;
if (xid == null)
throw new XAException(XAException.XAER_INVAL);
if ( (currentContext != null) &&
(!Arrays.equals(currentContext.getGlobalTransactionId(),
xid.getGlobalTransactionId())) )
throw new XAException(XAException.XAER_OUTSIDE);
switch (flags) {
case XAResource.TMNOFLAGS:
if (currentContext != null)
throw new XAException(XAException.XAER_INVAL);
currentContext = xid;
// is the idle status really ok ???
currentContexts.put
(Thread.currentThread(),new ContextTuple
(xid, TX_IDLE,
SlideTransactionManager.DEFAULT_TRANSACTION_TIMEOUT,
false));
break;
case XAResource.TMJOIN:
if (currentContext == null)
throw new XAException(XAException.XAER_NOTA);
if (!Arrays.equals(currentContext.getGlobalTransactionId(),
xid.getGlobalTransactionId()))
throw new XAException(XAException.XAER_INVAL);
break;
case XAResource.TMRESUME:
if (currentContext == null)
throw new XAException(XAException.XAER_NOTA);
if (currentContextTuple.getStatus() != TX_SUSPENDED)
throw new XAException(XAException.XAER_INVAL);
currentContextTuple.setStatus(TX_IDLE);
break;
}
}
// ------------------------------------------------ ContextTuple InnerClass
/**
* inner class to pack both the xid and the status
**/
private class ContextTuple {
private Xid xid;
private int status;
private int transactionTimeout;
private boolean rollbackOnly;
public ContextTuple(Xid xid, int status, int transactionTimeout,
boolean rollbackOnly) {
this.xid = xid;
setStatus(status);
setTransactionTimeout(transactionTimeout);
setRollbackOnly(rollbackOnly);
}
public Xid getXid() {
return xid;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public int getTransactionTimeout() {
return transactionTimeout;
}
public void setTransactionTimeout(int transactionTimeout) {
this.transactionTimeout = transactionTimeout;
}
public boolean getRollbackOnly() {
return rollbackOnly;
}
public void setRollbackOnly(boolean rollbackOnly) {
this.rollbackOnly = rollbackOnly;
}
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]