http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteOutputCacheProviderTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteOutputCacheProviderTest.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteOutputCacheProviderTest.cs new file mode 100644 index 0000000..bf2fd7e --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteOutputCacheProviderTest.cs @@ -0,0 +1,172 @@ +/* + * 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.Tests +{ + using System; + using System.Collections.Specialized; + using System.Threading; + using Apache.Ignite.Core; + using Apache.Ignite.Core.Common; + using Apache.Ignite.Core.Tests; + using NUnit.Framework; + + /// <summary> + /// Tests for <see cref="IgniteOutputCacheProvider"/> + /// </summary> + public class IgniteOutputCacheProviderTest + { + /** Grid name XML config attribute. */ + private const string GridNameAttr = "gridName"; + + /** Cache name XML config attribute. */ + private const string CacheNameAttr = "cacheName"; + + /** Cache name XML config attribute. */ + private const string SectionNameAttr = "igniteConfigurationSectionName"; + + /** Grid name. */ + private const string GridName = "grid1"; + + /** Cache name. */ + private const string CacheName = "myCache"; + + /// <summary> + /// Fixture setup. + /// </summary> + [TestFixtureSetUp] + public void TestFixtureSetUp() + { + Ignition.Start(new IgniteConfiguration(TestUtils.GetTestConfiguration()) {GridName = GridName}); + } + + /// <summary> + /// Fixture teardown. + /// </summary> + [TestFixtureTearDown] + public void TestFixtureTearDown() + { + Ignition.StopAll(true); + } + + /// <summary> + /// Tests provider initialization. + /// </summary> + [Test] + public void TestInitialization() + { + var cacheProvider = new IgniteOutputCacheProvider(); + + // Not initialized. + Assert.Throws<InvalidOperationException>(() => cacheProvider.Get("1")); + + // Invalid section. + Assert.Throws<IgniteException>(() => + cacheProvider.Initialize("testName", new NameValueCollection + { + {SectionNameAttr, "invalidSection"}, + })); + + // Valid grid. + cacheProvider = GetProvider(); + + cacheProvider.Set("1", 1, DateTime.MaxValue); + Assert.AreEqual(1, cacheProvider.Get("1")); + } + + /// <summary> + /// Tests autostart from web configuration section. + /// </summary> + [Test] + public void TestStartFromWebConfigSection() + { + var cacheProvider = new IgniteOutputCacheProvider(); + + cacheProvider.Initialize("testName2", new NameValueCollection + { + {SectionNameAttr, "igniteConfiguration2"}, + {CacheNameAttr, "cacheName2"} + }); + + cacheProvider.Set("1", 3, DateTime.MaxValue); + Assert.AreEqual(3, cacheProvider.Get("1")); + } + + /// <summary> + /// Tests provider caching. + /// </summary> + [Test] + public void TestCaching() + { + var cacheProvider = GetProvider(); + + Assert.AreEqual(null, cacheProvider.Get("1")); + cacheProvider.Set("1", 1, DateTime.MaxValue); + Assert.AreEqual(1, cacheProvider.Get("1")); + + cacheProvider.Remove("1"); + Assert.AreEqual(null, cacheProvider.Get("1")); + + Assert.AreEqual(null, cacheProvider.Add("2", 2, DateTime.MaxValue)); + Assert.AreEqual(2, cacheProvider.Add("2", 5, DateTime.MaxValue)); + } + + /// <summary> + /// Tests cache expiration. + /// </summary> + [Test] + public void TestExpiry() + { + var cacheProvider = GetProvider(); + cacheProvider.Remove("1"); + + // Set + cacheProvider.Set("1", 1, DateTime.UtcNow.AddSeconds(1.3)); + Assert.AreEqual(1, cacheProvider.Get("1")); + Thread.Sleep(2000); + Assert.AreEqual(null, cacheProvider.Get("1")); + + cacheProvider.Set("1", 1, DateTime.UtcNow); + Assert.AreEqual(null, cacheProvider.Get("1")); + + // Add + cacheProvider.Add("1", 1, DateTime.UtcNow.AddSeconds(0.7)); + Assert.AreEqual(1, cacheProvider.Get("1")); + Thread.Sleep(2000); + Assert.AreEqual(null, cacheProvider.Get("1")); + + cacheProvider.Add("1", 1, DateTime.UtcNow); + Assert.AreEqual(null, cacheProvider.Get("1")); + } + + /// <summary> + /// Gets the initialized provider. + /// </summary> + private static IgniteOutputCacheProvider GetProvider() + { + var cacheProvider = new IgniteOutputCacheProvider(); + + cacheProvider.Initialize("testName", new NameValueCollection + { + {GridNameAttr, GridName}, + {CacheNameAttr, CacheName} + }); + + return cacheProvider; + } + } +}
http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateItemCollectionTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateItemCollectionTest.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateItemCollectionTest.cs new file mode 100644 index 0000000..137382e --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateItemCollectionTest.cs @@ -0,0 +1,267 @@ +/* + * 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.Tests +{ + using System; + using System.IO; + using System.Linq; + using Apache.Ignite.AspNet.Impl; + using Apache.Ignite.Core.Impl.Binary; + using Apache.Ignite.Core.Impl.Binary.IO; + using NUnit.Framework; + + /// <summary> + /// Tests for <see cref="IgniteSessionStateItemCollection"/>. + /// </summary> + public class IgniteSessionStateItemCollectionTest + { + /// <summary> + /// Tests the empty collection. + /// </summary> + [Test] + public void TestEmpty() + { + var col1 = new IgniteSessionStateItemCollection(); + var col2 = SerializeDeserialize(col1); + + foreach (var col in new[] { col1, col2 }) + { + Assert.IsFalse(col.Dirty); + Assert.IsFalse(col.IsSynchronized); + Assert.AreEqual(0, col.Count); + Assert.IsNotNull(col.SyncRoot); + Assert.IsEmpty(col); + Assert.IsEmpty(col.OfType<string>().ToArray()); + Assert.IsEmpty(col.Keys); + Assert.IsNotNull(col.SyncRoot); + + Assert.IsNull(col["key"]); + Assert.Throws<ArgumentOutOfRangeException>(() => col[0] = "x"); + Assert.Throws<ArgumentOutOfRangeException>(() => Assert.AreEqual(0, col[0])); + Assert.Throws<ArgumentOutOfRangeException>(() => col.RemoveAt(0)); + + col.Clear(); + col.Remove("test"); + + Assert.AreEqual(0, col.Count); + + col.Dirty = true; + Assert.IsTrue(col.Dirty); + } + } + + /// <summary> + /// Tests the modification. + /// </summary> + [Test] + public void TestModification() + { + var col = new IgniteSessionStateItemCollection(); + + // Populate and check. + col["key"] = "val"; + col["1"] = 1; + + Assert.AreEqual("val", col["key"]); + Assert.AreEqual(1, col["1"]); + + Assert.AreEqual(2, col.Count); + Assert.IsTrue(col.Dirty); + + CollectionAssert.AreEquivalent(new[] {"key", "1"}, col); + CollectionAssert.AreEquivalent(new[] {"key", "1"}, col.Keys); + + // Modify using index. + col[0] = "val1"; + col[1] = 2; + + Assert.AreEqual("val1", col["key"]); + Assert.AreEqual(2, col["1"]); + + // Modify using key. + col["1"] = 3; + col["key"] = "val2"; + + Assert.AreEqual("val2", col["key"]); + Assert.AreEqual(3, col["1"]); + + // CopyTo. + var keys = new string[5]; + col.CopyTo(keys, 2); + Assert.AreEqual(new[] {null, null, "key", "1", null}, keys); + + // Remove. + col["2"] = 2; + col["3"] = 3; + + col.Remove("invalid"); + Assert.AreEqual(4, col.Count); + + col.Remove("1"); + + Assert.AreEqual(new[] { "key", "2", "3" }, col.OfType<string>()); + Assert.AreEqual(null, col["1"]); + + Assert.AreEqual("val2", col["key"]); + Assert.AreEqual("val2", col[0]); + + Assert.AreEqual(2, col["2"]); + Assert.AreEqual(2, col[1]); + + Assert.AreEqual(3, col["3"]); + Assert.AreEqual(3, col[2]); + + // RemoveAt. + col.RemoveAt(0); + Assert.AreEqual(new[] { "2", "3" }, col.OfType<string>()); + + // Clear. + Assert.AreEqual(2, col.Count); + + col.Clear(); + Assert.AreEqual(0, col.Count); + + // Set dirty. + var col1 = new IgniteSessionStateItemCollection {Dirty = true}; + Assert.IsTrue(col1.Dirty); + } + + /// <summary> + /// Tests dirty tracking. + /// </summary> + [Test] + public void TestApplyChanges() + { + Func<IgniteSessionStateItemCollection> getCol = () => + { + var res = new IgniteSessionStateItemCollection(); + + res["1"] = 1; + res["2"] = 2; + res["3"] = 3; + + return res; + }; + + var col = getCol(); + + var col0 = SerializeDeserialize(col); + + Assert.AreEqual(3, col0.Count); + + col0.Remove("1"); + col0["2"] = 22; + col0["4"] = 44; + + // Apply non-serialized changes. + col.ApplyChanges(col0); + + Assert.AreEqual(3, col.Count); + Assert.AreEqual(null, col["1"]); + Assert.AreEqual(22, col["2"]); + Assert.AreEqual(3, col["3"]); + Assert.AreEqual(44, col["4"]); + + // Apply serialized changes without WriteChangesOnly. + col = getCol(); + col.ApplyChanges(SerializeDeserialize(col0)); + + Assert.AreEqual(3, col.Count); + Assert.AreEqual(null, col["1"]); + Assert.AreEqual(22, col["2"]); + Assert.AreEqual(3, col["3"]); + Assert.AreEqual(44, col["4"]); + + // Apply serialized changes with WriteChangesOnly. + col = getCol(); + col.ApplyChanges(SerializeDeserialize(col0, true)); + + Assert.AreEqual(3, col.Count); + Assert.AreEqual(null, col["1"]); + Assert.AreEqual(22, col["2"]); + Assert.AreEqual(3, col["3"]); + Assert.AreEqual(44, col["4"]); + + // Remove key then add back. + col0.Remove("2"); + col0.Remove("3"); + col0["2"] = 222; + + col = getCol(); + col.ApplyChanges(SerializeDeserialize(col0)); + + Assert.AreEqual(2, col.Count); + Assert.AreEqual(222, col["2"]); + Assert.AreEqual(44, col["4"]); + + // Remove all. + col0 = SerializeDeserialize(getCol()); + col0.Clear(); + + col = getCol(); + col.ApplyChanges(SerializeDeserialize(col0, true)); + + Assert.AreEqual(0, col.Count); + + // Add to empty. + col0["-1"] = -1; + col0["-2"] = -2; + + col = getCol(); + col.ApplyChanges(SerializeDeserialize(col0)); + + Assert.AreEqual(2, col.Count); + Assert.AreEqual(-1, col0["-1"]); + Assert.AreEqual(-2, col0["-2"]); + + // Remove initial key, then add it back, then remove again. + col0 = SerializeDeserialize(getCol()); + + col0.Remove("1"); + col0.Remove("2"); + col0["1"] = "111"; + col0.Remove("1"); + + col = getCol(); + col.ApplyChanges(SerializeDeserialize(col0, true)); + + Assert.AreEqual(1, col.Count); + Assert.AreEqual(3, col["3"]); + } + + /// <summary> + /// Serializes and deserializes back an instance. + /// </summary> + private static IgniteSessionStateItemCollection SerializeDeserialize(IgniteSessionStateItemCollection data, + bool changesOnly = false) + { + var marsh = BinaryUtils.Marshaller; + + using (var stream = new BinaryHeapStream(128)) + { + var writer = marsh.StartMarshal(stream); + + data.WriteBinary(writer.GetRawWriter(), changesOnly); + + stream.Seek(0, SeekOrigin.Begin); + + return new IgniteSessionStateItemCollection(marsh.StartUnmarshal(stream)); + } + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreDataTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreDataTest.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreDataTest.cs new file mode 100644 index 0000000..e8dcd7c --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreDataTest.cs @@ -0,0 +1,117 @@ +/* + * 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.Tests +{ + using System; + using System.IO; + using System.Reflection; + using System.Web; + using Apache.Ignite.AspNet.Impl; + using Apache.Ignite.Core.Impl.Binary; + using Apache.Ignite.Core.Impl.Binary.IO; + using NUnit.Framework; + + /// <summary> + /// Tests for <see cref="IgniteSessionStateStoreData"/>. + /// </summary> + public class IgniteSessionStateStoreDataTest + { + /// <summary> + /// Tests the data. + /// </summary> + [Test] + public void TestData() + { + // Modification method is internal. + var statics = new HttpStaticObjectsCollection(); + statics.GetType().GetMethod("Add", BindingFlags.Instance | BindingFlags.NonPublic) + .Invoke(statics, new object[] { "int", typeof(int), false }); + + var data = new IgniteSessionStateStoreData(statics, 44); + + data.Items["key"] = "val"; + + Assert.AreEqual(44, data.Timeout); + Assert.AreEqual(1, data.StaticObjects.Count); + Assert.AreEqual(0, data.StaticObjects["int"]); + Assert.AreEqual("val", data.Items["key"]); + } + + /// <summary> + /// Tests the empty data. + /// </summary> + [Test] + public void TestEmpty() + { + var data = new IgniteSessionStateStoreData(null, 0); + + Assert.AreEqual(0, data.LockId); + Assert.AreEqual(0, data.Items.Count); + Assert.AreEqual(0, data.Timeout); + Assert.IsNull(data.LockNodeId); + Assert.IsNull(data.LockTime); + Assert.IsNull(data.StaticObjects); + } + + /// <summary> + /// Tests the serialization. + /// </summary> + [Test] + public void TestSerialization() + { + var data = new IgniteSessionStateStoreData(null, 96) + { + Timeout = 97, + LockId = 11, + LockNodeId = Guid.NewGuid(), + LockTime = DateTime.UtcNow.AddHours(-1), + }; + + data.Items["key1"] = 1; + data.Items["key2"] = 2; + + var data0 = SerializeDeserialize(data); + + Assert.AreEqual(data.Timeout, data0.Timeout); + Assert.AreEqual(data.LockId, data0.LockId); + Assert.AreEqual(data.LockNodeId, data0.LockNodeId); + Assert.AreEqual(data.LockTime, data0.LockTime); + Assert.AreEqual(data.Items.Keys, data0.Items.Keys); + } + + + /// <summary> + /// Serializes and deserializes back an instance. + /// </summary> + private static IgniteSessionStateStoreData SerializeDeserialize(IgniteSessionStateStoreData data) + { + var marsh = BinaryUtils.Marshaller; + + using (var stream = new BinaryHeapStream(128)) + { + var writer = marsh.StartMarshal(stream); + + data.WriteBinary(writer.GetRawWriter(), false); + + stream.Seek(0, SeekOrigin.Begin); + + return new IgniteSessionStateStoreData(marsh.StartUnmarshal(stream)); + } + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreProviderTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreProviderTest.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreProviderTest.cs new file mode 100644 index 0000000..fc239ad --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreProviderTest.cs @@ -0,0 +1,425 @@ +/* + * 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.Tests +{ + using System; + using System.Collections.Specialized; + using System.Linq; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; + using System.Web; + using System.Web.SessionState; + using Apache.Ignite.Core; + using Apache.Ignite.Core.Common; + using Apache.Ignite.Core.Tests; + using NUnit.Framework; + + /// <summary> + /// Tests for <see cref="IgniteSessionStateStoreProvider"/>. + /// </summary> + public class IgniteSessionStateStoreProviderTest + { + /** Grid name XML config attribute. */ + private const string GridNameAttr = "gridName"; + + /** Cache name XML config attribute. */ + private const string CacheNameAttr = "cacheName"; + + /** Section name XML config attribute. */ + private const string SectionNameAttr = "igniteConfigurationSectionName"; + + /** Grid name. */ + private const string GridName = "grid1"; + + /** Cache name. */ + private const string CacheName = "myCache"; + + /** Session id. */ + private const string Id = "1"; + + /** Test context. */ + private static readonly HttpContext HttpContext = + new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null)); + + /// <summary> + /// Fixture setup. + /// </summary> + [TestFixtureSetUp] + public void TestFixtureSetUp() + { + Ignition.Start(new IgniteConfiguration(TestUtils.GetTestConfiguration()) { GridName = GridName }); + } + + /// <summary> + /// Fixture teardown. + /// </summary> + [TestFixtureTearDown] + public void TestFixtureTearDown() + { + Ignition.StopAll(true); + } + + /// <summary> + /// Test teardown. + /// </summary> + [TearDown] + public void TearDown() + { + // Clear all caches. + var ignite = Ignition.GetIgnite(GridName); + ignite.GetCacheNames().ToList().ForEach(x => ignite.GetCache<object, object>(x).RemoveAll()); + } + + /// <summary> + /// Test setup. + /// </summary> + [SetUp] + public void SetUp() + { + // Make sure caches are empty. + var ignite = Ignition.GetIgnite(GridName); + + foreach (var cache in ignite.GetCacheNames().Select(x => ignite.GetCache<object, object>(x))) + CollectionAssert.IsEmpty(cache.ToArray()); + } + + /// <summary> + /// Tests provider initialization. + /// </summary> + [Test] + public void TestInitialization() + { + var stateProvider = new IgniteSessionStateStoreProvider(); + + SessionStateActions actions; + bool locked; + TimeSpan lockAge; + object lockId; + + // Not initialized. + Assert.Throws<InvalidOperationException>(() => + stateProvider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId, out actions)); + + // Invalid section. + Assert.Throws<IgniteException>(() => + stateProvider.Initialize("testName", new NameValueCollection + { + {SectionNameAttr, "invalidSection"}, + {CacheNameAttr, CacheName} + })); + + // Valid grid. + stateProvider = GetProvider(); + + CheckProvider(stateProvider); + + // Same grid once again. + stateProvider = GetProvider(); + + CheckProvider(stateProvider); + } + + /// <summary> + /// Tests autostart from web configuration section. + /// </summary> + [Test] + public void TestStartFromWebConfigSection() + { + var provider = new IgniteSessionStateStoreProvider(); + + provider.Initialize("testName3", new NameValueCollection + { + {SectionNameAttr, "igniteConfiguration3"}, + {CacheNameAttr, "cacheName3"} + }); + + CheckProvider(provider); + } + + /// <summary> + /// Tests the caching. + /// </summary> + [Test] + public void TestCaching() + { + bool locked; + TimeSpan lockAge; + object lockId; + SessionStateActions actions; + + var provider = GetProvider(); + + // Not locked, no item. + var res = provider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId, out actions); + Assert.IsNull(res); + Assert.IsNull(lockId); + Assert.IsFalse(locked); + Assert.AreEqual(TimeSpan.Zero, lockAge); + Assert.AreEqual(SessionStateActions.None, actions); + + // Exclusive: not locked, no item. + res = provider.GetItemExclusive(HttpContext, Id, out locked, out lockAge, out lockId, out actions); + Assert.IsNull(res); + Assert.IsNull(lockId); + Assert.IsFalse(locked); + Assert.AreEqual(TimeSpan.Zero, lockAge); + Assert.AreEqual(SessionStateActions.None, actions); + + // Add item. + provider.CreateUninitializedItem(HttpContext, Id, 7); + + // Check added item. + res = provider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId, out actions); + Assert.IsNotNull(res); + Assert.IsNull(lockId); + Assert.AreEqual(7, res.Timeout); + Assert.IsFalse(locked); + Assert.AreEqual(TimeSpan.Zero, lockAge); + Assert.AreEqual(SessionStateActions.None, actions); + + // Lock and update. + res = provider.GetItemExclusive(HttpContext, Id, out locked, out lockAge, out lockId, out actions); + Assert.IsNotNull(res); + Assert.IsNotNull(lockId); + Assert.IsFalse(locked); + Assert.AreEqual(TimeSpan.Zero, lockAge); + Assert.AreEqual(SessionStateActions.None, actions); + provider.SetAndReleaseItemExclusive(HttpContext, Id, UpdateStoreData(res), lockId, true); + + // Not locked, item present. + res = provider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId, out actions); + CheckStoreData(res); + Assert.IsNull(lockId); + Assert.IsFalse(locked); + Assert.AreEqual(TimeSpan.Zero, lockAge); + Assert.AreEqual(SessionStateActions.None, actions); + + // Lock item. + res = provider.GetItemExclusive(HttpContext, Id, out locked, out lockAge, out lockId, out actions); + CheckStoreData(res); + Assert.IsNotNull(lockId); + Assert.IsFalse(locked); + Assert.AreEqual(TimeSpan.Zero, lockAge); + Assert.AreEqual(SessionStateActions.None, actions); + + // Try to get it in a different thread. + Task.Factory.StartNew(() => + { + object lockId1; // do not overwrite lockId + res = provider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId1, out actions); + Assert.IsNull(res); + Assert.IsNotNull(lockId1); + Assert.IsTrue(locked); + Assert.Greater(lockAge, TimeSpan.Zero); + Assert.AreEqual(SessionStateActions.None, actions); + }).Wait(); + + // Try to get it in a different thread. + Task.Factory.StartNew(() => + { + object lockId1; // do not overwrite lockId + res = provider.GetItemExclusive(HttpContext, Id, out locked, out lockAge, out lockId1, out actions); + Assert.IsNull(res); + Assert.IsNotNull(lockId1); + Assert.IsTrue(locked); + Assert.Greater(lockAge, TimeSpan.Zero); + Assert.AreEqual(SessionStateActions.None, actions); + }).Wait(); + + // Release item. + provider.ReleaseItemExclusive(HttpContext, Id, lockId); + + // Make sure it is accessible in a different thread. + Task.Factory.StartNew(() => + { + res = provider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId, out actions); + Assert.IsNotNull(res); + Assert.IsFalse(locked); + Assert.AreEqual(TimeSpan.Zero, lockAge); + Assert.AreEqual(SessionStateActions.None, actions); + }).Wait(); + + // Remove item. + provider.RemoveItem(HttpContext, Id, lockId, null); + + // Check removal. + res = provider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId, out actions); + Assert.IsNull(res); + Assert.IsFalse(locked); + Assert.AreEqual(TimeSpan.Zero, lockAge); + Assert.AreEqual(SessionStateActions.None, actions); + } + + /// <summary> + /// Tests the create new store data. + /// </summary> + [Test] + public void TestCreateNewStoreData() + { + var provider = GetProvider(); + + var data = provider.CreateNewStoreData(HttpContext, 56); + + Assert.AreEqual(56, data.Timeout); + Assert.IsEmpty(data.Items); + Assert.IsEmpty(data.StaticObjects); + + // Check that caches are empty. + var ignite = Ignition.GetIgnite(GridName); + Assert.IsFalse(ignite.GetCacheNames().SelectMany(x => ignite.GetCache<int, int>(x)).Any()); + } + + /// <summary> + /// Tests the expiry. + /// </summary> + [Test] + [Category(TestUtils.CategoryIntensive)] // Minimum expiration is 1 minute + public void TestExpiry() + { + var provider = GetProvider(); + + bool locked; + TimeSpan lockAge; + object lockId; + SessionStateActions actions; + + // Callbacks are not supported for now. + Assert.IsFalse(GetProvider().SetItemExpireCallback(null)); + + // Check there is no item. + var res = provider.GetItem(HttpContext, "myId", out locked, out lockAge, out lockId, out actions); + Assert.IsNull(res); + + // Put an item. + provider.CreateUninitializedItem(HttpContext, "myId", 1); + + // Check that it is there. + res = provider.GetItem(HttpContext, "myId", out locked, out lockAge, out lockId, out actions); + Assert.IsNotNull(res); + + // Wait a minute and check again. + Thread.Sleep(TimeSpan.FromMinutes(1.05)); + + res = provider.GetItem(HttpContext, "myId", out locked, out lockAge, out lockId, out actions); + Assert.IsNull(res); + } + + /// <summary> + /// Tests the create uninitialized item. + /// </summary> + [Test] + public void TestCreateUninitializedItem() + { + bool locked; + TimeSpan lockAge; + object lockId; + SessionStateActions actions; + + var provider = GetProvider(); + provider.CreateUninitializedItem(HttpContext, "myId", 45); + + var res = provider.GetItem(HttpContext, "myId", out locked, out lockAge, out lockId, out actions); + Assert.IsNotNull(res); + Assert.AreEqual(45, res.Timeout); + Assert.AreEqual(0, res.Items.Count); + Assert.AreEqual(0, res.StaticObjects.Count); + } + + /// <summary> + /// Updates the store data. + /// </summary> + private static SessionStateStoreData UpdateStoreData(SessionStateStoreData data) + { + data.Timeout = 8; + + data.Items["name1"] = 1; + data.Items["name2"] = "2"; + + var statics = data.StaticObjects; + + // Modification method is internal. + statics.GetType().GetMethod("Add", BindingFlags.Instance | BindingFlags.NonPublic) + .Invoke(statics, new object[] {"int", typeof(int), false}); + + CheckStoreData(data); + + return data; + } + + /// <summary> + /// Checks that store data is the same as <see cref="UpdateStoreData"/> returns. + /// </summary> + private static void CheckStoreData(SessionStateStoreData data) + { + Assert.IsNotNull(data); + + Assert.AreEqual(8, data.Timeout); + + Assert.AreEqual(1, data.Items["name1"]); + Assert.AreEqual(1, data.Items[0]); + + Assert.AreEqual("2", data.Items["name2"]); + Assert.AreEqual("2", data.Items[1]); + + Assert.AreEqual(0, data.StaticObjects["int"]); + } + + /// <summary> + /// Gets the initialized provider. + /// </summary> + private static IgniteSessionStateStoreProvider GetProvider() + { + var stateProvider = new IgniteSessionStateStoreProvider(); + + stateProvider.Initialize("testName", new NameValueCollection + { + {GridNameAttr, GridName}, + {CacheNameAttr, CacheName} + }); + + return stateProvider; + } + + /// <summary> + /// Checks the provider. + /// </summary> + private static void CheckProvider(SessionStateStoreProviderBase provider) + { + bool locked; + TimeSpan lockAge; + object lockId; + SessionStateActions actions; + + provider.InitializeRequest(HttpContext); + provider.CreateUninitializedItem(HttpContext, Id, 42); + + var data = provider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId, out actions); + Assert.IsNotNull(data); + Assert.AreEqual(42, data.Timeout); + Assert.IsFalse(locked); + Assert.AreEqual(TimeSpan.Zero, lockAge); + Assert.IsNull(lockId); + Assert.AreEqual(SessionStateActions.None, actions); + + provider.ResetItemTimeout(HttpContext, Id); + provider.EndRequest(HttpContext); + provider.Dispose(); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Properties/AssemblyInfo.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Properties/AssemblyInfo.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..afaa6f0 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,42 @@ +/* +* 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. +*/ + +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Apache.Ignite.AspNet.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Apache Software Foundation")] +[assembly: AssemblyProduct("Apache Ignite.NET")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("18ea4c71-a11d-4ab1-8042-418f7559d84f")] + +[assembly: AssemblyVersion("1.8.0.14218")] +[assembly: AssemblyFileVersion("1.8.0.14218")] +[assembly: AssemblyInformationalVersion("1.8.0")] + +[assembly: CLSCompliant(true)] \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/packages.config ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/packages.config b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/packages.config new file mode 100644 index 0000000..c1198cb --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/packages.config @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + 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. +--> + +<packages> + <package id="NUnit.Runners" version="2.6.3" targetFramework="net40" /> +</packages> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj b/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj index 0c273e0..1ac452f 100644 --- a/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj +++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj @@ -47,7 +47,13 @@ </ItemGroup> <ItemGroup> <Compile Include="IgniteOutputCacheProvider.cs" /> + <Compile Include="IgniteSessionStateStoreProvider.cs" /> <Compile Include="IgniteWebUtils.cs" /> + <Compile Include="Impl\ConfigUtil.cs" /> + <Compile Include="Impl\ExpiryCacheHolder.cs" /> + <Compile Include="Impl\IgniteSessionStateStoreData.cs" /> + <Compile Include="Impl\IgniteSessionStateItemCollection.cs" /> + <Compile Include="Impl\SessionStateLockResult.cs" /> <Compile Include="Package-Info.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.ruleset ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.ruleset b/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.ruleset index bc7683a..5a77e40 100644 --- a/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.ruleset +++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.ruleset @@ -2,7 +2,10 @@ <RuleSet Name="Rules for Apache.Ignite.AspNet" Description="Code analysis rules for Apache.Ignite.AspNet.csproj." ToolsVersion="14.0"> <IncludeAll Action="Error" /> <Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed"> + <Rule Id="CA1020" Action="None" /> + <Rule Id="CA1303" Action="None" /> <Rule Id="CA1704" Action="None" /> + <Rule Id="CA2202" Action="None" /> <Rule Id="CA2204" Action="None" /> <Rule Id="CA2243" Action="None" /> </Rules> http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteOutputCacheProvider.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteOutputCacheProvider.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteOutputCacheProvider.cs index 64216dd..d232726 100644 --- a/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteOutputCacheProvider.cs +++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteOutputCacheProvider.cs @@ -18,23 +18,20 @@ namespace Apache.Ignite.AspNet { using System; - using System.Collections.Generic; using System.Collections.Specialized; - using System.Configuration; using System.Diagnostics.CodeAnalysis; - using System.Globalization; using System.Web.Caching; + using Apache.Ignite.AspNet.Impl; using Apache.Ignite.Core; using Apache.Ignite.Core.Cache; - using Apache.Ignite.Core.Cache.Expiry; - using Apache.Ignite.Core.Common; /// <summary> /// ASP.NET output cache provider that uses Ignite cache as a 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"/>). + /// 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. @@ -42,26 +39,7 @@ namespace Apache.Ignite.AspNet public class IgniteOutputCacheProvider : OutputCacheProvider { /** */ - private const string GridName = "gridName"; - - /** */ - private const string CacheName = "cacheName"; - - /** */ - private const string IgniteConfigurationSectionName = "igniteConfigurationSectionName"; - - /** Max number of cached expiry caches. */ - private const int MaxExpiryCaches = 1000; - - /** */ - private volatile ICache<string, object> _cache; - - /** Cached caches per expiry seconds. */ - private volatile Dictionary<long, ICache<string, object>> _expiryCaches = - new Dictionary<long, ICache<string, object>>(); - - /** Sync object. */ - private readonly object _syncRoot = new object(); + private volatile ExpiryCacheHolder<string, object> _expiryCacheHolder; /// <summary> /// Returns a reference to the specified entry in the output cache. @@ -88,7 +66,7 @@ namespace Apache.Ignite.AspNet /// </returns> public override object Add(string key, object entry, DateTime utcExpiry) { - return GetCacheWithExpiry(utcExpiry).GetAndPutIfAbsent(key, entry).Value; + return _expiryCacheHolder.GetCacheWithExpiry(utcExpiry).GetAndPutIfAbsent(key, entry).Value; } /// <summary> @@ -99,7 +77,7 @@ namespace Apache.Ignite.AspNet /// <param name="utcExpiry">The time and date on which the cached <paramref name="entry" /> expires.</param> public override void Set(string key, object entry, DateTime utcExpiry) { - GetCacheWithExpiry(utcExpiry)[key] = entry; + _expiryCacheHolder.GetCacheWithExpiry(utcExpiry)[key] = entry; } /// <summary> @@ -121,46 +99,11 @@ namespace Apache.Ignite.AspNet { base.Initialize(name, config); - var gridName = config[GridName]; - var cacheName = config[CacheName]; - var cfgSection = config[IgniteConfigurationSectionName]; - - try - { - var grid = cfgSection != null - ? StartFromApplicationConfiguration(cfgSection) - : Ignition.GetIgnite(gridName); + var cache = ConfigUtil.InitializeCache<string, object>(config, GetType()); - _cache = grid.GetOrCreateCache<string, object>(cacheName); - } - catch (Exception ex) - { - throw new IgniteException(string.Format(CultureInfo.InvariantCulture, - "Failed to initialize {0}: {1}", GetType(), ex), ex); - } + _expiryCacheHolder = new ExpiryCacheHolder<string, object>(cache); } - /// <summary> - /// Starts Ignite from application configuration. - /// </summary> - private static IIgnite StartFromApplicationConfiguration(string sectionName) - { - var section = ConfigurationManager.GetSection(sectionName) as IgniteConfigurationSection; - - if (section == null) - throw new ConfigurationErrorsException(string.Format(CultureInfo.InvariantCulture, - "Could not find {0} with name '{1}'", typeof(IgniteConfigurationSection).Name, sectionName)); - - var config = section.IgniteConfiguration; - - if (string.IsNullOrWhiteSpace(config.IgniteHome)) - { - // IgniteHome not set by user: populate from default directory - config = new IgniteConfiguration(config) {IgniteHome = IgniteWebUtils.GetWebIgniteHome()}; - } - - return Ignition.Start(config); - } /// <summary> /// Gets the cache. @@ -169,51 +112,12 @@ namespace Apache.Ignite.AspNet { get { - var cache = _cache; - - if (cache == null) - throw new InvalidOperationException("IgniteOutputCacheProvider has not been initialized."); - - return cache; - } - } - - /// <summary> - /// Gets the cache with expiry policy according to provided expiration date. - /// </summary> - /// <param name="utcExpiry">The UTC expiry.</param> - /// <returns>Cache with expiry policy.</returns> - private ICache<string, object> GetCacheWithExpiry(DateTime utcExpiry) - { - if (utcExpiry == DateTime.MaxValue) - return Cache; - - // Round up to seconds ([OutputCache] duration is in seconds). - var expirySeconds = (long) Math.Round((utcExpiry - DateTime.UtcNow).TotalSeconds); - - if (expirySeconds < 0) - expirySeconds = 0; - - ICache<string, object> expiryCache; - - if (_expiryCaches.TryGetValue(expirySeconds, out expiryCache)) - return expiryCache; - - lock (_syncRoot) - { - if (_expiryCaches.TryGetValue(expirySeconds, out expiryCache)) - return expiryCache; - - // Copy on write with size limit - _expiryCaches = _expiryCaches.Count > MaxExpiryCaches - ? new Dictionary<long, ICache<string, object>>() - : new Dictionary<long, ICache<string, object>>(_expiryCaches); - - expiryCache = Cache.WithExpiryPolicy(new ExpiryPolicy(TimeSpan.FromSeconds(expirySeconds), null, null)); + var holder = _expiryCacheHolder; - _expiryCaches[expirySeconds] = expiryCache; + if (holder == null) + throw new InvalidOperationException(GetType() + " has not been initialized."); - return expiryCache; + return holder.Cache; } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteSessionStateStoreProvider.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteSessionStateStoreProvider.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteSessionStateStoreProvider.cs new file mode 100644 index 0000000..1ee6d92 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteSessionStateStoreProvider.cs @@ -0,0 +1,494 @@ +/* + * 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 + { + /** 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()); + + _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 (!(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" />.</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.</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); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ConfigUtil.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ConfigUtil.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ConfigUtil.cs new file mode 100644 index 0000000..3eb3d90 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ConfigUtil.cs @@ -0,0 +1,109 @@ +/* + * 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.Impl +{ + using System; + using System.Collections.Specialized; + using System.Configuration; + using System.Diagnostics; + using System.Globalization; + using Apache.Ignite.Core; + using Apache.Ignite.Core.Cache; + using Apache.Ignite.Core.Cache.Configuration; + using Apache.Ignite.Core.Common; + + /// <summary> + /// Config utils. + /// </summary> + internal static class ConfigUtil + { + /** */ + public const string GridName = "gridName"; + + /** */ + public const string CacheName = "cacheName"; + + /** */ + public const string IgniteConfigurationSectionName = "igniteConfigurationSectionName"; + + /// <summary> + /// Initializes the cache from configuration. + /// </summary> + public static ICache<TK, TV> InitializeCache<TK, TV>(NameValueCollection config, Type callerType) + { + Debug.Assert(config != null); + Debug.Assert(callerType != null); + + var gridName = config[GridName]; + var cacheName = config[CacheName]; + var cfgSection = config[IgniteConfigurationSectionName]; + + try + { + var grid = StartFromApplicationConfiguration(cfgSection, gridName); + + var cacheConfiguration = new CacheConfiguration(cacheName); + + return grid.GetOrCreateCache<TK, TV>(cacheConfiguration); + } + catch (Exception ex) + { + throw new IgniteException(string.Format(CultureInfo.InvariantCulture, + "Failed to initialize {0}: {1}", callerType, ex), ex); + } + + } + + /// <summary> + /// Starts Ignite from application configuration. + /// </summary> + private static IIgnite StartFromApplicationConfiguration(string sectionName, string gridName) + { + IgniteConfiguration config; + + if (!string.IsNullOrEmpty(sectionName)) + { + var section = ConfigurationManager.GetSection(sectionName) as IgniteConfigurationSection; + + if (section == null) + throw new ConfigurationErrorsException(string.Format(CultureInfo.InvariantCulture, + "Could not find {0} with name '{1}'", typeof(IgniteConfigurationSection).Name, sectionName)); + + config = section.IgniteConfiguration; + } + else + config = new IgniteConfiguration {GridName = gridName}; + + // Check if already started. + var ignite = Ignition.TryGetIgnite(config.GridName); + + if (ignite != null) + return ignite; + + // Start. + if (string.IsNullOrWhiteSpace(config.IgniteHome)) + { + // IgniteHome not set by user: populate from default directory. + config = new IgniteConfiguration(config) { IgniteHome = IgniteWebUtils.GetWebIgniteHome() }; + } + + return Ignition.Start(config); + } + + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ExpiryCacheHolder.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ExpiryCacheHolder.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ExpiryCacheHolder.cs new file mode 100644 index 0000000..9678c38 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ExpiryCacheHolder.cs @@ -0,0 +1,113 @@ +/* + * 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.Impl +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Apache.Ignite.Core.Cache; + using Apache.Ignite.Core.Cache.Expiry; + + /// <summary> + /// Holds WithExpiry caches per expiration interval to avoid garbage on frequent WithExpiry calls. + /// </summary> + internal class ExpiryCacheHolder<TK, TV> + { + /** Max number of cached expiry caches. */ + private const int MaxExpiryCaches = 1000; + + /** */ + private readonly ICache<TK, TV> _cache; + + /** Cached caches per expiry seconds. */ + private volatile Dictionary<long, ICache<TK, TV>> _expiryCaches = + new Dictionary<long, ICache<TK, TV>>(); + + /** Sync object. */ + private readonly object _syncRoot = new object(); + + /// <summary> + /// Initializes a new instance of the <see cref="ExpiryCacheHolder{TK, TV}"/> class. + /// </summary> + /// <param name="cache">The cache.</param> + public ExpiryCacheHolder(ICache<TK, TV> cache) + { + Debug.Assert(cache != null); + + _cache = cache; + } + + /// <summary> + /// Gets the cache. + /// </summary> + public ICache<TK, TV> Cache + { + get { return _cache; } + } + + /// <summary> + /// Gets the cache with expiry policy according to provided expiration date. + /// </summary> + /// <param name="utcExpiry">The UTC expiry.</param> + /// <returns>Cache with expiry policy.</returns> + public ICache<TK, TV> GetCacheWithExpiry(DateTime utcExpiry) + { + if (utcExpiry == DateTime.MaxValue) + return _cache; + + Debug.Assert(utcExpiry.Kind == DateTimeKind.Utc); + + // Round up to seconds ([OutputCache] duration is in seconds). + var expirySeconds = (long)Math.Round((utcExpiry - DateTime.UtcNow).TotalSeconds); + + if (expirySeconds < 0) + expirySeconds = 0; + + return GetCacheWithExpiry(expirySeconds); + } + + /// <summary> + /// Gets the cache with expiry. + /// </summary> + /// <param name="expiry">The expiration interval (in seconds).</param> + public ICache<TK, TV> GetCacheWithExpiry(long expiry) + { + ICache<TK, TV> expiryCache; + + if (_expiryCaches.TryGetValue(expiry, out expiryCache)) + return expiryCache; + + lock (_syncRoot) + { + if (_expiryCaches.TryGetValue(expiry, out expiryCache)) + return expiryCache; + + // Copy on write with size limit + _expiryCaches = _expiryCaches.Count > MaxExpiryCaches + ? new Dictionary<long, ICache<TK, TV>>() + : new Dictionary<long, ICache<TK, TV>>(_expiryCaches); + + expiryCache = Cache.WithExpiryPolicy(new ExpiryPolicy(TimeSpan.FromSeconds(expiry), null, null)); + + _expiryCaches[expiry] = expiryCache; + + return expiryCache; + } + } + } +}