/*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.ejb;
import java.rmi.RemoteException;
import java.security.Identity;
import java.security.Principal;
import java.util.Properties;
import java.util.HashSet;
import java.util.Iterator;
import javax.ejb.EJBLocalHome;
import javax.ejb.EJBHome;
import javax.ejb.EJBContext;
import javax.ejb.EJBException;
import javax.transaction.Status;
import javax.transaction.Transaction;
import javax.transaction.UserTransaction;
import javax.transaction.TransactionManager;
import javax.transaction.Synchronization;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import javax.transaction.RollbackException;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import org.jboss.logging.Logger;
import org.jboss.metadata.BeanMetaData;
import org.jboss.metadata.ApplicationMetaData;
import org.jboss.metadata.SecurityRoleRefMetaData;
import org.jboss.security.RealmMapping;
import org.jboss.security.SimplePrincipal;
import org.jboss.tm.usertx.client.ServerVMClientUserTransaction;
/**
* The EnterpriseContext is used to associate EJB instances with
* metadata about it.
*
* @see StatefulSessionEnterpriseContext
* @see StatelessSessionEnterpriseContext
* @see EntityEnterpriseContext
*
* @author Rickard Öberg
* @author Marc Fleury
* @author Sebastien Alborini
* @author Juha Lindfors
* @author Ole Husgaard
* @version $Revision: 1.50 $
*
* Revisions:
* 2001/06/29: marcf
* - Added txLock to permit locking and most of all notifying on tx
* demarcation only
*/
public abstract class EnterpriseContext
{
// Constants -----------------------------------------------------
// Attributes ----------------------------------------------------
/** Instance logger. */
protected Logger log = Logger.getLogger(this.getClass());
/** The EJB instance */
Object instance;
/** The container using this context */
Container con;
/**
* Set to the synchronization currently associated with this context.
* May be null
*/
Synchronization synch;
/** The transaction associated with the instance */
Transaction transaction;
/** The principal associated with the call */
private Principal principal;
/** The principal for the bean associated with the call */
private Principal beanPrincipal;
/** Only StatelessSession beans have no Id, stateful and entity do */
Object id;
/** The instance is being used. This locks it's state */
int locked = 0;
/** The instance is used in a transaction, synchronized methods on the tx */
Object txLock = new Object();
// Static --------------------------------------------------------
//Registration for CachedConnectionManager so our UserTx can notify
//on tx started.
private static ServerVMClientUserTransaction.UserTransactionStartedListener tsl;
/**
* The setUserTransactionStartedListener method is called by
* CachedConnectionManager on start and stop. The tsl is notified on
* UserTransaction.begin so it (the CachedConnectionManager) can enroll
* connections that are already checked out.
*
* @param newTsl a ServerVMClientUserTransaction.UserTransactionStartedListener value
*/
public static void setUserTransactionStartedListener(ServerVMClientUserTransaction.UserTransactionStartedListener newTsl)
{
tsl = newTsl;
}
// Constructors --------------------------------------------------
public EnterpriseContext(Object instance, Container con)
{
this.instance = instance;
this.con = con;
}
// Public --------------------------------------------------------
public Object getInstance()
{
return instance;
}
/**
* Gets the container that manages the wrapped bean.
*/
public Container getContainer() {
return con;
}
public abstract void discard()
throws RemoteException;
/**
* Get the EJBContext object
*/
public abstract EJBContext getEJBContext();
public void setId(Object id) {
this.id = id;
}
public Object getId() {
return id;
}
public Object getTxLock() {
return txLock;
}
public void setTransaction(Transaction transaction) {
// DEBUG log.debug("EnterpriseContext.setTransaction "+((transaction == null) ? "null" : Integer.toString(transaction.hashCode())));
this.transaction = transaction;
}
public Transaction getTransaction() {
return transaction;
}
public void setPrincipal(Principal principal) {
this.principal = principal;
this.beanPrincipal = null;
}
public void lock()
{
locked ++;
//new Exception().printStackTrace();
//DEBUG log.debug("EnterpriseContext.lock() "+hashCode()+" "+locked);
}
public void unlock() {
// release a lock
locked --;
//new Exception().printStackTrace();
if (locked <0) {
// new Exception().printStackTrace();
log.error("locked < 0", new Throwable());
}
//DEBUG log.debug("EnterpriseContext.unlock() "+hashCode()+" "+locked);
}
public boolean isLocked() {
//DEBUG log.debug("EnterpriseContext.isLocked() "+hashCode()+" at "+locked);
return locked != 0;
}
/**
* before reusing this context we clear it of previous state called
* by pool.free()
*/
public void clear() {
this.id = null;
this.locked = 0;
this.principal = null;
this.beanPrincipal = null;
this.synch = null;
this.transaction = null;
}
// Package protected ---------------------------------------------
// Protected -----------------------------------------------------
protected boolean isContainerManagedTx()
{
BeanMetaData md = (BeanMetaData)con.getBeanMetaData();
return md.isContainerManagedTx();
}
// Private -------------------------------------------------------
// Inner classes -------------------------------------------------
protected class EJBContextImpl
implements EJBContext
{
/**
* A per-bean instance UserTransaction instance cached after the
* first call to getUserTransaction().
*/
private UserTransactionImpl userTransaction = null;
/**
* @deprecated
*/
public Identity getCallerIdentity()
{
throw new EJBException("Deprecated");
}
/** Get the Principal for the current caller. This method
cannot return null according to the ejb-spec.
*/
public Principal getCallerPrincipal()
{
if( beanPrincipal == null )
{
RealmMapping rm = con.getRealmMapping();
if( principal != null )
{
if( rm != null )
beanPrincipal = rm.getPrincipal(principal);
else
beanPrincipal = principal;
}
else if( rm != null )
{ // Let the RealmMapping map the null principal
beanPrincipal = rm.getPrincipal(principal);
}
else
{ // Check for a unauthenticated principal value
ApplicationMetaData appMetaData = con.getBeanMetaData().getApplicationMetaData();
String name = appMetaData.getUnauthenticatedPrincipal();
if( name != null )
beanPrincipal = new SimplePrincipal(name);
}
}
if( beanPrincipal == null )
throw new IllegalStateException("No security context set");
return beanPrincipal;
}
public EJBHome getEJBHome()
{
if (con instanceof EntityContainer)
{
if (((EntityContainer)con).getProxyFactory()==null)
throw new IllegalStateException( "No remote home defined." );
return (EJBHome)((EntityContainer)con).getProxyFactory().getEJBHome();
}
else if (con instanceof StatelessSessionContainer)
{
if (((StatelessSessionContainer)con).getProxyFactory()==null)
throw new IllegalStateException( "No remote home defined." );
return (EJBHome) ((StatelessSessionContainer)con).getProxyFactory().getEJBHome();
}
else if (con instanceof StatefulSessionContainer)
{
if (((StatefulSessionContainer)con).getProxyFactory()==null)
throw new IllegalStateException( "No remote home defined." );
return (EJBHome) ((StatefulSessionContainer)con).getProxyFactory().getEJBHome();
}
// Should never get here
throw new EJBException("No EJBHome available (BUG!)");
}
public EJBLocalHome getEJBLocalHome()
{
if (con instanceof EntityContainer)
{
if (((EntityContainer)con).getLocalHomeClass()==null)
throw new IllegalStateException( "No local home defined." );
return ((EntityContainer)con).getLocalProxyFactory().getEJBLocalHome();
}
else if (con instanceof StatelessSessionContainer)
{
if (((StatelessSessionContainer)con).getLocalHomeClass()==null)
throw new IllegalStateException( "No local home defined." );
return ((StatelessSessionContainer)con).getLocalProxyFactory().getEJBLocalHome();
}
else if (con instanceof StatefulSessionContainer)
{
if (((StatefulSessionContainer)con).getLocalHomeClass()==null)
throw new IllegalStateException( "No local home defined." );
return ((StatefulSessionContainer)con).getLocalProxyFactory().getEJBLocalHome();
}
// Should never get here
throw new EJBException("No EJBLocalHome available (BUG!)");
}
/**
* @deprecated
*/
public Properties getEnvironment()
{
throw new EJBException("Deprecated");
}
public boolean getRollbackOnly()
{
// EJB1.1 11.6.1: Must throw IllegalStateException if BMT
if (con.getBeanMetaData().isBeanManagedTx())
throw new IllegalStateException("ctx.getRollbackOnly() not allowed for BMT beans.");
try {
return con.getTransactionManager().getStatus() == Status.STATUS_MARKED_ROLLBACK;
} catch (SystemException e) {
log.warn("failed to get tx manager status; ignoring", e);
return true;
}
}
public void setRollbackOnly()
{
// EJB1.1 11.6.1: Must throw IllegalStateException if BMT
if (con.getBeanMetaData().isBeanManagedTx())
throw new IllegalStateException("ctx.setRollbackOnly() not allowed for BMT beans.");
try {
con.getTransactionManager().setRollbackOnly();
} catch (IllegalStateException e) {
} catch (SystemException e) {
log.warn("failed to set rollback only; ignoring", e);
}
}
/**
* @deprecated
*/
public boolean isCallerInRole(Identity id)
{
throw new EJBException("Deprecated");
}
// TODO - how to handle this best?
public boolean isCallerInRole(String id)
{
if (principal == null)
return false;
RealmMapping rm = con.getRealmMapping();
if( rm == null )
{
String msg = "isCallerInRole() called with no security context. "
+ "Check that a security-domain has been set for the application.";
throw new IllegalStateException(msg);
}
// Map the role name used by Bean Provider to the security role
// link in the deployment descriptor. The EJB 1.1 spec requires
// the security role refs in the descriptor but for backward
// compability we're not enforcing this requirement.
//
// TODO (2.3): add a conditional check using jboss.xml element
// which will throw an exception in case no matching
// security ref is found.
Iterator it = getContainer().getBeanMetaData().getSecurityRoleReferences();
boolean matchFound = false;
while (it.hasNext())
{
SecurityRoleRefMetaData meta = (SecurityRoleRefMetaData)it.next();
if (meta.getName().equals(id))
{
id = meta.getLink();
matchFound = true;
break;
}
}
if (!matchFound)
log.warn("no match found for security role " + id +
" in the deployment descriptor.");
HashSet set = new HashSet();
set.add( new SimplePrincipal(id) );
return rm.doesUserHaveRole( principal, set );
}
public UserTransaction getUserTransaction()
{
if (userTransaction == null)
{
if (isContainerManagedTx()) {
throw new IllegalStateException
("CMT beans are not allowed to get a UserTransaction");
}
userTransaction = new UserTransactionImpl();
}
return userTransaction;
}
}
// Inner classes -------------------------------------------------
protected class UserTransactionImpl
implements UserTransaction
{
/**
* Timeout value in seconds for new transactions started
* by this bean instance.
*/
private int timeout = 0;
public void begin()
throws NotSupportedException, SystemException
{
TransactionManager tm = con.getTransactionManager();
// Set the timeout value
tm.setTransactionTimeout(timeout);
// Start the transaction
tm.begin();
//notify checked out connections
if (tsl != null)
{
tsl.userTransactionStarted();
} // end of if ()
// keep track of the transaction in enterprise context for BMT
setTransaction(tm.getTransaction());
}
public void commit()
throws RollbackException,
HeuristicMixedException,
HeuristicRollbackException,
java.lang.SecurityException,
java.lang.IllegalStateException,
SystemException
{
try {
con.getTransactionManager().commit();
} finally {
// According to the spec, after commit and rollback was called on
// UserTransaction, the thread is associated with no transaction.
// Since the BMT Tx interceptor will associate and resume the tx
// from the context with the thread that comes in
// on a subsequent invocation, we must set the context transaction to null
setTransaction(null);
}
}
public void rollback()
throws IllegalStateException, SecurityException, SystemException
{
try {
con.getTransactionManager().rollback();
} finally {
// According to the spec, after commit and rollback was called on
// UserTransaction, the thread is associated with no transaction.
// Since the BMT Tx interceptor will associate and resume the tx
// from the context with the thread that comes in
// on a subsequent invocation, we must set the context transaction to null
setTransaction(null);
}
}
public void setRollbackOnly()
throws IllegalStateException, SystemException
{
con.getTransactionManager().setRollbackOnly();
}
public int getStatus()
throws SystemException
{
return con.getTransactionManager().getStatus();
}
/**
* Set the transaction timeout value for new transactions
* started by this instance.
*/
public void setTransactionTimeout(int seconds)
throws SystemException
{
timeout = seconds;
}
}
}