/* * JBoss, Home of Professional Open Source. * Copyright 2006, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.ejb; import java.rmi.RemoteException; import java.security.CodeSource; import java.security.Identity; import java.security.Policy; import java.security.Principal; import java.security.ProtectionDomain; import java.util.HashSet; import java.util.Iterator; import java.util.Properties; import java.util.Set; import java.util.Stack; import javax.ejb.EJBContext; import javax.ejb.EJBException; import javax.ejb.EJBHome; import javax.ejb.EJBLocalHome; import javax.ejb.TimerService; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.security.auth.Subject; import javax.security.jacc.EJBRoleRefPermission; import javax.security.jacc.PolicyContextException; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.NotSupportedException; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import javax.transaction.UserTransaction; import org.jboss.logging.Logger; import org.jboss.metadata.ApplicationMetaData; import org.jboss.metadata.BeanMetaData; import org.jboss.metadata.SecurityRoleRefMetaData; import org.jboss.security.RealmMapping; import org.jboss.security.RunAsIdentity; import org.jboss.security.SimplePrincipal; import org.jboss.tm.TransactionTimeoutConfiguration; import org.jboss.tm.usertx.client.ServerVMClientUserTransaction; //$Id: EnterpriseContext.java 58987 2006-12-12 09:53:18Z dimitris@jboss.org $ /** * The EnterpriseContext is used to associate EJB instances with * metadata about it. * * @author Rickard Öberg * @author Marc Fleury * @author Sebastien Alborini * @author Juha Lindfors * @author Ole Husgaard * @author Thomas Diesler * @version $Revision: 58987 $ * @see StatefulSessionEnterpriseContext * @see StatelessSessionEnterpriseContext * @see EntityEnterpriseContext */ public abstract class EnterpriseContext implements AllowedOperationsFlags { // Constants ----------------------------------------------------- // Attributes ---------------------------------------------------- /** * Instance logger. */ protected static Logger log = Logger.getLogger(EnterpriseContext.class); /** * 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(); /** * Holds one of the IN_METHOD constants, to indicate that we are in an ejb method * According to the EJB2.1 spec not all context methods can be accessed at all times * For example ctx.getPrimaryKey() should throw an IllegalStateException when called from within ejbCreate() */ private Stack inMethodStack = new Stack(); // 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; /* Clear the bean principal used for getCallerPrincipal and synch with the new call principal */ this.beanPrincipal = null; if( con.getSecurityManager() != null ) this.beanPrincipal = getCallerPrincipal(); } 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; } public Principal getCallerPrincipal() { EJBContextImpl ctxImpl = (EJBContextImpl) getEJBContext(); return ctxImpl.getCallerPrincipalInternal(); } /** * 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; this.inMethodStack.clear(); } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- protected boolean isContainerManagedTx() { BeanMetaData md = con.getBeanMetaData(); return md.isContainerManagedTx(); } protected boolean isUserManagedTx() { BeanMetaData md = con.getBeanMetaData(); return md.isContainerManagedTx() == false; } // Private ------------------------------------------------------- // Inner classes ------------------------------------------------- protected class EJBContextImpl implements EJBContext { private InitialContext ctx; private InitialContext getContext() { if (ctx==null) { try { ctx = new InitialContext(); } catch (NamingException e) { throw new RuntimeException(e); } } return ctx; } protected EJBContextImpl() { } public Object lookup(String name) { try { return getContext().lookup(name); } catch (NamingException ignored) { } return null; } /** * 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"); } public TimerService getTimerService() throws IllegalStateException { return getContainer().getTimerService(null); } /** * Get the Principal for the current caller. This method * cannot return null according to the ejb-spec. */ public Principal getCallerPrincipal() { return getCallerPrincipalInternal(); } /** * The implementation of getCallerPrincipal() * @return the caller principal */ Principal getCallerPrincipalInternal() { if( beanPrincipal == null ) { /* Get the run-as user or authenticated user. The run-as user is returned before any authenticated user. */ Principal caller = SecurityActions.getCallerPrincipal(); RealmMapping rm = con.getRealmMapping(); /* Apply any domain caller mapping. This should really only be done for non-run-as callers. */ if (rm != null) caller = rm.getPrincipal(caller); if( caller == null ) { /* Try the incoming request principal. This is needed if a client clears the current caller association and and an interceptor calls getCallerPrincipal as the call stack unwinds. */ if( principal != null ) { if( rm != null ) caller = rm.getPrincipal(principal); else caller = principal; } // Check for an unauthenticated principal value else { ApplicationMetaData appMetaData = con.getBeanMetaData().getApplicationMetaData(); String name = appMetaData.getUnauthenticatedPrincipal(); if (name != null) caller = new SimplePrincipal(name); } } if( caller == null ) { throw new IllegalStateException("No valid security context for the caller identity"); } /* Save caller as the beanPrincipal for reuse if getCallerPrincipal is called as the stack unwinds. An example of where this would occur is the cmp2 audit layer. */ beanPrincipal = caller; } return beanPrincipal; } public EJBHome getEJBHome() { EJBProxyFactory proxyFactory = con.getProxyFactory(); if (proxyFactory == null) throw new IllegalStateException("No remote home defined."); return (EJBHome) proxyFactory.getEJBHome(); } public EJBLocalHome getEJBLocalHome() { if (con.getLocalHomeClass() == null) throw new IllegalStateException("No local home defined."); if (con instanceof EntityContainer) return ((EntityContainer) con).getLocalProxyFactory().getEJBLocalHome(); else if (con instanceof StatelessSessionContainer) return ((StatelessSessionContainer) con).getLocalProxyFactory().getEJBLocalHome(); else if (con instanceof StatefulSessionContainer) 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("getRollbackOnly() not allowed for BMT beans."); try { TransactionManager tm = con.getTransactionManager(); // The getRollbackOnly and setRollBackOnly method of the SessionContext interface should be used // only in the session bean methods that execute in the context of a transaction. if (tm.getTransaction() == null) throw new IllegalStateException("getRollbackOnly() not allowed without a transaction."); // JBAS-3847, consider an asynchronous rollback due to timeout int status = tm.getStatus(); return status == Status.STATUS_MARKED_ROLLBACK || status == Status.STATUS_ROLLING_BACK || status == Status.STATUS_ROLLEDBACK; } 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("setRollbackOnly() not allowed for BMT beans."); try { TransactionManager tm = con.getTransactionManager(); // The getRollbackOnly and setRollBackOnly method of the SessionContext interface should be used // only in the session bean methods that execute in the context of a transaction. if (tm.getTransaction() == null) throw new IllegalStateException("setRollbackOnly() not allowed without a transaction."); tm.setRollbackOnly(); } catch (SystemException e) { log.warn("failed to set rollback only; ignoring", e); } } /** * @deprecated */ public boolean isCallerInRole(Identity id) { throw new EJBException("Deprecated"); } /** * Checks if the current caller has a given role. * The current caller is either the principal associated with the method invocation * or the current run-as principal. */ public boolean isCallerInRole(String roleName) { //Check if JACC is installed if(con.isJaccEnabled()) return this.isCallerInRoleCheckForJacc(roleName); // Check the caller of this beans run-as identity RunAsIdentity runAsIdentity = SecurityActions.peekRunAsIdentity(1); if (principal == null && runAsIdentity == 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(roleName)) { roleName = meta.getLink(); matchFound = true; break; } } if (!matchFound) log.warn("no match found for security role " + roleName + " in the deployment descriptor for ejb " + getContainer().getBeanMetaData().getEjbName()); HashSet set = new HashSet(); set.add(new SimplePrincipal(roleName)); if (runAsIdentity == null) return rm.doesUserHaveRole(principal, set); else return runAsIdentity.doesUserHaveRole(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; } //Private Methods for the inner class - EJBContextImpl private boolean isCallerInRoleCheckForJacc(String roleName) { SimplePrincipal rolePrincipal = new SimplePrincipal(roleName); //This has to be the EJBRoleRefPermission String ejbName = con.getBeanMetaData().getEjbName(); EJBRoleRefPermission ejbRoleRefPerm = new EJBRoleRefPermission(ejbName,roleName); //Get the caller Subject caller; try { caller = SecurityActions.getContextSubject(); } catch (PolicyContextException e) { throw new RuntimeException(e); } Principal[] principals = null; if( caller != null ) { // Get the caller principals Set principalsSet = caller.getPrincipals(); principals = new Principal[principalsSet.size()]; principalsSet.toArray(principals); } CodeSource ejbCS = con.getBeanClass().getProtectionDomain().getCodeSource(); ProtectionDomain pd = new ProtectionDomain (ejbCS, null, null, principals); return Policy.getPolicy().implies(pd, ejbRoleRefPerm); } } // Inner classes ------------------------------------------------- protected class UserTransactionImpl implements UserTransaction { /** * Timeout value in seconds for new transactions started by this bean instance. */ private int timeout = 0; /** * Whether trace is enabled */ boolean trace; public UserTransactionImpl() { trace = log.isTraceEnabled(); if (trace) log.trace("new UserTx: " + this); } public void begin() throws NotSupportedException, SystemException { TransactionManager tm = con.getTransactionManager(); int oldTimeout = -1; if (tm instanceof TransactionTimeoutConfiguration) oldTimeout = ((TransactionTimeoutConfiguration) tm).getTransactionTimeout(); // Set the timeout value tm.setTransactionTimeout(timeout); try { // Start the transaction tm.begin(); //notify checked out connections if (tsl != null) tsl.userTransactionStarted(); Transaction tx = tm.getTransaction(); if (trace) log.trace("UserTx begin: " + tx); // keep track of the transaction in enterprise context for BMT setTransaction(tx); } finally { // Reset the transaction timeout (if we know what it was) if (oldTimeout != -1) tm.setTransactionTimeout(oldTimeout); } } public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException { TransactionManager tm = con.getTransactionManager(); try { Transaction tx = tm.getTransaction(); if (trace) log.trace("UserTx commit: " + tx); int status = tm.getStatus(); tm.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 { TransactionManager tm = con.getTransactionManager(); try { Transaction tx = tm.getTransaction(); if (trace) log.trace("UserTx rollback: " + tx); tm.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 { TransactionManager tm = con.getTransactionManager(); Transaction tx = tm.getTransaction(); if (trace) log.trace("UserTx setRollbackOnly: " + tx); tm.setRollbackOnly(); } public int getStatus() throws SystemException { TransactionManager tm = con.getTransactionManager(); return tm.getStatus(); } /** * Set the transaction timeout value for new transactions * started by this instance. */ public void setTransactionTimeout(int seconds) throws SystemException { timeout = seconds; } } }