001: package org.mockejb;
002:
003: import java.io.Serializable;
004: import java.lang.reflect.Method;
005:
006: import javax.naming.*;
007: import javax.transaction.*;
008:
009: import javax.ejb.*;
010:
011: import org.apache.commons.logging.*;
012:
013: import org.mockejb.interceptor.*;
014:
015: /**
016: * Provides the support for the container-managed transactions
017: * according to EJB spec (chapter 18).
018: * <p>
019: * Note that RequiredNew is not fully supported since <code>TransactionManager</code>
020: * does not know how to suspend transactions.
021: * <p>
022: * Transaction policy must be provided in the invocationContext in the "transactionPolicy" field.
023: * If it is not provided the Supprts policy is used.
024: * @author Alexander Ananiev
025: */
026: public class TransactionManager implements Interceptor, Serializable {
027:
028: // logger for this class
029: private static Log logger = LogFactory
030: .getLog(TransactionManager.class.getName());
031:
032: public final static String USER_TRANSACTION_JNDI = "javax.transaction.UserTransaction";
033:
034: public final static String POLICY_CONTEXT_KEY = "transactionPolicy";
035:
036: /**
037: * "Cached" instance of the UserTransaction
038: */
039: private static UserTransaction sharedUserTransaction;
040:
041: private TransactionPolicy policy = TransactionPolicy.SUPPORTS;
042:
043: /**
044: * Creates a new instance of the <code>TransactionManager</code> with the
045: * given policy.
046: * @param policy transaction policy
047: */
048: public TransactionManager(TransactionPolicy policy) {
049: super ();
050: setPolicy(policy);
051: }
052:
053: /**
054: * Creates a new instance of the <code>TransactionManager</code> with the
055: * default (Supports) policy.
056: */
057: public TransactionManager() {
058: super ();
059: }
060:
061: /**
062: * Returns the currently set transaction policy.
063: * @return transaction policy
064: */
065: public TransactionPolicy getPolicy() {
066: return policy;
067: }
068:
069: /**
070: * Sets the transaction policy.
071: * @param policy policy to set.
072: */
073:
074: public void setPolicy(TransactionPolicy policy) {
075:
076: this .policy = policy;
077: }
078:
079: /**
080: * Begins, commits and rolls back the transaction according to the currently
081: * set policy and EJB spec.
082: */
083: public void intercept(InvocationContext invocationContext)
084: throws Exception {
085:
086: UserTransaction newTran = null;
087:
088: TransactionPolicy this CallPolicy = (TransactionPolicy) invocationContext
089: .getOptionalPropertyValue(POLICY_CONTEXT_KEY);
090:
091: if (this CallPolicy == null)
092: this CallPolicy = this .policy;
093: Method method = invocationContext.getTargetMethod();
094: if (handlePolicy(this CallPolicy, invocationContext
095: .getTargetObject(), method, invocationContext
096: .getParamVals())) {
097:
098: newTran = getUserTransaction();
099: log(method, "Begin transaction");
100: newTran.begin();
101:
102: }
103:
104: try {
105: invocationContext.proceed();
106:
107: commitOrRollback(newTran);
108:
109: }
110:
111: /* Here we try to follow the guidelines of chapter 18 of EJB spec
112: * with the exception of javax.transaction.TransactionRolledback
113: * Note that ExceptionHandler already provides logging and wrapping services
114: * so we don't need to do it here
115: */
116: catch (Exception exception) {
117:
118: if (MockContainer.isSystemException(exception)) {
119: // If this transaction was started immediately before this invocation
120: if (newTran != null
121: && (newTran.getStatus() == Status.STATUS_ACTIVE || newTran
122: .getStatus() == Status.STATUS_MARKED_ROLLBACK)) {
123: newTran.rollback();
124: log(method, "Rollback because of system exception");
125:
126: }
127: }
128: // if it is application exception
129: // we should rollback/commit only if it was new transaction context
130: else {
131: // TODO: according to the spec we must ignore the exceptions from the
132: // rollback if we try to rollback in the catch block of the business
133: // exception. We should re-throw business exception
134: try {
135: commitOrRollback(newTran);
136: } catch (RollbackException rollbackEx) {
137: logger
138: .error(
139: "There has been rollback exception trying to rollback the transaction set for rollback. Ignoring. ",
140: rollbackEx);
141: }
142: }
143:
144: throw exception;
145: }
146:
147: }
148:
149: private void commitOrRollback(UserTransaction tran)
150: throws SystemException, RollbackException,
151: HeuristicMixedException, HeuristicRollbackException {
152:
153: if (tran != null) {
154:
155: if (tran.getStatus() == Status.STATUS_ACTIVE) {
156: tran.commit();
157: log("Committing transaction");
158: } else if (tran.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
159: tran.rollback();
160: log("Rollling back transaction");
161: }
162: }
163: }
164:
165: /**
166: * Performs the actions necessary to handle the transaction policy
167: * according to the spec.
168: * Determines whether the new transaction has to begin for the given method.
169: * @param policy policy of this invoker
170: * @param targetObj bean being called
171: * @param method method being called
172: * @param args parameter values of the method being called
173: * @return true if the new transaction must begin for the given method
174: */
175: // TODO: We use TransactionRequiredLocalException even for remote EJBs
176: protected boolean handlePolicy(TransactionPolicy policy,
177: Object targetObj, Method method, Object[] args)
178: throws SystemException, NamingException {
179:
180: boolean newTranRequired = false;
181:
182: if (policy == TransactionPolicy.REQUIRED) {
183:
184: UserTransaction tran = getUserTransaction();
185: newTranRequired = (tran == null
186: || tran.getStatus() == Status.STATUS_NO_TRANSACTION
187: || tran.getStatus() == Status.STATUS_COMMITTED
188: || tran.getStatus() == Status.STATUS_ROLLEDBACK || tran
189: .getStatus() == Status.STATUS_UNKNOWN);
190: } else if (policy == TransactionPolicy.REQUIRED_NEW) {
191: newTranRequired = true;
192: } else if (policy == TransactionPolicy.MANDATORY) {
193:
194: UserTransaction tran = getUserTransaction();
195:
196: if (tran == null
197: || tran.getStatus() == Status.STATUS_NO_TRANSACTION
198: || tran.getStatus() == Status.STATUS_COMMITTED
199: || tran.getStatus() == Status.STATUS_ROLLEDBACK
200: || tran.getStatus() == Status.STATUS_UNKNOWN) {
201:
202: throw new TransactionRequiredLocalException(
203: "Attempt to invoke method with Mandatory policy without transaction context");
204: }
205: } else if (policy == TransactionPolicy.NEVER) {
206:
207: UserTransaction tran = getUserTransaction();
208: if (tran != null
209: || tran.getStatus() == Status.STATUS_ACTIVE) {
210: throw new EJBException(
211: "Attempt to invoke method with Never policy inside transaction context");
212: }
213: }
214: // For Supports we don't need to do anything
215: // NotSupported is the same deal since we can't suspend the transaction
216:
217: return newTranRequired;
218: }
219:
220: /**
221: * Returns UserTransaction object. If <code>setUserTransaction()</code>
222: * was called, will return the object that was set by this method.
223: * Otherwise, tries to obtain UserTransaction object from JNDI.
224: *
225: * @return UserTransaction object
226: */
227: public static UserTransaction getUserTransaction() {
228:
229: UserTransaction userTransaction = null;
230:
231: if (sharedUserTransaction != null) {
232: userTransaction = sharedUserTransaction;
233: } else {
234: // obtain tran from the JNDI
235:
236: try {
237:
238: Context context = new InitialContext();
239: userTransaction = (UserTransaction) context
240: .lookup(USER_TRANSACTION_JNDI);
241: } catch (NamingException namingEx) {
242: throw new MockEjbSystemException(
243: "Errors while trying to obtain javax.transaction.UserTransaction from JNDI",
244: namingEx);
245: }
246: }
247:
248: return userTransaction;
249: }
250:
251: /**
252: * Sets the shared instance of UserTransaction that will be used by MockEJB.
253: * This is convenient when the remote JNDI is used and the cost of obtaining
254: * UserTransaction object from JNDI every time is too high. TransactionManager tries to
255: * get UserTransaction for every EJB method call.
256: *
257: */
258: public void setUserTransaction(UserTransaction userTransaction) {
259: sharedUserTransaction = userTransaction;
260:
261: }
262:
263: private void log(Method method, String message) {
264:
265: log(message + " for \n" + method);
266: }
267:
268: protected void log(String message) {
269: logger.debug(message);
270: }
271:
272: /**
273: * Returns true if the given object is of the same type and
274: * it has the same transaction policy.
275: */
276: public boolean equals(Object obj) {
277: if (!(obj instanceof ClassPatternPointcut))
278: return false;
279:
280: TransactionManager transactionManager = (TransactionManager) obj;
281:
282: return (this .policy == transactionManager.policy);
283: }
284:
285: public int hashCode() {
286: return policy.hashCode();
287: }
288:
289: }
|