Fixing an issue with output parameters that are of type IntPtr
[castle.git] / ActiveRecord / Castle.ActiveRecord / Framework / Scopes / TransactionScope.cs
blobff23964eeafe39f253ec8634c260e16276170bb3
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.ActiveRecord
17 using System;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Data;
21 using NHibernate;
22 using Castle.ActiveRecord.Framework.Scopes;
24 /// <summary>
25 /// Defines the transaction scope behavior
26 /// </summary>
27 public enum TransactionMode
29 /// <summary>
30 /// Inherits a transaction previously create on
31 /// the current context.
32 /// </summary>
33 Inherits,
34 /// <summary>
35 /// Always create an isolated transaction context.
36 /// </summary>
37 New
40 /// <summary>
41 /// Governs the <see cref="TransactionScope"/> behavior
42 /// on dispose if neither <see cref="TransactionScope.VoteCommit"/>
43 /// nor <see cref="TransactionScope.VoteRollBack"/> was called
44 /// </summary>
45 public enum OnDispose
47 /// <summary>
48 /// Should commit the transaction, unless <see cref="TransactionScope.VoteRollBack"/>
49 /// was called before the disposing the scope (this is the default behavior)
50 /// </summary>
51 Commit,
52 /// <summary>
53 /// Should rollback the transaction, unless <see cref="TransactionScope.VoteCommit"/>
54 /// was called before the disposing the scope
55 /// </summary>
56 Rollback
59 /// <summary>
60 /// Implementation of <see cref="ISessionScope"/> to
61 /// provide transaction semantics
62 /// </summary>
63 public class TransactionScope : SessionScope
65 private static readonly object CompletedEvent = new object();
67 private readonly TransactionMode mode;
68 private readonly IsolationLevel isolationLevel;
69 private readonly OnDispose onDisposeBehavior;
70 private readonly IDictionary<ISession, ITransaction> transactions = new Dictionary<ISession, ITransaction>();
71 private readonly TransactionScope parentTransactionScope;
72 private readonly AbstractScope parentSimpleScope;
73 private readonly EventHandlerList events = new EventHandlerList();
74 private bool rollbackOnly, setForCommit;
76 /// <summary>
77 /// Initializes a new instance of the <see cref="TransactionScope"/> class.
78 /// </summary>
79 public TransactionScope() : this(TransactionMode.New)
83 /// <summary>
84 /// Initializes a new instance of the <see cref="TransactionScope"/> class.
85 /// </summary>
86 /// <param name="onDisposeBehavior">The on dispose behavior.</param>
87 public TransactionScope(OnDispose onDisposeBehavior) : this(TransactionMode.New, onDisposeBehavior)
91 /// <summary>
92 /// Initializes a new instance of the <see cref="TransactionScope"/> class.
93 /// </summary>
94 /// <param name="mode">Whatever to create a new transaction or inherits an existing one</param>
95 public TransactionScope(TransactionMode mode) : this(mode, IsolationLevel.Unspecified, OnDispose.Commit)
99 /// <summary>
100 /// Initializes a new instance of the <see cref="TransactionScope"/> class.
101 /// </summary>
102 /// <param name="mode">Whatever to create a new transaction or inherits an existing one</param>
103 /// <param name="onDisposeBehavior">The on dispose behavior.</param>
104 public TransactionScope(TransactionMode mode, OnDispose onDisposeBehavior) : this(mode, IsolationLevel.Unspecified, onDisposeBehavior)
108 /// <summary>
109 /// Initializes a new instance of the <see cref="TransactionScope"/> class.
110 /// </summary>
111 /// <param name="mode">Whatever to create a new transaction or inherits an existing one</param>
112 /// <param name="isolationLevel">The transaction isolation level.</param>
113 /// <param name="onDisposeBehavior">The on dispose behavior.</param>
114 public TransactionScope(TransactionMode mode, IsolationLevel isolationLevel, OnDispose onDisposeBehavior)
115 : base(FlushAction.Auto, SessionScopeType.Transactional)
117 this.mode = mode;
118 this.isolationLevel = isolationLevel;
119 this.onDisposeBehavior = onDisposeBehavior;
121 bool preferenceForTransactionScope = mode == TransactionMode.Inherits ? true : false;
123 ISessionScope previousScope = ScopeUtil.FindPreviousScope(this, preferenceForTransactionScope);
125 if (previousScope != null)
127 if (previousScope.ScopeType == SessionScopeType.Transactional)
129 parentTransactionScope = previousScope as TransactionScope;
131 else
133 // This is not a safe cast. Reconsider it
134 parentSimpleScope = (AbstractScope) previousScope;
136 foreach(ISession session in parentSimpleScope.GetSessions())
138 EnsureHasTransaction(session);
144 #region OnTransactionCompleted event
146 /// <summary>
147 /// This event is raised when a transaction is completed
148 /// </summary>
149 public event EventHandler OnTransactionCompleted
153 if (parentTransactionScope != null)
155 parentTransactionScope.OnTransactionCompleted += value;
157 else
159 events.AddHandler(CompletedEvent, value);
162 remove
164 if (parentTransactionScope != null)
166 parentTransactionScope.OnTransactionCompleted -= value;
168 else
170 events.RemoveHandler(CompletedEvent, value);
175 #endregion
177 /// <summary>
178 /// Votes to roll back the transaction
179 /// </summary>
180 public void VoteRollBack()
182 if (mode == TransactionMode.Inherits && parentTransactionScope != null)
184 parentTransactionScope.VoteRollBack();
186 rollbackOnly = true;
189 /// <summary>
190 /// Votes to commit the transaction
191 /// </summary>
192 public void VoteCommit()
194 if (rollbackOnly)
196 throw new TransactionException("The transaction was marked as rollback " + "only - by itself or one of the nested transactions");
198 setForCommit = true;
201 /// <summary>
202 /// This method is invoked when the
203 /// <see cref="Castle.ActiveRecord.Framework.ISessionFactoryHolder"/>
204 /// instance needs a session instance. Instead of creating one it interrogates
205 /// the active scope for one. The scope implementation must check if it
206 /// has a session registered for the given key.
207 /// <seealso cref="RegisterSession"/>
208 /// </summary>
209 /// <param name="key">an object instance</param>
210 /// <returns>
211 /// <c>true</c> if the key exists within this scope instance
212 /// </returns>
213 public override bool IsKeyKnown(object key)
215 if (mode == TransactionMode.Inherits && parentTransactionScope != null)
217 return parentTransactionScope.IsKeyKnown(key);
220 bool keyKnown = false;
222 if (parentSimpleScope != null)
224 keyKnown = parentSimpleScope.IsKeyKnown(key);
227 return keyKnown ? true : base.IsKeyKnown(key);
230 /// <summary>
231 /// This method is invoked when no session was available
232 /// at and the <see cref="Castle.ActiveRecord.Framework.ISessionFactoryHolder"/>
233 /// just created one. So it registers the session created
234 /// within this scope using a key. The scope implementation
235 /// shouldn't make any assumption on what the key
236 /// actually is as we reserve the right to change it
237 /// <seealso cref="IsKeyKnown"/>
238 /// </summary>
239 /// <param name="key">an object instance</param>
240 /// <param name="session">An instance of <c>ISession</c></param>
241 public override void RegisterSession(object key, ISession session)
243 if (mode == TransactionMode.Inherits && parentTransactionScope != null)
245 parentTransactionScope.RegisterSession(key, session);
247 else if (parentSimpleScope != null)
249 parentSimpleScope.RegisterSession(key, session);
252 base.RegisterSession(key, session);
255 /// <summary>
256 /// This method should return the session instance associated with the key.
257 /// </summary>
258 /// <param name="key">an object instance</param>
259 /// <returns>
260 /// the session instance or null if none was found
261 /// </returns>
262 public override ISession GetSession(object key)
264 if (mode == TransactionMode.Inherits && parentTransactionScope != null)
266 return parentTransactionScope.GetSession(key);
269 ISession session = null;
271 if (parentSimpleScope != null)
273 session = parentSimpleScope.GetSession(key);
276 session = session ?? base.GetSession(key);
278 EnsureHasTransaction(session);
280 return session;
283 /// <summary>
284 /// Flushes the sessions that this scope
285 /// or its parents are maintaining
286 /// </summary>
287 public override void Flush()
289 if (mode == TransactionMode.Inherits && parentTransactionScope != null)
291 parentTransactionScope.Flush();
294 if (parentSimpleScope != null)
296 parentSimpleScope.Flush();
299 base.Flush();
302 /// <summary>
303 /// Ensures that a transaction exist, creating one if neccecary
304 /// </summary>
305 /// <param name="session">The session.</param>
306 protected internal void EnsureHasTransaction(ISession session)
308 if (!transactions.ContainsKey(session))
310 session.FlushMode = FlushMode.Commit;
312 ITransaction transaction;
314 if (isolationLevel == IsolationLevel.Unspecified)
316 transaction = session.BeginTransaction();
318 else
320 transaction = session.BeginTransaction(isolationLevel);
323 transactions.Add(session, transaction);
327 /// <summary>
328 /// Initializes the current transaction scope using the session
329 /// </summary>
330 /// <param name="session">The session.</param>
331 protected override void Initialize(ISession session)
333 if (mode == TransactionMode.Inherits && parentTransactionScope != null)
335 parentTransactionScope.EnsureHasTransaction(session);
336 return;
339 EnsureHasTransaction(session);
342 /// <summary>
343 /// Dispose of this scope
344 /// </summary>
345 /// <param name="sessions">The sessions.</param>
346 protected override void PerformDisposal(ICollection<ISession> sessions)
348 if (!setForCommit && !rollbackOnly) // Neither VoteCommit or VoteRollback were called
350 if (onDisposeBehavior == OnDispose.Rollback)
352 VoteRollBack();
356 if (mode == TransactionMode.Inherits && parentTransactionScope != null)
358 // In this case it's not up to this instance to perform the clean up
359 return;
362 Exception transactionError = null;
364 foreach(ITransaction transaction in transactions.Values)
368 if (rollbackOnly)
370 transaction.Rollback();
372 else
374 transaction.Commit();
377 catch(Exception ex)
379 transactionError = ex;
381 transaction.Dispose();
385 if (parentSimpleScope == null)
387 // No flush necessary, but we should close the session
389 PerformDisposal(sessions, false, true);
391 else
393 if (rollbackOnly)
395 // Cancel all pending changes
396 // (not sure whether this is a good idea, it should be scoped
398 foreach(ISession session in parentSimpleScope.GetSessions())
400 session.Clear();
405 RaiseOnCompleted();
407 if (transactionError != null)
409 throw new TransactionException("An error occured when trying to dispose the transaction", transactionError);
413 /// <summary>
414 /// This is called when a session has a failure
415 /// </summary>
416 /// <param name="session">the session</param>
417 public override void FailSession(ISession session)
419 VoteRollBack();
422 /// <summary>
423 /// Discards the sessions.
424 /// </summary>
425 /// <param name="sessions">The sessions.</param>
426 protected internal override void DiscardSessions(ICollection<ISession> sessions)
428 if (parentSimpleScope != null)
430 parentSimpleScope.DiscardSessions(sessions);
434 /// <summary>
435 /// Raises the on completed event
436 /// </summary>
437 private void RaiseOnCompleted()
439 EventHandler handler = (EventHandler) events[CompletedEvent];
441 if (handler != null)
443 handler(this, EventArgs.Empty);