| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| namespace Apache.Ignite.AspNet |
| { |
| using System; |
| using System.Collections.Specialized; |
| using System.Diagnostics; |
| using System.Diagnostics.CodeAnalysis; |
| using System.Globalization; |
| using System.Threading; |
| using System.Web; |
| using System.Web.SessionState; |
| using Apache.Ignite.AspNet.Impl; |
| using Apache.Ignite.Core; |
| using Apache.Ignite.Core.Binary; |
| using Apache.Ignite.Core.Cache; |
| using Apache.Ignite.Core.Common; |
| using Apache.Ignite.Core.Impl.Cache; |
| |
| /// <summary> |
| /// ASP.NET Session-State Store Provider that uses Ignite distributed cache as an underlying storage. |
| /// <para /> |
| /// You can either start Ignite yourself, and provide <c>gridName</c> attribute, |
| /// or provide <c>igniteConfigurationSectionName</c> attribute to start Ignite automatically from specified |
| /// configuration section (see <see cref="IgniteConfigurationSection"/>) |
| /// using <c>igniteConfigurationSectionName</c>. |
| /// <para /> |
| /// <c>cacheName</c> attribute specifies Ignite cache name to use for data storage. This attribute can be omitted |
| /// if cache name is null. |
| /// <para /> |
| /// Optional <c>applicationId</c> attribute allows sharing a single Ignite cache between multiple web applications. |
| /// </summary> |
| public class IgniteSessionStateStoreProvider : SessionStateStoreProviderBase |
| { |
| /// <summary> |
| /// The default cache name to be used when <c>cacheName</c> attribute is not specified. |
| /// </summary> |
| public const string DefaultCacheName = "ASPNET_SESSION_STATE"; |
| |
| /** Extension id */ |
| private const int ExtensionId = 0; |
| |
| /// <summary> |
| /// Op codes for <see cref="ICacheInternal.DoOutInOpExtension{T}"/>. |
| /// </summary> |
| private enum Op |
| { |
| /** Lock the session data. */ |
| Lock = 1, |
| |
| /** Update and unlock the session data. */ |
| SetAndUnlock = 2, |
| |
| /** Get the data without lock. */ |
| Get = 3, |
| |
| /** Put the data without lock. */ |
| Put = 4, |
| |
| /** Remove the data without lock. */ |
| Remove = 5 |
| } |
| |
| /** Application id config parameter. */ |
| private const string ApplicationId = "applicationId"; |
| |
| /** */ |
| private volatile string _applicationId; |
| |
| /** */ |
| private volatile ExpiryCacheHolder<string, IgniteSessionStateStoreData> _expiryCacheHolder; |
| |
| /** */ |
| private static long _lockId; |
| |
| /// <summary> |
| /// Initializes the provider. |
| /// </summary> |
| /// <param name="name">The friendly name of the provider.</param> |
| /// <param name="config">A collection of the name/value pairs representing the provider-specific attributes |
| /// specified in the configuration for this provider.</param> |
| [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] |
| public override void Initialize(string name, NameValueCollection config) |
| { |
| base.Initialize(name, config); |
| |
| var cache = ConfigUtil.InitializeCache<string, IgniteSessionStateStoreData>(config, GetType(), |
| DefaultCacheName); |
| |
| _expiryCacheHolder = new ExpiryCacheHolder<string, IgniteSessionStateStoreData>(cache); |
| |
| _applicationId = config[ApplicationId]; |
| } |
| |
| |
| /// <summary> |
| /// Releases all resources used by the <see cref="T:System.Web.SessionState.SessionStateStoreProviderBase" /> |
| /// implementation. |
| /// </summary> |
| public override void Dispose() |
| { |
| // No-op. |
| } |
| |
| /// <summary> |
| /// Sets a reference to the <see cref="T:System.Web.SessionState.SessionStateItemExpireCallback" /> |
| /// delegate for the Session_OnEnd event defined in the Global.asax file. |
| /// </summary> |
| /// <param name="expireCallback">The <see cref="T:System.Web.SessionState.SessionStateItemExpireCallback" /> |
| /// delegate for the Session_OnEnd event defined in the Global.asax file.</param> |
| /// <returns> |
| /// true if the session-state store provider supports calling the Session_OnEnd event; otherwise, false. |
| /// </returns> |
| public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback) |
| { |
| // Expiration events not supported for now. |
| return false; |
| } |
| |
| /// <summary> |
| /// Called by the <see cref="T:System.Web.SessionState.SessionStateModule" /> object |
| /// for per-request initialization. |
| /// </summary> |
| /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param> |
| public override void InitializeRequest(HttpContext context) |
| { |
| // No-op. |
| } |
| |
| /// <summary> |
| /// Returns read-only session-state data from the session data store. |
| /// </summary> |
| /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param> |
| /// <param name="id">The <see cref="P:System.Web.SessionState.HttpSessionState.SessionID" /> for the |
| /// current request.</param> |
| /// <param name="locked">When this method returns, contains a Boolean value that is set to true if the |
| /// requested session item is locked at the session data store; otherwise, false.</param> |
| /// <param name="lockAge">When this method returns, contains a <see cref="T:System.TimeSpan" /> object that |
| /// is set to the amount of time that an item in the session data store has been locked.</param> |
| /// <param name="lockId">When this method returns, contains an object that is set to the lock identifier |
| /// for the current request. For details on the lock identifier, see "Locking Session-Store Data" |
| /// in the <see cref="T:System.Web.SessionState.SessionStateStoreProviderBase" /> class summary.</param> |
| /// <param name="actions">When this method returns, contains one of the |
| /// <see cref="T:System.Web.SessionState.SessionStateActions" /> values, indicating whether the current |
| /// session is an uninitialized, cookieless session.</param> |
| /// <returns> |
| /// A <see cref="T:System.Web.SessionState.SessionStateStoreData" /> populated with session values and |
| /// information from the session data store. |
| /// </returns> |
| public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, |
| out TimeSpan lockAge, out object lockId, |
| out SessionStateActions actions) |
| { |
| actions = SessionStateActions.None; |
| lockId = null; |
| lockAge = TimeSpan.Zero; |
| locked = false; |
| |
| var key = GetKey(id); |
| var data = GetItem(key); |
| |
| if (data != null) |
| { |
| locked = data.LockNodeId != null; |
| |
| if (!locked) |
| { |
| return data; |
| } |
| |
| Debug.Assert(data.LockTime != null); |
| |
| lockAge = DateTime.UtcNow - data.LockTime.Value; |
| |
| lockId = data.LockId; |
| |
| return null; |
| } |
| |
| return null; |
| } |
| |
| /// <summary> |
| /// Returns read-only session-state data from the session data store. |
| /// </summary> |
| /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param> |
| /// <param name="id">The <see cref="P:System.Web.SessionState.HttpSessionState.SessionID" /> for the current |
| /// request.</param> |
| /// <param name="locked">When this method returns, contains a Boolean value that is set to true if a lock |
| /// is successfully obtained; otherwise, false.</param> |
| /// <param name="lockAge">When this method returns, contains a <see cref="T:System.TimeSpan" /> object that |
| /// is set to the amount of time that an item in the session data store has been locked.</param> |
| /// <param name="lockId">When this method returns, contains an object that is set to the lock identifier |
| /// for the current request. For details on the lock identifier, see "Locking Session-Store Data" in |
| /// the <see cref="T:System.Web.SessionState.SessionStateStoreProviderBase" /> class summary.</param> |
| /// <param name="actions">When this method returns, contains one of the |
| /// <see cref="T:System.Web.SessionState.SessionStateActions" /> values, indicating whether the current |
| /// session is an uninitialized, cookieless session.</param> |
| /// <returns> |
| /// A <see cref="T:System.Web.SessionState.SessionStateStoreData" /> populated with session values |
| /// and information from the session data store. |
| /// </returns> |
| public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, |
| out TimeSpan lockAge, |
| out object lockId, out SessionStateActions actions) |
| { |
| actions = SessionStateActions.None; // Our items never need initialization. |
| lockAge = TimeSpan.Zero; |
| lockId = null; |
| |
| var lockId0 = Interlocked.Increment(ref _lockId); |
| |
| var key = GetKey(id); |
| |
| var lockResult = LockItem(key, lockId0); |
| |
| // No item found. |
| if (lockResult == null) |
| { |
| locked = false; |
| |
| return null; |
| } |
| |
| // Item was already locked. |
| if (!lockResult.Success) |
| { |
| locked = true; |
| |
| Debug.Assert(lockResult.LockTime != null); |
| |
| lockAge = DateTime.UtcNow - lockResult.LockTime.Value; |
| lockId = lockResult.LockId; |
| |
| return null; |
| } |
| |
| // Item found and lock obtained. |
| locked = false; |
| lockId = lockId0; |
| |
| if (lockId0 != lockResult.Data.LockId) |
| throw new IgniteException(string.Format(CultureInfo.InvariantCulture, |
| "Invalid session state lock result, " + |
| "expected lockId: {0}, actual: {1}", lockId0, lockResult.Data.LockId)); |
| |
| return lockResult.Data; |
| } |
| |
| /// <summary> |
| /// Releases a lock on an item in the session data store. |
| /// </summary> |
| /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param> |
| /// <param name="id">The session identifier for the current request.</param> |
| /// <param name="lockId">The lock identifier for the current request.</param> |
| public override void ReleaseItemExclusive(HttpContext context, string id, object lockId) |
| { |
| UnlockItem(GetKey(id), (long) lockId); |
| } |
| |
| /// <summary> |
| /// Updates the session-item information in the session-state data store with values from the current request, |
| /// and clears the lock on the data. |
| /// </summary> |
| /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param> |
| /// <param name="id">The session identifier for the current request.</param> |
| /// <param name="item">The <see cref="T:System.Web.SessionState.SessionStateStoreData" /> object that |
| /// contains the current session values to be stored.</param> |
| /// <param name="lockId">The lock identifier for the current request.</param> |
| /// <param name="newItem">true to identify the session item as a new item; false to identify the session |
| /// item as an existing item.</param> |
| [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] |
| public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, |
| object lockId, bool newItem) |
| { |
| Debug.Assert(item != null); |
| |
| var key = GetKey(id); |
| |
| var data = (IgniteSessionStateStoreData) item; |
| |
| if (newItem) |
| { |
| var cache = _expiryCacheHolder.GetCacheWithExpiry(data.Timeout * 60); |
| |
| PutItem(key, data, cache); |
| } |
| else |
| { |
| if (!(lockId is long) || data.LockId != (long) lockId) |
| throw new IgniteException(string.Format(CultureInfo.InvariantCulture, |
| "Invalid session release request, expected lockId: {0}, actual: {1}", data.LockId, lockId)); |
| |
| SetAndUnlockItem(key, data); |
| } |
| } |
| |
| /// <summary> |
| /// Deletes item data from the session data store. |
| /// </summary> |
| /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param> |
| /// <param name="id">The session identifier for the current request.</param> |
| /// <param name="lockId">The lock identifier for the current request.</param> |
| /// <param name="item">The <see cref="T:System.Web.SessionState.SessionStateStoreData" /> that represents |
| /// the item to delete from the data store.</param> |
| public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item) |
| { |
| RemoveItem(GetKey(id)); |
| } |
| |
| /// <summary> |
| /// Updates the expiration date and time of an item in the session data store. |
| /// </summary> |
| /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param> |
| /// <param name="id">The session identifier for the current request.</param> |
| public override void ResetItemTimeout(HttpContext context, string id) |
| { |
| // No-op. |
| |
| // This is not necessary since ResetItemTimeout is called right after SetAndReleaseItemExclusive, |
| // which itself resets the timeout when the item is inserted into cache. |
| } |
| |
| /// <summary> |
| /// Creates a new <see cref="T:System.Web.SessionState.SessionStateStoreData" /> object to be used |
| /// for the current request. |
| /// </summary> |
| /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param> |
| /// <param name="timeout">The session-state <see cref="P:System.Web.SessionState.HttpSessionState.Timeout" /> |
| /// value for the new <see cref="T:System.Web.SessionState.SessionStateStoreData" />, in minutes.</param> |
| /// <returns> |
| /// A new <see cref="T:System.Web.SessionState.SessionStateStoreData" /> for the current request. |
| /// </returns> |
| public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout) |
| { |
| return new IgniteSessionStateStoreData(SessionStateUtility.GetSessionStaticObjects(context), timeout); |
| } |
| |
| /// <summary> |
| /// Adds a new session-state item to the data store. |
| /// </summary> |
| /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param> |
| /// <param name="id">The <see cref="P:System.Web.SessionState.HttpSessionState.SessionID" /> |
| /// for the current request.</param> |
| /// <param name="timeout">The session <see cref="P:System.Web.SessionState.HttpSessionState.Timeout" /> |
| /// for the current request, in minutes.</param> |
| public override void CreateUninitializedItem(HttpContext context, string id, int timeout) |
| { |
| var cache = _expiryCacheHolder.GetCacheWithExpiry((long) timeout * 60); |
| |
| var key = GetKey(id); |
| |
| var data = new IgniteSessionStateStoreData(SessionStateUtility.GetSessionStaticObjects(context), timeout); |
| |
| PutItem(key, data, cache); |
| } |
| |
| /// <summary> |
| /// Called by the <see cref="T:System.Web.SessionState.SessionStateModule" /> object at the end of a request. |
| /// </summary> |
| /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param> |
| public override void EndRequest(HttpContext context) |
| { |
| // No-op. |
| } |
| |
| /// <summary> |
| /// Gets the cache. |
| /// </summary> |
| private ICache<string, IgniteSessionStateStoreData> Cache |
| { |
| get |
| { |
| var holder = _expiryCacheHolder; |
| |
| if (holder == null) |
| throw new InvalidOperationException(GetType() + " has not been initialized."); |
| |
| return holder.Cache; |
| } |
| } |
| |
| /// <summary> |
| /// Gets the key. |
| /// </summary> |
| private string GetKey(string sessionId) |
| { |
| return _applicationId == null ? sessionId : ApplicationId + "." + sessionId; |
| } |
| |
| /// <summary> |
| /// Writes the lock info. |
| /// </summary> |
| private void WriteLockInfo(IBinaryRawWriter writer, long lockId, bool writeTime = false) |
| { |
| writer.WriteGuid(Cache.Ignite.GetCluster().GetLocalNode().Id); |
| writer.WriteLong(lockId); |
| |
| if (writeTime) |
| writer.WriteTimestamp(DateTime.UtcNow); |
| } |
| |
| /// <summary> |
| /// Locks the item. |
| /// </summary> |
| private SessionStateLockResult LockItem(string key, long lockId) |
| { |
| return OutInOp(Op.Lock, |
| w => |
| { |
| w.WriteString(key); |
| WriteLockInfo(w, lockId, true); |
| }, |
| r => new SessionStateLockResult(r)); |
| } |
| |
| /// <summary> |
| /// Unlocks the item. |
| /// </summary> |
| private void UnlockItem(string key, long lockId) |
| { |
| OutOp(Op.SetAndUnlock, |
| w => |
| { |
| w.WriteString(key); |
| w.WriteBoolean(false); // Only unlock. |
| WriteLockInfo(w, lockId); |
| }); |
| } |
| |
| /// <summary> |
| /// Sets and unlocks the item. |
| /// </summary> |
| private void SetAndUnlockItem(string key, IgniteSessionStateStoreData data) |
| { |
| var cache = _expiryCacheHolder.GetCacheWithExpiry(data.Timeout * 60); |
| |
| OutOp(Op.SetAndUnlock, w => |
| { |
| w.WriteString(key); |
| w.WriteBoolean(true); // Unlock and update. |
| data.WriteBinary(w, true); |
| }, cache); |
| } |
| |
| /// <summary> |
| /// Puts the item. |
| /// </summary> |
| private void PutItem(string key, IgniteSessionStateStoreData data, ICache<string, IgniteSessionStateStoreData> cache) |
| { |
| OutOp(Op.Put, w => |
| { |
| w.WriteString(key); |
| data.WriteBinary(w, false); |
| }, cache); |
| } |
| |
| /// <summary> |
| /// Gets the item. |
| /// </summary> |
| private IgniteSessionStateStoreData GetItem(string key) |
| { |
| return OutInOp(Op.Get, w => w.WriteString(key), r => new IgniteSessionStateStoreData(r)); |
| } |
| |
| /// <summary> |
| /// Removes the item. |
| /// </summary> |
| private void RemoveItem(string key) |
| { |
| OutOp(Op.Remove, w => w.WriteString(key)); |
| } |
| |
| /// <summary> |
| /// Invokes the extension operation. |
| /// </summary> |
| private void OutOp(Op op, Action<IBinaryRawWriter> writeAction, |
| ICache<string, IgniteSessionStateStoreData> cache = null) |
| { |
| OutInOp<object>(op, writeAction, null, cache); |
| } |
| |
| /// <summary> |
| /// Invokes the extension operation. |
| /// </summary> |
| private T OutInOp<T>(Op op, Action<IBinaryRawWriter> writeAction, Func<IBinaryRawReader, T> readFunc, |
| ICache<string, IgniteSessionStateStoreData> cache = null) |
| { |
| cache = cache ?? Cache; |
| |
| return ((ICacheInternal) cache).DoOutInOpExtension(ExtensionId, (int) op, writeAction, readFunc); |
| } |
| } |
| } |