001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ejb;
023:
024: import java.rmi.RemoteException;
025: import java.security.CodeSource;
026: import java.security.Identity;
027: import java.security.Policy;
028: import java.security.Principal;
029: import java.security.ProtectionDomain;
030: import java.util.HashSet;
031: import java.util.Iterator;
032: import java.util.Properties;
033: import java.util.Set;
034: import java.util.Stack;
035:
036: import javax.ejb.EJBContext;
037: import javax.ejb.EJBException;
038: import javax.ejb.EJBHome;
039: import javax.ejb.EJBLocalHome;
040: import javax.ejb.TimerService;
041: import javax.naming.InitialContext;
042: import javax.naming.NamingException;
043: import javax.security.auth.Subject;
044: import javax.security.jacc.EJBRoleRefPermission;
045: import javax.security.jacc.PolicyContextException;
046: import javax.transaction.HeuristicMixedException;
047: import javax.transaction.HeuristicRollbackException;
048: import javax.transaction.NotSupportedException;
049: import javax.transaction.RollbackException;
050: import javax.transaction.Status;
051: import javax.transaction.Synchronization;
052: import javax.transaction.SystemException;
053: import javax.transaction.Transaction;
054: import javax.transaction.TransactionManager;
055: import javax.transaction.UserTransaction;
056:
057: import org.jboss.logging.Logger;
058: import org.jboss.metadata.ApplicationMetaData;
059: import org.jboss.metadata.BeanMetaData;
060: import org.jboss.metadata.SecurityRoleRefMetaData;
061: import org.jboss.security.RealmMapping;
062: import org.jboss.security.RunAsIdentity;
063: import org.jboss.security.SimplePrincipal;
064: import org.jboss.tm.TransactionTimeoutConfiguration;
065: import org.jboss.tm.usertx.client.ServerVMClientUserTransaction;
066:
067: //$Id: EnterpriseContext.java 58987 2006-12-12 09:53:18Z dimitris@jboss.org $
068:
069: /**
070: * The EnterpriseContext is used to associate EJB instances with
071: * metadata about it.
072: *
073: * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Öberg</a>
074: * @author <a href="mailto:marc.fleury@telkel.com">Marc Fleury</a>
075: * @author <a href="mailto:sebastien.alborini@m4x.org">Sebastien Alborini</a>
076: * @author <a href="mailto:juha@jboss.org">Juha Lindfors</a>
077: * @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a>
078: * @author <a href="mailto:thomas.diesler@jboss.org">Thomas Diesler</a>
079: * @version $Revision: 58987 $
080: * @see StatefulSessionEnterpriseContext
081: * @see StatelessSessionEnterpriseContext
082: * @see EntityEnterpriseContext
083: */
084: public abstract class EnterpriseContext implements
085: AllowedOperationsFlags {
086: // Constants -----------------------------------------------------
087:
088: // Attributes ----------------------------------------------------
089:
090: /**
091: * Instance logger.
092: */
093: protected static Logger log = Logger
094: .getLogger(EnterpriseContext.class);
095:
096: /**
097: * The EJB instance
098: */
099: Object instance;
100:
101: /**
102: * The container using this context
103: */
104: Container con;
105:
106: /**
107: * Set to the synchronization currently associated with this context.
108: * May be null
109: */
110: Synchronization synch;
111:
112: /**
113: * The transaction associated with the instance
114: */
115: Transaction transaction;
116:
117: /**
118: * The principal associated with the call
119: */
120: private Principal principal;
121:
122: /**
123: * The principal for the bean associated with the call
124: */
125: private Principal beanPrincipal;
126:
127: /**
128: * Only StatelessSession beans have no Id, stateful and entity do
129: */
130: Object id;
131:
132: /**
133: * The instance is being used. This locks it's state
134: */
135: int locked = 0;
136:
137: /**
138: * The instance is used in a transaction, synchronized methods on the tx
139: */
140: Object txLock = new Object();
141:
142: /**
143: * Holds one of the IN_METHOD constants, to indicate that we are in an ejb method
144: * According to the EJB2.1 spec not all context methods can be accessed at all times
145: * For example ctx.getPrimaryKey() should throw an IllegalStateException when called from within ejbCreate()
146: */
147: private Stack inMethodStack = new Stack();
148:
149: // Static --------------------------------------------------------
150: //Registration for CachedConnectionManager so our UserTx can notify
151: //on tx started.
152: private static ServerVMClientUserTransaction.UserTransactionStartedListener tsl;
153:
154: /**
155: * The <code>setUserTransactionStartedListener</code> method is called by
156: * CachedConnectionManager on start and stop. The tsl is notified on
157: * UserTransaction.begin so it (the CachedConnectionManager) can enroll
158: * connections that are already checked out.
159: *
160: * @param newTsl a <code>ServerVMClientUserTransaction.UserTransactionStartedListener</code> value
161: */
162: public static void setUserTransactionStartedListener(
163: ServerVMClientUserTransaction.UserTransactionStartedListener newTsl) {
164: tsl = newTsl;
165: }
166:
167: // Constructors --------------------------------------------------
168:
169: public EnterpriseContext(Object instance, Container con) {
170: this .instance = instance;
171: this .con = con;
172: }
173:
174: // Public --------------------------------------------------------
175:
176: public Object getInstance() {
177: return instance;
178: }
179:
180: /**
181: * Gets the container that manages the wrapped bean.
182: */
183: public Container getContainer() {
184: return con;
185: }
186:
187: public abstract void discard() throws RemoteException;
188:
189: /**
190: * Get the EJBContext object
191: */
192: public abstract EJBContext getEJBContext();
193:
194: public void setId(Object id) {
195: this .id = id;
196: }
197:
198: public Object getId() {
199: return id;
200: }
201:
202: public Object getTxLock() {
203: return txLock;
204: }
205:
206: public void setTransaction(Transaction transaction) {
207: // DEBUG log.debug("EnterpriseContext.setTransaction "+((transaction == null) ? "null" : Integer.toString(transaction.hashCode())));
208: this .transaction = transaction;
209: }
210:
211: public Transaction getTransaction() {
212: return transaction;
213: }
214:
215: public void setPrincipal(Principal principal) {
216: this .principal = principal;
217: /* Clear the bean principal used for getCallerPrincipal and synch with the
218: new call principal
219: */
220: this .beanPrincipal = null;
221: if (con.getSecurityManager() != null)
222: this .beanPrincipal = getCallerPrincipal();
223: }
224:
225: public void lock() {
226: locked++;
227: //new Exception().printStackTrace();
228: //DEBUG log.debug("EnterpriseContext.lock() "+hashCode()+" "+locked);
229: }
230:
231: public void unlock() {
232:
233: // release a lock
234: locked--;
235:
236: //new Exception().printStackTrace();
237: if (locked < 0) {
238: // new Exception().printStackTrace();
239: log.error("locked < 0", new Throwable());
240: }
241:
242: //DEBUG log.debug("EnterpriseContext.unlock() "+hashCode()+" "+locked);
243: }
244:
245: public boolean isLocked() {
246:
247: //DEBUG log.debug("EnterpriseContext.isLocked() "+hashCode()+" at "+locked);
248: return locked != 0;
249: }
250:
251: public Principal getCallerPrincipal() {
252: EJBContextImpl ctxImpl = (EJBContextImpl) getEJBContext();
253: return ctxImpl.getCallerPrincipalInternal();
254: }
255:
256: /**
257: * before reusing this context we clear it of previous state called
258: * by pool.free()
259: */
260: public void clear() {
261: this .id = null;
262: this .locked = 0;
263: this .principal = null;
264: this .beanPrincipal = null;
265: this .synch = null;
266: this .transaction = null;
267: this .inMethodStack.clear();
268: }
269:
270: // Package protected ---------------------------------------------
271:
272: // Protected -----------------------------------------------------
273:
274: protected boolean isContainerManagedTx() {
275: BeanMetaData md = con.getBeanMetaData();
276: return md.isContainerManagedTx();
277: }
278:
279: protected boolean isUserManagedTx() {
280: BeanMetaData md = con.getBeanMetaData();
281: return md.isContainerManagedTx() == false;
282: }
283:
284: // Private -------------------------------------------------------
285:
286: // Inner classes -------------------------------------------------
287:
288: protected class EJBContextImpl implements EJBContext {
289: private InitialContext ctx;
290:
291: private InitialContext getContext() {
292: if (ctx == null) {
293: try {
294: ctx = new InitialContext();
295: } catch (NamingException e) {
296: throw new RuntimeException(e);
297: }
298: }
299:
300: return ctx;
301: }
302:
303: protected EJBContextImpl() {
304: }
305:
306: public Object lookup(String name) {
307: try {
308: return getContext().lookup(name);
309: } catch (NamingException ignored) {
310: }
311: return null;
312: }
313:
314: /**
315: * A per-bean instance UserTransaction instance cached after the
316: * first call to <code>getUserTransaction()</code>.
317: */
318: private UserTransactionImpl userTransaction = null;
319:
320: /**
321: * @deprecated
322: */
323: public Identity getCallerIdentity() {
324: throw new EJBException("Deprecated");
325: }
326:
327: public TimerService getTimerService()
328: throws IllegalStateException {
329: return getContainer().getTimerService(null);
330: }
331:
332: /**
333: * Get the Principal for the current caller. This method
334: * cannot return null according to the ejb-spec.
335: */
336: public Principal getCallerPrincipal() {
337: return getCallerPrincipalInternal();
338: }
339:
340: /**
341: * The implementation of getCallerPrincipal()
342: * @return the caller principal
343: */
344: Principal getCallerPrincipalInternal() {
345: if (beanPrincipal == null) {
346: /* Get the run-as user or authenticated user. The run-as user is
347: returned before any authenticated user.
348: */
349: Principal caller = SecurityActions.getCallerPrincipal();
350: RealmMapping rm = con.getRealmMapping();
351: /* Apply any domain caller mapping. This should really only be
352: done for non-run-as callers.
353: */
354: if (rm != null)
355: caller = rm.getPrincipal(caller);
356: if (caller == null) {
357: /* Try the incoming request principal. This is needed if a client
358: clears the current caller association and and an interceptor calls
359: getCallerPrincipal as the call stack unwinds.
360: */
361: if (principal != null) {
362: if (rm != null)
363: caller = rm.getPrincipal(principal);
364: else
365: caller = principal;
366: }
367: // Check for an unauthenticated principal value
368: else {
369: ApplicationMetaData appMetaData = con
370: .getBeanMetaData()
371: .getApplicationMetaData();
372: String name = appMetaData
373: .getUnauthenticatedPrincipal();
374: if (name != null)
375: caller = new SimplePrincipal(name);
376: }
377: }
378:
379: if (caller == null) {
380: throw new IllegalStateException(
381: "No valid security context for the caller identity");
382: }
383: /* Save caller as the beanPrincipal for reuse if getCallerPrincipal is called as the
384: stack unwinds. An example of where this would occur is the cmp2 audit layer.
385: */
386: beanPrincipal = caller;
387: }
388: return beanPrincipal;
389: }
390:
391: public EJBHome getEJBHome() {
392: EJBProxyFactory proxyFactory = con.getProxyFactory();
393: if (proxyFactory == null)
394: throw new IllegalStateException(
395: "No remote home defined.");
396:
397: return (EJBHome) proxyFactory.getEJBHome();
398: }
399:
400: public EJBLocalHome getEJBLocalHome() {
401: if (con.getLocalHomeClass() == null)
402: throw new IllegalStateException(
403: "No local home defined.");
404:
405: if (con instanceof EntityContainer)
406: return ((EntityContainer) con).getLocalProxyFactory()
407: .getEJBLocalHome();
408: else if (con instanceof StatelessSessionContainer)
409: return ((StatelessSessionContainer) con)
410: .getLocalProxyFactory().getEJBLocalHome();
411: else if (con instanceof StatefulSessionContainer)
412: return ((StatefulSessionContainer) con)
413: .getLocalProxyFactory().getEJBLocalHome();
414:
415: // Should never get here
416: throw new EJBException("No EJBLocalHome available (BUG!)");
417: }
418:
419: /**
420: * @deprecated
421: */
422: public Properties getEnvironment() {
423: throw new EJBException("Deprecated");
424: }
425:
426: public boolean getRollbackOnly() {
427: // EJB1.1 11.6.1: Must throw IllegalStateException if BMT
428: if (con.getBeanMetaData().isBeanManagedTx())
429: throw new IllegalStateException(
430: "getRollbackOnly() not allowed for BMT beans.");
431:
432: try {
433: TransactionManager tm = con.getTransactionManager();
434:
435: // The getRollbackOnly and setRollBackOnly method of the SessionContext interface should be used
436: // only in the session bean methods that execute in the context of a transaction.
437: if (tm.getTransaction() == null)
438: throw new IllegalStateException(
439: "getRollbackOnly() not allowed without a transaction.");
440:
441: // JBAS-3847, consider an asynchronous rollback due to timeout
442: int status = tm.getStatus();
443: return status == Status.STATUS_MARKED_ROLLBACK
444: || status == Status.STATUS_ROLLING_BACK
445: || status == Status.STATUS_ROLLEDBACK;
446: } catch (SystemException e) {
447: log
448: .warn(
449: "failed to get tx manager status; ignoring",
450: e);
451: return true;
452: }
453: }
454:
455: public void setRollbackOnly() {
456: // EJB1.1 11.6.1: Must throw IllegalStateException if BMT
457: if (con.getBeanMetaData().isBeanManagedTx())
458: throw new IllegalStateException(
459: "setRollbackOnly() not allowed for BMT beans.");
460:
461: try {
462: TransactionManager tm = con.getTransactionManager();
463:
464: // The getRollbackOnly and setRollBackOnly method of the SessionContext interface should be used
465: // only in the session bean methods that execute in the context of a transaction.
466: if (tm.getTransaction() == null)
467: throw new IllegalStateException(
468: "setRollbackOnly() not allowed without a transaction.");
469:
470: tm.setRollbackOnly();
471: } catch (SystemException e) {
472: log.warn("failed to set rollback only; ignoring", e);
473: }
474: }
475:
476: /**
477: * @deprecated
478: */
479: public boolean isCallerInRole(Identity id) {
480: throw new EJBException("Deprecated");
481: }
482:
483: /**
484: * Checks if the current caller has a given role.
485: * The current caller is either the principal associated with the method invocation
486: * or the current run-as principal.
487: */
488: public boolean isCallerInRole(String roleName) {
489: //Check if JACC is installed
490: if (con.isJaccEnabled())
491: return this .isCallerInRoleCheckForJacc(roleName);
492:
493: // Check the caller of this beans run-as identity
494: RunAsIdentity runAsIdentity = SecurityActions
495: .peekRunAsIdentity(1);
496: if (principal == null && runAsIdentity == null)
497: return false;
498:
499: RealmMapping rm = con.getRealmMapping();
500: if (rm == null) {
501: String msg = "isCallerInRole() called with no security context. "
502: + "Check that a security-domain has been set for the application.";
503: throw new IllegalStateException(msg);
504: }
505:
506: // Map the role name used by Bean Provider to the security role
507: // link in the deployment descriptor. The EJB 1.1 spec requires
508: // the security role refs in the descriptor but for backward
509: // compability we're not enforcing this requirement.
510: //
511: // TODO (2.3): add a conditional check using jboss.xml <enforce-ejb-restrictions> element
512: // which will throw an exception in case no matching
513: // security ref is found.
514: Iterator it = getContainer().getBeanMetaData()
515: .getSecurityRoleReferences();
516: boolean matchFound = false;
517:
518: while (it.hasNext()) {
519: SecurityRoleRefMetaData meta = (SecurityRoleRefMetaData) it
520: .next();
521: if (meta.getName().equals(roleName)) {
522: roleName = meta.getLink();
523: matchFound = true;
524: break;
525: }
526: }
527:
528: if (!matchFound)
529: log
530: .warn("no match found for security role "
531: + roleName
532: + " in the deployment descriptor for ejb "
533: + getContainer().getBeanMetaData()
534: .getEjbName());
535:
536: HashSet set = new HashSet();
537: set.add(new SimplePrincipal(roleName));
538:
539: if (runAsIdentity == null)
540: return rm.doesUserHaveRole(principal, set);
541: else
542: return runAsIdentity.doesUserHaveRole(set);
543: }
544:
545: public UserTransaction getUserTransaction() {
546: if (userTransaction == null) {
547: if (isContainerManagedTx()) {
548: throw new IllegalStateException(
549: "CMT beans are not allowed to get a UserTransaction");
550: }
551:
552: userTransaction = new UserTransactionImpl();
553: }
554:
555: return userTransaction;
556: }
557:
558: //Private Methods for the inner class - EJBContextImpl
559: private boolean isCallerInRoleCheckForJacc(String roleName) {
560: SimplePrincipal rolePrincipal = new SimplePrincipal(
561: roleName);
562: //This has to be the EJBRoleRefPermission
563: String ejbName = con.getBeanMetaData().getEjbName();
564: EJBRoleRefPermission ejbRoleRefPerm = new EJBRoleRefPermission(
565: ejbName, roleName);
566: //Get the caller
567: Subject caller;
568: try {
569: caller = SecurityActions.getContextSubject();
570: } catch (PolicyContextException e) {
571: throw new RuntimeException(e);
572: }
573: Principal[] principals = null;
574: if (caller != null) {
575: // Get the caller principals
576: Set principalsSet = caller.getPrincipals();
577: principals = new Principal[principalsSet.size()];
578: principalsSet.toArray(principals);
579: }
580: CodeSource ejbCS = con.getBeanClass().getProtectionDomain()
581: .getCodeSource();
582: ProtectionDomain pd = new ProtectionDomain(ejbCS, null,
583: null, principals);
584: return Policy.getPolicy().implies(pd, ejbRoleRefPerm);
585: }
586: }
587:
588: // Inner classes -------------------------------------------------
589:
590: protected class UserTransactionImpl implements UserTransaction {
591: /**
592: * Timeout value in seconds for new transactions started by this bean instance.
593: */
594: private int timeout = 0;
595:
596: /**
597: * Whether trace is enabled
598: */
599: boolean trace;
600:
601: public UserTransactionImpl() {
602: trace = log.isTraceEnabled();
603: if (trace)
604: log.trace("new UserTx: " + this );
605: }
606:
607: public void begin() throws NotSupportedException,
608: SystemException {
609: TransactionManager tm = con.getTransactionManager();
610:
611: int oldTimeout = -1;
612: if (tm instanceof TransactionTimeoutConfiguration)
613: oldTimeout = ((TransactionTimeoutConfiguration) tm)
614: .getTransactionTimeout();
615:
616: // Set the timeout value
617: tm.setTransactionTimeout(timeout);
618:
619: try {
620: // Start the transaction
621: tm.begin();
622:
623: //notify checked out connections
624: if (tsl != null)
625: tsl.userTransactionStarted();
626:
627: Transaction tx = tm.getTransaction();
628: if (trace)
629: log.trace("UserTx begin: " + tx);
630:
631: // keep track of the transaction in enterprise context for BMT
632: setTransaction(tx);
633: } finally {
634: // Reset the transaction timeout (if we know what it was)
635: if (oldTimeout != -1)
636: tm.setTransactionTimeout(oldTimeout);
637: }
638: }
639:
640: public void commit() throws RollbackException,
641: HeuristicMixedException, HeuristicRollbackException,
642: SecurityException, IllegalStateException,
643: SystemException {
644: TransactionManager tm = con.getTransactionManager();
645: try {
646: Transaction tx = tm.getTransaction();
647: if (trace)
648: log.trace("UserTx commit: " + tx);
649:
650: int status = tm.getStatus();
651: tm.commit();
652: } finally {
653: // According to the spec, after commit and rollback was called on
654: // UserTransaction, the thread is associated with no transaction.
655: // Since the BMT Tx interceptor will associate and resume the tx
656: // from the context with the thread that comes in
657: // on a subsequent invocation, we must set the context transaction to null
658: setTransaction(null);
659: }
660: }
661:
662: public void rollback() throws IllegalStateException,
663: SecurityException, SystemException {
664: TransactionManager tm = con.getTransactionManager();
665: try {
666: Transaction tx = tm.getTransaction();
667: if (trace)
668: log.trace("UserTx rollback: " + tx);
669: tm.rollback();
670: } finally {
671: // According to the spec, after commit and rollback was called on
672: // UserTransaction, the thread is associated with no transaction.
673: // Since the BMT Tx interceptor will associate and resume the tx
674: // from the context with the thread that comes in
675: // on a subsequent invocation, we must set the context transaction to null
676: setTransaction(null);
677: }
678: }
679:
680: public void setRollbackOnly() throws IllegalStateException,
681: SystemException {
682: TransactionManager tm = con.getTransactionManager();
683: Transaction tx = tm.getTransaction();
684: if (trace)
685: log.trace("UserTx setRollbackOnly: " + tx);
686:
687: tm.setRollbackOnly();
688: }
689:
690: public int getStatus() throws SystemException {
691: TransactionManager tm = con.getTransactionManager();
692: return tm.getStatus();
693: }
694:
695: /**
696: * Set the transaction timeout value for new transactions
697: * started by this instance.
698: */
699: public void setTransactionTimeout(int seconds)
700: throws SystemException {
701: timeout = seconds;
702: }
703: }
704: }
|